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."
4 Answers
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.
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}/> }
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.
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
.