react router difference between component and render

ReactjsReact Router

Reactjs Problem Overview


I really don't get the difference between render and component prop in Route in react router, in docs it says that render doesn't create new element but component does, I tried to go back with history but I found componentWillMount is called when I use render in Route, what do they mean by "if you provide an inline function to the component attribute, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component."

Reactjs Solutions


Solution 1 - Reactjs

The source code tells the difference:

if (component)
  return match ? React.createElement(component, props) : null

if (render)
  return match ? render(props) : null

When you use component prop, the component is instantiated per every call of Route#render. It means that, for your component that you pass to component prop of Route, constructor, componentWillMount, and componentDidMount will be executed every time the route is rendered.

For example, if you have

<Route path="/:locale/store" component={Store} />

and the user navigates to /en/store, then goes elsewhere, and then navigates back to /en/store, the Store component will be mounted, then unmounted, and then mounted again. It is similar to doing

<Route path="/:locale/store">
  <Store />
</Route>

Compared to that, if you use render prop, the component is evaluated on every Route#render. Remember that every component is a function? This function will be executed as is, without any lifecycle methods. So when you have it like

<Route path="/:locale/store" render={Store} />

you can think of it as

<Route path="/:locale/store">
  {Store()}
</Route>

It saves you runtime because no lifecycle methods are run, but it also has a downside in case Store component has some post-mount lifecycle methods like shouldComponentUpdate that may increase performance as well.


There was a good post on Medium about this performance hack, please take a look at it. It's written very well and is applicable to React 16, too.

Solution 2 - Reactjs

So I'm confused on this section of docs as well, but I finally figure it out.

The key to understand this is the statement "provide an inline function to the component prop"

We all know that Route component will re-render when the location changed, and react will compare the old and new virtual DOM tree, get some diff result and apply to the real DOM.

And react will try it's best to reuse the DOM node, unless the type or key prop of the new ReactElement is changed.

So

// 1.
const componentA = React.createElement(App, props)
const componentB = React.createElement(App, props)
console.log(componentA.type === componentB.type)             // true

// 2.
const componentA = React.createElement(() => <App />, props)
const componentB = React.createElement(() => <App />, props)
console.log(componentA.type === componentB.type)             // false

All ReactElements created by way 1 have the same type(App component), but they don't have the same type if they are all created by way 2.

Why?

Because there is always a new anonymous function created in the way 2 when the parent component's(The component that contains Route component) render method got invoked, so the type of new&old ReactElement is two different instances of the anonymous function

() => <App />

So in React's point of view, there are different types element and should be treat with unmount old > mount new operation, that means every state or changes you made on the old component got loss everytime the parent component re-render.

But why the render prop avoid the unmount and mount behavior? It's an anonymous function too!?

Here I would like to refer the code that @Rishat Muhametshin posted, the core part of Route component's render method:

if (component)
  // We already know the differences:
  // React.createElement(component)
  // React.createElement(() => <component/>)
  return match ? React.createElement(component, props) : null

if (render)
  return match ? render(props) : null

render prop is a function that return a ReactElement when invoked, what's the type of that returned element?

<Route render={() => <AppComponent />}></Route>

It's AppComponent, not the anonymous function wrapper! Because after jsx compiled:

render = () => React.createElement(AppComponent)
render() = React.createElement(AppComponent)

React.createElement(render) =
  React.createElement(() => React.createElement(AppComponent))

React.createElement(render()) =
  React.createElement(React.createElement(AppComponent))

So when you use render instead of component prop, the type of element that render prop function return will not change on each render, even there always an new anonymous function instance created on each parentElement.render()

On my point of view, you can achieve the same behavior that render prop does with component prop by giving a name to the anonymous function:

// Put this line outside render method.
const CreateAppComponent = () => <AppComponent />

// Inside render method
render(){
  return <Route component={CreateAppComponent}/>
}

So the conclusion is, there is not performance different between component and render prop if you are use component={AppComponent} directly, if you want to assign some props to AppComponent, use render={() => <AppComponent {...props}/> } instead of component={() => <AppComponent {...props}/> }

Solution 3 - Reactjs

Most of concepts have been explained by other answers, Let me sort it out by following:

First of all, we have source code:

if (component)
  return match ? React.createElement(component, props) : null

if (render)
  return match ? render(props) : null

###case #1: component without function

<Route path="/create" component={CreatePage} />

React.createElement(CreatePage, props) be called because of React.createElement(component, props) from source code. The instantiation would cause to be remounting.

###case #2: render without function

<Route path="/create" render={CreatePage} />

React.createElement(CreatePage, props) was called before passing into render prop, and then called by render(props) from source code. No instantiation, no remounting.

###case #3: component with function

<Route path="/create" component={ () => <CreatePage /> } />

React.createElement(CreatePage, props) be called twice. First for jsx parsing(anonymous function), First for returning an instance of CreatePage from anonymous function, second from source code. So why don't do this in component prop.

Errors point out by oligofren: >Parsing the JSX does not call it. It just ends up creating the function expression. The reason you don't want to do #3 is that you create a new anonymous type each time, causing re-mounting of the dom.

###case #4: render with function

<Route path="/create" render={ () => <CreatePage /> } />

There is an instantiation(jsx parsing) each time when routing to path=/create. Does it feel like case #1?

Conclusion

According to the four cases, If we want to pass prop to Component, we need to use the case #4 to prevent remounting.

<Route path="/abc" render={()=><TestWidget num="2" someProp={100}/>}/>

This is a bit far from the topic, so I leave the official discussion for further reading.

Solution 4 - Reactjs

Even if we don't pass any props to ComponentToRender, I found some benefits from using render instead of component. By default <Route \> pass additional props({ history, location, match }) to ComponentToRender when using component. We can access this props via render callback too, but also we can omit it. Why do we need it? Every render of <Route />'s parent or any navigation(even if change route to same as before) create new match object. So when we pass it to our ComponentToRender, we will get new props every time, what may cause some performance issues, especially with PureComponent.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionscullyView Question on Stackoverflow
Solution 1 - ReactjsrishatView Answer on Stackoverflow
Solution 2 - Reactjs小广东View Answer on Stackoverflow
Solution 3 - ReactjsTokenyetView Answer on Stackoverflow
Solution 4 - ReactjsgsagasgzView Answer on Stackoverflow