I'm new to React and I'm trying to figure out the best way to request information from the server based on the URL. I'm using Redux and React Router v4.
Let's say I have a route /foo/:id, and a component Foo that will render something based on id. However Foo needs some server data related to id to do so. I believe the way to accomplish this would be to use mapDispatchToProps to create a function that takes id as input, does some async work, dispatches an action, and ultimately updates the redux state.
My question is: where is the most appropriate place to invoke the dispatch? In this scenario, there's no form submission or button click to kick things off. Originally I was thinking of including a check for the id data in render() and fetching if it was not populated, but this felt wrong due to the side effects.
You can do it in componentDidMount of the Foo component, similar to this example from the Redux GitHub project.
Your intuition is right that render is not a good place to do so. Most people do it in the componentDidMount lifecycle method of the component.
On a relevant note, you will also want to do fetching also in the componentWillReceiveProps method like what they did here. Reason being if your user navigated from foo/1/ to foo/2/, the component is already on the screen and will not be mounted again, hence componentDidMount will not be called again. The fetching for the second user will be done in the componentWillReceiveProps method.
i think the best way to do the dispatch inside the componentWillReceiveProps() which would help you fetch some data before the component renders
It seems your use case is well-captured by the react-refetch package which you can find here. It provides a higher-order component that allows you to specify dependencies at specific API endpoints and then resolves them when a new instance of your component is created.
Importantly it injects the data into your components props using a synchronous abstraction of a promise called a PromiseState. This will allow you to conditionally render your component depending on whether the data is say pending, fulfilled, rejected, etc.
This is not attached in any way to Redux, it skips that layer entirely, so do keep it in mind that the response is directly injected into the component and does not go through your redux store's state.
Related
I am learning how to use Reactjs and I read the following post :
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data
When componentWillMount is used, it is written that The above code is problematic for both server rendering (where the external data won’t be used) and the upcoming async rendering mode (where the request might be initiated multiple times).
I do not understand :
How the request might be initiated multiple times because
componentWillMount is used only one time.
Why componentDidMount solves this problems. For the server rendering,
the external data won't be used also in the first render call.
According to the React docs, changing your component’s state in componentWillMount will not trigger a re-render. This means that if you make your AJAX call and set the response in your component state, it will not re-render, which means you won’t see your data in the DOM. (Remember that the component was initially created with an initial state which most likely didn’t have the data from your external data/AJAX call response)
You could argue that wouldn’t it be better to do my AJAX call to pull external data before the component mounts for the first time?
It wont be better because you don’t know how much time it will take to do your AJAX call. Your AJAX request could take longer to get the data than the time it takes the component to mount and therefore your data does not show up on your DOM as the component has already rendered and there is no re-rendering happening. Your AJAX request could take longer for any reason - your user is on mobile and has slow internet, some issue with your server is making it slow to return responses, etc...
Best thing to do is make your AJAX call in componentDidMount and make your component handle empty data (probably display a loading spinner) until your AJAX request returns the data, sets it to the component state and triggers a re-render! :)
If you read further down they explain a bit more why componentWillMount is problematic.
The above code is problematic for both server rendering (where the
external data won’t be used) and the upcoming async rendering mode
(where the request might be initiated multiple times).
But these may be rendered moot as react is essentially deprecating that lifecycle function come react 17, and thus currently is renamed to UNSAFE_componentWillMount and not recommended for use, but instead use componentDidMount to make your async data fetches.
Why does componentDidMount fix this?
Because the server is pre-rendering the components/JSX, but you don't want the component to fetch its data until after it is actually mounted and running in a browser.
react component lifecycle docs
I am very confused recently.
When I write redux, if I have to call api ,I will put an action to update reducer, the component just render the data from props
But recently I see my coworker just call api in container component and then update the component state.
He says if your data do not need to share with other component, you can call api in component, so you don't have to write so many code in actions and reducers.
I thought this is very convenient. For example: If I have a feature : When user click the button, I have to send an email.
This feature do not need to update store by reducer, just have to alert "send success"
So I can write this code in container component:
async onClick() {
// 1. call api
const {error, response} = await sendMail({email: this.state.email});
if (response){
// 2. alert success
this.setState({
modal: {
show: true,
}
});
}
}
But I don't know if this match redux's principle.
Can I call api directly in component if the state do not need to share with other component??
You can call api from dispatched actions or from React components: it is your choice to make. There is no mandatory rules here and it depends on what you want to do with your components:
When to use React states:
It is better to have smart component handling their own state because it ease the integration in external projects. Having a component that uses Redux means a project needs to add the requires reducers to use the component.
If a component handles information not required by any other components, use React state. It is often the case for information related to UI state.
When using Redux reduces:
If you need to test your component, prefer Redux because you'll be able to connect "test actions" to your component when testing them.
If you need to share a bundle of data through components, prefer Redux to mutualise information.
This question has been treated by Gaeron on Redux github repository if you want to have a look. He explains:
Use React for ephemeral state that doesn't matter to the app globally and doesn't mutate in complex ways. For example, a toggle in some UI element, a form input state. Use Redux for state that matters globally or is mutated in complex ways. For example, cached users, or a post draft. Sometimes you'll want to move from Redux state to React state (when storing something in Redux gets awkward) or the other way around (when more components need to have access to some state that used to be local). The rule of thumb is: do whatever is less awkward.
I suggest you have a look at classux
We have a React / Redux application that usually fetches data via the "normal" way - a view calls a dispatcher, the dispatcher calls our server and dispatches an action with the result, which is then put into the state via a reducer.
Now, I'm creating a view that needs to call the server to get a custom URL based on a few properties. It's not global state at all - since it's just this view that's going to be using it.
It doesn't feel right to call the server from the view directly, but it doesn't feel right to put this data in the global state either.
What is the best practice in these kinds of situations?
You could bypass the "biased standard" here and create an action/lib file to do the fetching for you, and then let the result of that promise (or callback) be the input to the dispatch you will continue with.
From the question you ask, I assume you are going to do this for more than one component? If so, I think that the mentioned approach will suffice.
Further, if you are only doing this for one component, it could be enough to just put that logic in the component, or in a parent container (HoC).
I am converting a jsp multi-page app(mpa) into a React single-page app (spa). In the mpa, the back button worked and the form retained its state.
In my new React app, the back button works but the form does not retain its state.
Question: is there a trick to make my form retain its state after "backing" to it (in an spa).
Here are the two solutions I came up with:
Encode the entire form state into the url. Then update the browser history with an updated url every time the form changes. But, this seems like a huge pain in the butt.
Modify the structure of my app such that the form in question (a React Component) stays mounted (and just use the css visibility or display property to show and hide). But, in a large app, leaving every page mounted in the DOM seems like it might lead to performance problems.
By the way, I am using popstate and the browser history api to achieve SPA behavior (i.e. i have rolled my own router) as described here.
Hopefully someone can propose a solution that is better than my two solutions. Thanks.
I ended up using something similar to Wylie Кулик's answer with a few changes:
I didn't want to switch to Redux for just this one use-case. So I used the component state of my top-level component (i.e. a component higher up the tree).
I cached the form's state on the form component's componentWillUnmount and restored the cached state on componentDidMount.
I passed the cached state as a prop from the higher component to the child component.
It ended up being a very small amount of code and is working like a charm so far.
Use Redux to have a state store which transcends any particular component. Then in your component, as part of the form submission process, dispatch an action with payload of all of the form data. This should be cached on state and then when the component is remounted by your navigation structure, it should have access to this cache via Reduxsconnectfunctions mapStateToProps method. You can repopulate your form from that.
It's not clear from your question whether or not you are submitting the form in the traditional old way. I would use e.preventDefault in the handler instead, and have all the form data on the component's state, this can be sent to Redux's state store as described above, and Ajaxed off with superagent or similar. At the same time it can be cached.
Redux: http://redux.js.org
I am using react-router with the flux architecture (facebook's flux implementation).
Currently in my system I have route that says "chat/:topic".
When the user is entering this component, I am creating a subscription (using action creation, on componentWillMount) to a websocket server, and I am removing the subscription on componentWillUnmount.
When the user is moving to another route the whole workflow works alright - because react-router is unmounting my component.
When I transition inside my route (from "chat/games" to "chat/tv"), the component isn't mounted and I need to clear my state of the components.
I read about different actions that I can take and this on transition to dispatch an action "TRANSITION" and every relevant store will clear it's store.
In my opinion, this kind of action - is wrong, it couples my stores and my router.
How would you solve this problem? Is this an issue that I should raise to react-router and ask them to unmount inside my route?
I found the answer thanks to gaearon (https://github.com/gaearon/),
Use a store to keep the selected topic and ask the messages store for messages, in flux you shouldn't remove anything from your store, unless you need it for a performance reasons.
In my application, I must remove the messages (since they are large objects) and clear my subscriptions (to reduce the load on the server).
In order to achieve this there were three solutions that I found:
Use componentWillReceiveProps and check if the params are changed, if the params are changed - do whatever you need in order to clear the store - for example call ActionCreator and reset the state.
Send a dispose payload in transition (inside Router.run) which will tell all the stores to clear themselves.
last solution (which I used) making sure that my components are unmounted - Why? It is too error prone to clear the state on componentWillReceiveProps/dispose and it is clearer to just ensure the components are unmounted.
Details on how to achieve this:
https://github.com/rackt/react-router/issues/292
https://github.com/rackt/react-router/issues/496#issuecomment-64152941
I believe that compomentWillReceiveProps might solve your issue. This is what react router uses to pass stuff to you.
As far as I know, you need to use both componentWillReceiveProps AND componentDid/WillMount to catch the initial render also.
I'm anxiously awaiting the react-router 1.0 release (this weekend?) in hopes that there is a more elegant way to do this.