In the example component tree, Component C and E both require the same data. This data is fetched from an API call which is triggered via dispatching an action.
My question is, where should this dispatching of the fetch action occur?
It could occur in the componentWillMount() (via mapDispatchToProps()) method of the first mutual parent component, Component B in this case. The problem with this is that component B now has an intimate knowledge of the data it's children require and I can no longer re-use Component C or E without some higher-order component.
Alternatively it could occur in componentWillMount() (again, via mapDispatchToProps()) of both Component C and E, potentially using a debounce on the action creation (helper libs available). My concern with this is that now Component C and E have some knowledge that they exist on the same page as each other otherwise they wouldn't debounce. Worse still, if Component C appears on another page without Component E then it is unnecessarily debouncing an action.
Does anybody know of a cleaner pattern for fetching data to be used by multiple child components?
One approach would be to have both components call the same thunk action creator, which should check to see if the request actually needs to be made. It might look something like this:
function loadSomeDataIfNeeded() {
return (dispatch, getState) => {
const state = getState();
if(!dataAlreadyLoaded(state) && !currentlyFetchingData(state)) {
dispatch(startFetchingData())
fetchData()
.then(response => {
dispatch(dataFetched(response));
});
}
}
}
So, track a "are we fetching right now?" value in your state, and also have logic to figure out if the data has already been loaded. That way, you can call the thunk multiple times and only actually have it fetch once.
Related
For the official Redux async app, when a user wants to see Reddit topic of "reactjs", "frontend" or "reduxjs", the dispatch merely dispatches an action object:
{
type: SELECT_SUBREDDIT,
subreddit: 'frontend'
}
and then let the componentDidUpdate(prevProps) to handles the "side effect" of fetching the data.
I would have done it by not dispatching the action, but dispatching a function:
dispatch(
dispatch => {
fetch(`https://www.reddit.com/r/${topic}.json`)
.then(response => response.json())
.then(data => {
dispatch({
type: "GOT_DATA",
data: data
})
});
}
)
I thought my approach is more straight-forward, and if using componentDidUpdate() to do the side effect, won't the component be rendered three time? First for the change of topic, the second time for dispatch of a fetching state (and with the code to fetch the data), and the third time when data comes back.
This approach actually is quite interesting: it is "just to change the state of the app and I won't do anything" to begin with, and then in the class component's lifecycle code (or function component's useEffect()), it is, "when the props that come in (from state), says something needs to be done, I will do it, and change more state if needed."
Also, the first way of doing things, if we look at the source code, has so many states and conditions. This is just a simple example of fetching some Reddit topics. What if there are 7 states and conditions, then the code will be so full of conditions handling of this state is true, and that state is not, and the third state is true, etc? It can be 2⁷ combinations and the code is so tightly coupled -- if the manager asks some behavior to be changed, it is like the programmer has to re-wire everything and check for all conditions, all actions, all dispatch, etc. It is like a spaghetti of states and conditions.
I thought the original intention of having this MVC or component way of doing things is so that we have less imperative code, but now we actually may end up with more code (about 275 lines of code), and they imperatively dispatch actions, and reduce, and handle all conditions, and it is even more complicated than if we do it imperatively.
Sure, it shows the "Loading..." status, and can cache the data, and can allow refresh... but even if this is done by imperative code, it may be actually shorter code, and it is more direct and can be more easily understood.
I'd hope to know what methods there can be: do we have to, or in best practice, follow the way in the original example code, or can we do it the second method as above -- is it actually not a good practice to dispatch a function that does async or "side effect" processing?
Both your approach and theirs involve dispatching a function, to be handled by redux-thunk. The part that makes this a bit more interesting is that there are two things that need to be done:
Immediately update which subreddit is marked as selected (this value is used on app.js line 42)
Download data for that subreddit
There's a few ways this could be done. Option 1: dispatch an action to update the selected subredit, then separately dispatch an action to do the fetch. Something like:
handleChange = (nextSubreddit) => {
this.props.dispatch(selectSubreddit(nextSubreddit));
this.props.dispatch(fetchPostsIfNeeded(nextSubreddit));
}
This works, but it has a vulnerability. What if i dispatch one, but forget to dispatch the other? Then i'll have the app in an inconsistent state. Perhaps not a big problem if this is the only place in the code where we're doing this, but redux's benefit comes in complicated applications where multiple parts of the application may be interacting with the state, and they need to not mess eachother up. So for a more complicated app i wouldn't want to leave it up to the discipline of the developer to know that dispatching one should always be followed by dispatching another.
Option 2: Put the dispatching of the select action inside the thunk
export const fetchPostsIfNeeded = subreddit => (dispatch, getState) => {
dispatch(selectSubreddit(subreddit);
if (shouldFetchPosts(getState(), subreddit)) {
return dispatch(fetchPosts(subreddit))
}
}
// ... elsewhere:
handleChange = (nextSubreddit) => {
this.props.dispatch(fetchPostsIfNeeded(nextSubreddit));
}
This approach is an improvement in the sense that now there's a single point of entry, which will make sure both things happen. But it's still actually possible to dispatch selectSubreddit on its own. We've made a way to dispatch both at once, but the old way still exists.
Option 3: Use a change in state as the indicator to fetch data.
This is the one in their code. If the state is changed for any reason, the component can see that change and make the appropriate action. This further improves upon option two in terms of making sure that changing the state can't be done in isolation.
One downside of this is that the component needs to be mounted in order to kick off the fetch. That's not often a problem, since if it isn't mounted why would you need the data anyway? But it could be an issue if there are multiple components that want to consume the data, since you may be forced to duplicate the fetching logic in all the components. If that's a concern i'd probably fall back to option 2.
One thing i havn't touched on in these possibilities is the number of renders. And that's because it's no different in any of them. Regardless, you're going to have an initial render, then a synchronous render to change the selected subreddit, and then some time later a render to show the resulting data.
I have a componentDidMount where I am calling an API and then have a this.setState({...this.state}).
Inside render, I have a button that calls delete function. Now when this delete is called I am re-rendering but I also want this API that is present in componentDidMount to be called because the delete functionality deletes some data in the same component, which should show updated value later.
The problem is, when I reload the page I get the data required but re-rendering does not show the required data.
Practically the API call code that you write in componentDidMount could be written as a separate function which you can then call, when you delete the data.
componentDidMount() {
this.fetchData();
}
// using arrow function to achieve correct context binding
fetchData = () => {
// your API call here
}
delete = () => {
// call fetchData here after delete
this.fetchData();
}
if your react version is going to be less than 16 , then you can us componentDidUpdate for that. This is been deprecated but still you can use UNSAFE_ flag before its name.
check this link from stackoverflow itself for a real world example: here
if you want to use a more robust way of handling that in react version 16 and above you could use getDerivedStateFromProps , for that you can check the corresponding way of handeling it from stackoverflow: here
I have two container(connected to a redux store) components(say A and B) on a page that both depend on one redux saga to complete for their data.
Currently what I'm doing to avoid two async calls(and to avoid one making the call and have the other check for the data to be there) is to move the async call up into a higher container component C that contains both A and B.
Then I'm converting A and B into normal components(not connected to the store) and passing them props from C.
Is there a pattern out there to achieve this better?
or any migration path to achieve this conversion?
or is it best just to do this change and spend the time moving the async call up to C and convert A and B to standard components?
Is there a pattern out there to achieve this better?
Best practice to prepare data into multiple components and handle their actions is having one root redux-provider, which provides all actions and store connection to descending components.
Then, redux-saga can intercept any actions and perform API call, since it combination of middleware and process manager. Usually components dispatch only _REQUEST-like action, after receiving which saga performs batch of async operations and then put _SUCCESS of _FAILURE-like action to reducers.
Of course, if you want to support optimistical update, _REQUEST-like action should be also supported in reducers.
If your components has explicit or implicit link between them, simple action handling via takeEvery or takeLatest can be not enough. But saga simply allows to perform creating multiple async processes, which can accumulate state in local closure before yield-based loop.
function * manyActionsSaga() {
let flowPromise = Promise.resolve();
while(true) {
const action = yield take(['ACTION_1', 'ACTION_2', 'ACTION_3']);
yield call(() => flowPromise)
flowPromise = flowPromise.then(() => {
// Similar event handling
})
}
}
I'm working on a component in React (with Redux) that has the requirements to:
Retreive data from an API endpoint.
Display that data, with one field being editable.
If the editable field is edited, it has to update the rest of the fields and send them to a different endpoint.
I'm having trouble grasping how exactly I should implement the editing using the Redux metodology.
Here is a simplified component:
class MyComponent extends Component {
constructor(...args) {
super(...args);
this.props.dispatch(getDataFromEndpoint());
}
editField(e) {
this.props.dispatch(editFieldAction(e.target.value));
}
render() {
return (
<div>
<input type="text" defaultValue={this.props.data.editableField} onChange={this.editField} />
{
this.props.data.uneditable.map(x => {
return (
<span>{x}</span>
);
})
}
</div>
);
}
}
const mapProps = state => {
const { data } = state.endpointData;
return { data };
}
export default connect(mapProps)(MyComponent);
Note that I've written this component as an example, it might have errors, but you should get the point. Whenever this.editField is called, it should update every value in this.props.data.uneditable.
What is the correct way to do this?
In a sense these questions all boil down to: How do I wire up my component(s) to read from (and write to) Redux state. This is essentially the purpose of redux-react's connect function (which you have in the bottom of your example). You use the first parameter, mapStateToProps, to be able to read from state. You use the second parameter (that you don't have yet) - mapDispatchToProps - to provide the means to write to it. Dispatching an action is how you update Redux state.
You can simply use this.props.dispatch, but it is more idiomatic to use mapDispatchToProps, which simply provides an object where each property is a function which calls dispatch with the appropriate action creator. You might accept as a parameter for example the data which you are looking to update your store with.
What ends up happening is in your component when you want to update the state (say from an input), you use the methods you wired up with mapDispatchToProps to dispatch an action with the relevant data (the updated input value), and then write a reducer which responds to the action and updates the relevant part of your store. Redux will then trigger a re-render in your component and it will pull the updated data from the store.
Async actions are handled a bit differently of course (which is what your API calls will be), because they typically have several changes to state being spread out over time. Usually something like:
Start the API request. (Dispatch an action to..) Set a flag somewhere in your state indicating the API request is 'in transit'.
Receive the response. (Dispatch again to..) Set a flag somewhere saying the request is finished, and if it was successful, store your response data somewhere. If it was an error, store the error (or just the fact there was an error).
I would recommend redux-thunk for this purpose, since at least for me it is the easiest to understand and work with out of all the various libraries and methods to handle async with redux. Instead of dispatching an object which contains the data describing the action, you instead dispatch a function, which means you can write any kind of logic you would like, allowing you to dispatch several times for example. This makes it easy to do the steps outlined above.. simply have a function which calls your API, and in the callback (or .then() method) you dispatch an action with the result.
I'm a bit confused about the component lifecycle in React. Consider a component that loads its initial state by dispatching the action, getTodo() and updating the state of the entire app
componentDidMount() {
var _this = this;
axios.get('/api/todo').then(function(response) {
_this.props.actions.getTodo(response.data);
}).catch(function(err) {
console.log(err);
});
}
What can I use in componentWillUnmount() to prevent the memory leak? If I choose to do this, how will it be possible to update the state when I come back to the page from another page with this method?
Also, is it a better to just store the props that I need for the page as the state and updating the state instead? My concern with this approach is that it just doesn't update the state of the entire app and other components that might need the same props will have to go through the same unnecessary process, which could be avoided by using the first approach.
You should avoid doing api call in a component. React offers an interface "nothing" more.
You should dispatch an action. In fact an action creator can be used (check redux-thunk or a redux-api-middleware). Your state app must be hold by redux store, not in your interface.
You probably have a "top" component will is mount only once, it can dispatch this action (action which is able to get initial state)
I hope it will help