I wanted to understand the exact differences between the 2 ways of writing React <Route /> i.e. one with "render" and other with "children" prop;
// With "render"
<Route
path={route.path}
render={props => (
// pass the sub-routes down to keep nesting
<route.component {...props} routes={route.routes} />
)}
/>
// With "children"
<Route
path={route.path}
exact={route.exact}
children={props => <route.component {...props} routes={route.routes} />}
/>
Also, with the 2nd use case (i.e. with "children"), what difference does it make when exact property is true v/s false ?
From the docs of
React Router's render function
This allows for convenient inline rendering and wrapping without the undesired remounting explained above.Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches
And
React Router's children function
Sometimes you need to render whether the path matches the location or not. In these cases, you can use the function children prop. It works exactly like render except that it gets called whether there is a match or not.
The main difference as per what I understand is that when we pass a component as children prop then that component will get rendered even if the location matches or not.
I have created a sample app in order to simulate the same, here is the app
Related
In my parent App.js file I have :
<Route path="/login" component={Login} onFormSubmit={this.onLoginSubmit} />
But in Login.js when I do :
this.props.onFormSubmit(this.state.email, this.state.password);
I get : TypeError: this.props.onFormSubmit is not a function
EDIT : I see that this is a workaround :
<Route path="/login" render={(props) => (<Login onFormSubmit={this.onLoginSubmit} {...props} />)} />
Or this the only / right way to get this working like this ?
You should use render props like this.
<Route
path='/login'
render={(props) => (
<Login onFormSubmit={this.onLoginSubmit} />
)}
/>
I am assuming that you are using react-router-dom here. In that case, the Route component injects only three properties to the component property, Login.
You may refer to https://reactrouter.com/web/api/Route/route-props, but basically only the following values will be exposed (i.e. injected) by Route to the Login component:
match
A match object contains information about how a matched the URL
location
Locations represent where the app is now, where you want it to go, or even where it was
history
The term “history” and "history object" in this documentation refers to the history package, which is one of only 2 major dependencies of React Router (besides React itself), and which provides several different implementations for managing session history in JavaScript in various environments
Hence, passing an arbitrary property value like onFormSubmit will not work since Route does not inject it into the child component. That's the reason why you're getting the TypeError. If you really need to expose this.onLoginSubmit, I suggest you use React context, aside from the answer by Nilesh that uses render props.
Say, I have the following wrapper:
<Switch>
<Route path={`/app/list`} component={ListPage} key="list"/>
<Route path={`/app/view`} component={EditPage} key="view"/>
<Route path={`/app/edit`} component={EditPage} key="edit"/>
<Route path={`/app/new`} component={EditPage} key="new"/>
</Switch>
When I load the corresponding component, say ListPage, I want to be able to get the key of the current route, in this case, it would be "list".
If I check this.props.match, I have only isExact, params, path, url but not the key.
You don't get the key property because you just provide the component that you want to get rendered by the Router to the Route component and this way you cannot specify what props your component will accept except the default ones - match, location, history. You can read more about it here.
You can use the render prop in order to specify how to render the component by providing a render function and this way you can specify the props that your component will accept for example - key. The function will have all the default props provided by the router and you can just spread them as props on your component.
Since key and ref are reserved props in React and they are not passed to the component, I've renamed the key prop to keyName. More info can be found here.
It will look like:
<Switch>
<Route path="/app/list" render={(props) => <ListPage {...props} keyName="list"/>} />
<Route path="/app/view" render={(props) => <EditPage {...props} keyName="list"/>}/>
<Route path="/app/edit" render={(props) => <EditPage {...props} keyName="edit"/>} />
<Route path="/app/new" render={(props) => <EditPage {...props} keyName="new" />}/>
</Switch>
and you will be able to access the key by using props.key inside the component that is rendered.
Of course it would be better to extract the render function into a function called renderWithKey or something in order remove the duplication so the code to be cleaner.
It's reserved property used in react or react-router internally
If you want to get list use different property. ref, key are reserved
I ran into an issue with HotModuleReloading, using ReactRouter for the first time. The browser console would display the correct change updates, but the window itself would not update.
From the official docs:
When you use component (instead of render or children, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, 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. When using an inline function for inline rendering, use the render or the children prop (below).
I read that as render reduces the amount of unnecessary re-renders, here are their docs:
This allows for convenient inline rendering and wrapping without the undesired remounting explained above.Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop receives all the same route props as the component render prop.
I had been using the render method like so:
const App = () => {
return (
<Switch>
<Route exact path="/" render={() => <Home />} />
</Switch>
);
};
I tried removing my Redux <Provider> content, but no changes. So I swapped out render for component like so and it works fine:
const App = () => {
return (
<Switch>
<Route exact path="/" component={Home} />
</Switch>
);
};
So, why is this?? What am I missing?
When you use component the Route component passes certain props to the rendered component - such as location, history, match etc.
When you use render you're rendering the component in JSX like <Home />. This doesn't have any props passed to it.
If for whatever reason, you'd need to use render over component, you should pass the same props as component does:
const App = () => {
return (
<Switch>
<Route exact path="/" render={(props) => <Home ...props />} />
</Switch>
);
};
This will make sure Home gets the props it needs to deal with Router stuff.
Hope this helps you get to the bottom of it!
This Route is using the render prop to render a child component with props:
<Route to='/path/:id' render={ () => (<ChildComponent myProp={myProp} match={this.props.match}/>)} />
But the version of match passed seems to be 'matching' against the parent component's route state and is thus not registering id underneath match.route.params.
I figured some solution like this might synchronize the route state:
<Route to='/path/:id' render={ () => (withRouter(<ChildComponent myProp={myProp} />))} />
But that just leads to an error saying that child components cannot be functions...
What's the correct way to deal with this?
Since you want to pass the match parameter, you shouldn't pass the one available to parent but the one relevant to the Route. render prop callback receives the Router Props which you can pass down
<Route to='/path/:id' render={(routerProps) => (<ChildComponent {...routerProps} />)} />
or you can simply write
<Route to='/path/:id' component={ChildComponent} />
in which case it will pass on the relevant router props to the component.
and in the child component you can access the match props like
this.props.match.params.id
Id like to use my app entry point as a global state store. Passing info down to children as props.
Using react-router 4, how can I send prop data down to the rendered components. In a similar fashion to this:
<Route Path=“/someplace” component={someComponent} extra-prop-data={dataPassedToSomeComponent} />
I’ve seen some janky workarounds for older versions of react-router, that appear to be deprecated.
What is the correct way of doing this in v4?
You can pass in a function to the render prop instead of passing in the component directly to the component prop.
<Route path="/someplace" render={() => <SomeComponent props={} />} />
You can read more here.
And to add to the above answer, if you want the routing properties accessible to the component you need to include those. Now when the router activates "SomeComponent", the component will get all the routing props plus the extra param(s) - in this example "param".
<Route path='/someplace' component={(props) => <SomeComponent param="yo" {...props}/>} />
Technically there is 2 ways to do it.
The first one (not the most efficient) is to pass an inline function to the component prop:
<Route
path=“/someplace”
component={(props) => (
<SomeComponent {...props} extra-prop-data={ dataPassedToSomeComponent } />
) />
The second one is the best solution. To prevent create a new component on every render like on the first exemple, we pass the same inline function but this time to the render prop:
<Route
path=“/someplace”
render={(props) => (
<SomeComponent {...props} extra-prop-data={ dataPassedToSomeComponent } />
) />