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.
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 currently having some trouble using React-router v4, with redirections when using URL parameters.
Here's the route I'm using:
<Route path="/blog/page/:number" component={Blog} />
I'm using the UI framework Semantic-ui-react and its component Pagination ( https://react.semantic-ui.com/addons/pagination#pagination-example-shorthand) and I can't render its elements as Links or NavLinks from react-router-dom.
Instead, I have to use the event function onPageChange(activePage), which allow me to redirect to the page clicked.
I figured a way to do so, by rendering a <Redirect to={location} push /> component from react-router-dom, and it works quite well except that it requires a component update management as it won't remount the component Blog...
My issue is, that I can't use browser's (chrome) button to navigate forward and backward, it just modifies the URL and doesn't remount the component.
As I have no hook to control the browser "previous" and "next" button, I'm wondering how I could fix it.
Thank you.
react-router has a built in history management and you can navigate using the push(new_location) function.
I found a way.
Actually when you push the previous and next button of your browser, if you are using the same component, the life cycle's methods componentWillUpdate () and componentDidUpdate() will be triggered.
I parse the URL with this.props.history.location, and now, I can make sure that the component will rerender with the right datas when thoses buttons are pushed.
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.
I was wondering how it's the best way to render server-side (without rendering this on the client) AND handle the state changes coming from async queries.
Ideal Example:
A server gets a requests to generate an HTML
The server 'requires' a react component, passing some props
The react component renders in the server, but it has to do an
Ajax/Socket query to get some info. The event is sent and the
response comes later.
The react component changes the state, changing the output
The HMTL generated is ready and with the latest state changes.
What's happening here is that ReactDOM.renderToStaticMarkup(), as expected, doesn't wait for the component to finish the state changes (in this case, wait for the data and re-render).
To clarify:
There is no client here, it's not the typical SSR. I just need the full HTML on the server so I can do other actions later. The problme here is how to wait for those async calls and use the same component that I use on the client (it has states and some setStates).
I'm assumming then only way is to have stateless components that just receive the props (doing all the socket handling previously and passing the data to each component). Right now the components are plain React, no Redux involved (that's why they are not stateless).
Any other idea/approach?
Thanks!
If there's no client involved, then whatever happens, you'll need to wait for all the data to load before rendering.
If possible, I'd suggest moving all async work outside of your component tree. Then passing it into the root component to distribute to the children (via props) when you call render.
createAsyncState(function(state) {
// generate the markup for this component tree
const markup = ReactDOMServer.renderToString(
<Component state={state} />
);
// serve the markup to the client
res.end(markup);
});
If you want to use the same component on the client too, then all you need to do is make sure your createAsyncState equivalent also works in a browser.
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.