I've got some troubles understanding what's the diferrence between this:
<Route path="user/:id" component={UserComponent} />
and this:
<Route path="user/:id" children={<UserComponent/>} />
inside Switch component when using React Router.
In both cases those components will render if the url will look like this "/user/4322". I'm reading React Router documentation but I can't understand this use case correctly (https://reactrouter.com/core/api/Route/route-props)
Okay, I got it:
https://forum.freecodecamp.org/t/react-router-what-is-the-difference-between-route-children-and-component-props/429503
#DanCouper from FreeCodeCamp forum explained it perfectly:
the first one is if you want to mount a new component when the route
matches. This is what things most often look like: you have a set of
routes and they all open a new “screen”, last component unmounts, new
component mounts, everything gets blown away. It’s the simplest way to
do things.
second one is if you want something to render all the time and not
remount. So for example, as in the example in the docs, you have a set
of navigation links, and you want to animate between them on route
change. If you used component this wouldn’t work, because the new
component would mount every time and the animations wouldn’t happen.
But because children is used, can still have the URL change in the
browser but not have everything be blown away every time the URL
changes.
children (and render) are going to be more fiddly to use most of the
time, because they cater to less common scenarios (in children's case,
preventing new component creation every time routes change, allowing
the UI to dynamically change based on route).
Related
Consider the following example:
return (
<Router>
<Route component={Table} path='/table' />
</Router>
);
Let's assume we're on the /table route the entire time.
Whenever the URL changes, the Router will re-render one of the Route components (even when the path doesn't change, see: https://github.com/ReactTraining/react-router/issues/7539).
My users like to share the URLs with each other and expect the displayed page to be more or less the same. Therefore, I need to store some information into the URL, e.g. /table?country=Narnia. The problem is, when I append or edit the search query, the child component will be re-rendered (because the search inside props.location changed), which will in turn trigger the update of its children and so on. In truth, only a part of the page needs to take into account this change inside the URL, e.g. table might apply some sort of filtering logic to the data.
What is the best way to achieve this behavior? I am talking about the likes of useSelector in Redux or Context Provider Pattern with React's Context.
Using useLocation/useMatch in nested children will also result in unwanted renders - one component might be interested in changes to parameter country, but some other might want subscribe to parameter age. useLocation would cause both to re-render when the url changes.
I am trying to avoid writing custom useEffect to control rendering of components subscribing to route changes.
So I'm still learning React, but I feel like I understand at least the "basics". However, Routing with React router and just general "architecture" is where I'm getting a little lost at.
I'm making a personal project app, but ill keep my descriptions/components generic for the explanation. Essentially I have 3 Main components right now:
<Table/> (Rendered in "App" and also the main / URL route, it's essentially just a bootstrap table. Also contains my state which is an array of objects (That will be eventually retrieved via JSON via a GET request to an API)
<Row/> (A single row of data on the table, represented by 1 object in the array of objects from the state provided in <Table/>. Also contains a button that takes you to the "View" page/component below)
<View/> (What is going to be a "View/Details" page for the Row)
So I imagine my route for <View/> is going to be something like <Route path="/:id/view" children={<View/>} /> (Keeping it super simple and generic right now just for the explanation)
Sidenote: How come sometimes I see <Route> wrapping a component and sometimes I see <Route> defined with the children prop pointing at the Component to render?)
Anyways...the button in <Row/> will be a <Link to={'{$props.id}/view'}> I think....however how exactly do I pass the props down to <View/> based on the :id param? I need to pass the data I receive in the <Row/> component onto the <View/> component (Because within the <Row/> component is where I link to <View/>.
I suppose ONE option would be to do something like let {id} = useParams(); and then do a GET request specific for that particular resource from the API. But considering I'm already passing the data down to each row and it's available in the state within <Table/> it seems like an unnecessary HTTP request.
Im probably making this more complex than I need to, but I need to pass the "data" all the way down from <Table/> to the <View/> component depending on the data id. And I'm not really sure how to set up React router to do that. Hopefully what I'm asking makes sense.
To answer your first question, I'll reference the react-router documentation as this is answered in the subsection named "route render methods".
The tldr of the above is simply that the "preferred" method of rendering components via <Route/> is by supplying actual children to the component. However, there are other methods and specific use cases that go along with each.
As for the core issue you're asking about: What you'll want to do is use an object in the <Link /> component that persists a state to the <View /> element that has all of the information that it will need. You can find react-router's documentation on this here.
Our current app has each screen implement a AppLayout component, that takes a body and a header prop.
While the body prop is often very different, the (quite complex) header has a lot of repeating content between the various screens. At times just the title text is changing.
Currently, going from one route to another rerenders the entire screen, a quite wasteful operation given the similar content. Is there a way of avoid re-rendering and re-mounting of common sub-components between two screens?
Example of routing setup
This shows how a lot is seemingly repeated in the trees.
<Route
exact
path='/user'
component={()=> {
return (<AppLayout
header={<AppHeader heading="Users"/>}
main={<UserList/>} />);
} />
<Route
exact
path='/user/new'
component={()=>{
return (<AppLayout
header={<AppHeader heading="Create user"/>}
main={<UserAccountAdd />} />);
} />
<Route
exact
path='/user/:userId'
component={({ match }) =>
return (<AppLayout
header={<AppHeader heading="User profile"/>}
main={<UserAccountInfo userId={match.params.userId}/>}
/>)
} />
The AppHeader component basically looks like
connected(div)
ReallyExpensiveFoo
ReallyExpensiveBar
<p>{this.props.title}</p>
Basically I would like to have seen that the <p> tag of header that renders the heading title should remount.
React doesn't update whole DOM on each state update. Instead it lets each class component to decide if it should be re-rendered by calling its shouldComponentUpdate() method. For functional components it, most likely, simply compares component props to decide if component should re-render.
You can avoid re-rendering of the component by converting your component into class component and by implementing your own version of shouldComponentUpdate(). However normally it is not necessary as default behavior (or versions provided by e.g. Redux's connect() HoC) are smart enough to avoid unnecessary re-rendering and provide good application's performance.
React Router has a good SideBar example on their site that is quite relevant. Basically it injects pairwise Routes at different parts in the hierarchy, so they update independently to routing changes.
I think need your help.
Currently I am working with React.
I think in React, Container and Route are 1: 1. So when I do Router configuration with React. In a typical 'Redux' configuration, the Container(smart component) same to the number of pages in the route.
If I look at the implementation pattern of others, the Container does not just reflect the route.
Common There are many more cases than Route.
In the structure using 'Redux', is Container 1: 1 Route?
Also, Container does not seem to have an HTML Tag, is this correct?
This is not necessarily true, you may have a route that renders a component that doesn't have behaviour and because of that it'd still be a presentational component.
I'd recommend you to not get overwhelmed by the definitions of containers and component. Try to think a Route as just another component with the difference that when the path matches it will render the specified component (or in line function if you used the render property of the route.
Always start simple by creating components that are just functions which return content, and then as the requirements are changing start growing by declaring a class which extends Component and then start decoupling the returned elements into other components. Same thing with Redux, by default don't assume you always need behaviour/state so don't even connect the component and then start growing by adding state and then adding actions. But whenever you think your component has many responsibilities start the cycle again by decoupling into more components.
The use case is that I want to map the root (/) to one of two different components based on whether the user is logged in or not, and I want these two components to reside in different bundles and lazily loaded, so simply putting the login check in the render() method would not do.
I tried to use dynamic route definition with require.ensure() to lazily load the component, and it works for the first time, but after changing the login state the component doesn't get updated (even if I navigate to another route and back to / ).
I tried to force re-rendering the router by setting props on the component that contains the router, both manually and by making it a Redux connected component, and I also tried to add a listener to the Redux store and change the component state in response to login change, but in all of the attempts I got the error "You cannot change ; it will be ignored" and the component doesn't change.
My ugly solution is to have the different component loading code outside of the router, listen to the login state change and in response load the matching component and set it in the wrapping component's state, which is referenced in the render() code. Is there a clean "React-Router-ish" way to do what I want?
React Router 4 pretty much solves this as it made the route configuration part of the component rendering, so having conditional rendering is the same whether it's based on the location or on other props/state.
The closest thing to a clean "React-Router-ish" way to do that is to use the React Router Enterhooks.
An enter hook is a user-defined function that is called when a route is about to be rendered. It receives the next router state as its first argument. The replace function may be used to trigger a transition to a different URL.
So, use the onEnter(nextState, replace, callback?) attribute on your <Route />.
Called when a route is about to be entered. It provides the next router state and a function to redirect to another path. this will be the route instance that triggered the hook.
If callback is listed as a 3rd argument, this hook will run asynchronously, and the transition will block until callback is called.
The general best practice I follow is to place the auth-check flow away from your routes, and place it inside the transition events/hooks.
The usual behavior is - before the route handler actually gets rendered, check the auth, and redirect the user to another route. In your case, if you want to use the same route, but render different components - you should be able to do that using the same technique too. However, that's not a common thing (based on what I've seen), but it should be possible.
For a complete example of this approach, here's the auth-flow code example you can check. It is shared by the creators of React Router, so it looks credible to me.
PS: My answer is valid for React Router versions > 0.13.x.