I'm building a pretty large application and would like to get some insight on the best way to re-render the application based on a root component state change.
Sample Architecture
<Route path="/" component={App}>
<Route component={Layout}>
<IndexRoute component={Page} />
<Route path={page} component={Page}></Route>
...
</Route>
<Route component={Layout}>
<IndexRoute component={Page} />
<Route path={page} component={Page}></Route>
...
</Route>
In my architecture after a successful login i'm storing the users data in my App component. Every user has a client key which is used as the identifier as to which clients database/data to display. The behavior i would like to accomplish would be to update my current page with the new clients data after the a client change.
Now i could pass my data down to my components as props from my App component but i think this would be inefficient because of two reasons.
I would have to get the data for all my routes before rendering my App component.
My data would become "stale" until i re-render my App component.
Because of these reason i decided to let each page fetch it's own data using the componentWillMount life cycle hook, and passing the data down as props to the pages child components. This works for me because i'm able to fetch fresh data for each page upon navigation.
What i'm currently experiencing is that after a client change my application re-renders but since my current page has already mounted it doesn't fetch the new client data. But everything works as expected if i navigate away from the current page and then back to the page.
One solution i have in mind is to pass the users data down to each page and use the componentWillReceiveProps life cycle hook and perform a comparison check in order to fetch new data. I would like to prevent that if at all possible since my application will have 40+ pages.
Any suggestions would be greatly appreciated.
What you have mentioned about
pass the users data down to each page and use the
componentWillReceiveProps life cycle hook and perform a comparison
check in order to fetch new data.
is perfectly fine. Few examples of such a pattern in the wild:
Redux repository real world example
React Router core team recommends this method too as per issue here.
Related
I have to find a solution to share information between components in React js.
The use case is that in an online store when a guest tries to add a product to his wishlist he should be redirected to the registration page and after registering it should be automatically added to his wishlist.
I tried with window.localStorage and made it work but I was told that it was not necessary to do it this way, the page is a bit complicated and the components do not have common ancestors the component tree is like this.
<App>
<Routes>
<SearchPage>
<Gallery>
<GalleryItem>
<AddToListButton>
</AddToListButton>
</GalleryItem>
</Gallery>
</SearchPage>
</Routes>
</App>
<App>
<Routes>
<Signin>
</Signin>
</Routes>
</App>
<App>
<Routes>
<Route>
<WishlistPage>
<Wishlist>
</Wishlist>
</WishlistPage>
</Route>
</Routes>
</App>
and I have to pass the button information to the registration page and then make it redirect to its wish list when it registers, apart from useContext what other method could I use?
It depend on the requirements, using localStorage allows you to store user session between tabs, even between sessions.
sessionStorage works similar but it stores data only within single session (for example - one browser tab).
You can try to use React Context API (as you mentioned).
One more is to use centralized store (for example React Redux).
Of course there are some additional browser features like IndexedDb or websql to persist some data for the user, but their support for various browsers can be different.
You have plenty of options, starting with the ones you have out of the box.
Lift the state up The best solution for non complex use cases is to get the state to the parent component and pass down the state value and a function that modifies the state to this sibling components that you want talking to each other.
Context If you have a piece of state that lots of children components are depending on you can create a context, wrap the parent component in a context provider and consume that in the childrens with useContext.
Or you can find libraries for state management here
If this "add to wishlist" action is something that happens in the server I highly recommend react-query. If not, try zustand it's easier to use than redux and feels very similar to context but with less boilerplate and without the need to wrap things in a context provider
I'm using React Router Dom and the BrowserRouter object to enable browser based navigation for a site I'm building. Navigation works well, but if a user hits the back button, the entirety of my App's state is wiped clean.
I'm not sure why this is happening. I've tried searching around, but all of the answers I've found don't mention why App state is getting reset.
Here's my implementation:
<BrowserRouter>
<Switch>
<Route path="/" exact render={()=><LandingPage/>}
<Route path="/about" render={()=><About/>}
</Switch>
</BrowserRouter>
Am I doing something glaringly wrong? Could anyone with experience working with React Router give me some advice? Thanks so much!
If someone like me has this problem in the future and comes here from Google, here's what's going on:
React Router doesn't store state between page loads. It just routes traffic between components and handles history, among other things.
You need a separate state management store to preserve state changes. (Redux was recommended).
Thanks #charlietfl for the info.
I need to render a page for base url in react. I defined base url as,
<Router basename="/baseUrl">
<Switch>
<Route path={"/childUrl"}
</Switch>
</Router>
I am able to render page via /baseUrl/childUrl. When accessing, /baseUrl it redirects to /baseUrl/childUrl. How can I set a different page to /baseUrl
I know this is old, but in case anyone else stumbles upon this I would recommend using redux-first-router. It lets you dispatch actions either by changing the url in your browser, or the regular react way. This lets you control how components are rendered and keep the state of your application in sync with the url, without having multiple sources of truth.
Michael Sargent did a brilliant explanation, which can be found here.
And of course, you can also check out the git repo.
I currently have a router set up in my React app that has a link with query params:
<Router history={hashHistory}>
<Route path="/" component={Home} />
<Route path="/details/:id" component={Details} />
</Router>
What I want to do is query my firebase database when the user navigates to the details page (i.e /details/123). This code in the Details component will be:
const id = this.props.params.id;
const rootRef = firebase.database().ref();
rootRef.orderByChild("id").equalTo(id).on("child_added", function (snapshot) {
this.setState({
user: snapshot.value()
});
});
Where would I put the code that queries the firebase database? I would have thought it should not go in the render method as it does not belong there as I am setting the state - is this correct?. I have tried using the componentWillReceiveProps lifecycle function but that does not seem to get called - am I misunderstanding the lifecycles and how react-router works?
render is not the right place to put it, because there's a very strong convention that render should be pure (i.e., it shouldn't modify a component's state).
componentWillReceiveProps isn't right, either, because componentWillReceiveProps is not called when a component first mounts.
The typical place to load necessary state is from componentDidMount. (Using componentWillMount could technically work, too, but if you're doing server-side rendering, then componentWillMount will execute on the server, and dispatching AJAX or database requests from the server instead of the client can cause issues. See here.)
To be really robust, you'd also want to handle the case where the component is unmounted before your asynchronous database query finishes, because calling setState on an unmounted component will trigger a warning. The React blog has some suggested techniques for handling that.
Since you're using react-router, another option is to issue your database query from the route's onEnter event, so that the route doesn't even transition until the database query has finished. I don't have experience with this approach.
Is it possible to refresh the react component route on history pushState?
For ex :
<Route path="/" component={App}>
<Route path="/search/:query/" component={search}/>
</Route>
this.props.history.pushState(null,"/search/test");
Suppose if i am on the search route itself, and in my code am pushing the state,is it possible to refresh the route?
Apparently not, and it's not something the react-router maintainers are planning to support since that's not how react works (i.e. only if virtual dom changes will react reload the component). See this thread for the context of the discussion.
Angular blows everything away and reinitializes all of it. React will do a virtual DOM diff. None of your willMount, didMount etc. hooks will be called. But you'll get "already mounted" hooks like componentWillReceiveProps and componentDidUpdate.
There could be 2,000 people asking for this feature to try to reload their data, but React (not React Router) just doesn't work that way.