question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Bug: Certain uses of render props result in remounts on every render

See original GitHub issue

React version: 16.13

Both of the following code snippets produce an input component that loses focus on render: Sandbox

// render prop is passed a component it can render
const Wizard = ({ components, render }) => {
  const [state, setState] = React.useState({})
  const BaseComp = components[0]
  const Component = props => <BaseComp state={state} setState={setState} {...props} />
  return render({ Component })
}

const ChildComponent = ({ state, setState }) => {
  React.useEffect(() => console.log("mounting"), []) // for demonstration purposes only
  const { foo = "" } = state
  const handleChange = ({ target: { name, value } }) => {
    setState(prevState => ({ ...prevState, [name]: value }))
  }
  return <input name="foo" value={foo} onChange={handleChange} />
}

const Frame = ({ Component }) => (
  <div style={{ margin: 20 }}>
    <Component /> // {Component()} works
  </div>
)

const App = () => (
  <Wizard
    components={[ChildComponent]}
    render={Frame}
  />
)

Sandbox

// render prop is passed already rendered children
const Wizard = ({ components, render }) => {
  const [state, setState] = React.useState({})
  const BaseComp = components[0]
  const children = <BaseComp state={state} setState={setState} />
  return render({ children })
}

const ChildComponent = ({ state, setState }) => {
  React.useEffect(() => console.log("mounting"), []) // for demonstration purposes only
  const { foo = "" } = state
  const handleChange = ({ target: { name, value } }) => {
    setState(prevState => ({ ...prevState, [name]: value }))
  }
  return <input name="foo" value={foo} onChange={handleChange} />
}

const createFrame = () => ({ children }) => (
  <div style={{ margin: 20 }}>{children}</div>
)

const Frame = createFrame()

const App = () => (
  <Wizard
    components={[ChildComponent]}
    render={props => {
      const Comp = createFrame()
      // const Comp = Frame // uncomment this line and comment out the one above and it works
      return <Comp {...props} /> // return createFrame()(props) also works
    }}
  />
)

Context

In an app I’m working on there is a wizard component that does state management for an array of pages. It takes a render prop which is passed the current page and other stuff like status and navigation functions so that the look of the wizard is separated from the state management. As the render props passed to different wizards have gotten more complex, we’ve had issues with inputs losing focus and even some app crashes.

Preliminary Investigation

In both of the above examples, ChildComponent is being remounted on every render of the wizard.

One thing I noticed in the 2nd example is that it can be fixed by swapping Frame and createFrame(). I find this concerning because I thought functional components were pure functions and when working with pure functions you can swap the left side of an assignment for the right side without affecting the result.

Edit: It seems to be partially a syntax issue. Both examples can be made to work by avoiding JSX

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

5reactions
gaearoncommented, Mar 3, 2020

This should explain what’s happening: https://reactjs.org/docs/reconciliation.html#elements-of-different-types

In general, defining components inside other components (or Hooks) is a mistake. Instead, usually you want to declare them outside.

2reactions
jddxfcommented, Mar 3, 2020

You were writing const Component = props => <BaseComp state={state} setState={setState} {...props} /> in the first example and createFrame in the second. So in both examples, a new type of component is generated on every render. This a hint that React should unmount the old component and mount the new one instead of reusing the old one .

Read more comments on GitHub >

github_iconTop Results From Across the Web

reactjs - Render prop in lists keeps remounting - Stack Overflow
I have a List component that accepts a render prop. The render function then gets passed a Component. Something like this:
Read more >
Strict Mode - React
StrictMode is a tool for highlighting potential problems in an application. Like Fragment , StrictMode does not render any visible UI.
Read more >
A Story of a React Re-Rendering Bug - Engineering Blog
From Redux's doc about connect, it is a wrapper function which will return a wrapped component with some props injected. This means, every...
Read more >
Why you should avoid using state for computed properties
Since we calculate this computed state property on every render wouldn't ... as props to the editor component, which it then uses as...
Read more >
React, Iframes, and a Back-Navigation Bug
To solve this problem, we need to remount the entire iframe instead of only re-rendering it. This is why the code above works:...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found