I am looking for the best practices how to handle correctly delete action.
For example I have array of photos in my reducer.
And one of features is ability to select them to delete.
I have decided to store items that selected in react state. I store array of IDs.
So after delete action I have to remove photos from redux store and clear selected array in react state.
My suggestion was to subscribe on promise and do something like below:
this.props.deletePhotos(selected)
.then(() => {
this.setState({ selected: [] })
})
.catch((err) => {
console.log(err)
})
but guys have said that it is a bad practice. Is it true? How to change it in right way?
The main principle you need to adhere to is that your state should always be valid. By using a promise, you are deleting the photos, rendering an invalid state, and then updating the react state. Even if such a render does is insignificant, it's not great practice.
You could just not set the state in the promise:
const selected = this.state.selected
this.props.deletePhotos(selected)
.catch((err) => {
console.log(err)
})
this.setState({selected: []})
Alternatively, and if you wanted to do something more complicated than clearing, you could use getDerivedStateFromProps and modify the state if relevant props were changed which may have come from redux.
Another alternative is that if you're done with the input, you would simply just not render the component in the next render and the state would be cleared automatically since it would be unmounted.
Related
I've got a simple login dialog that uses redux (just the dialog is shown below for reference). Each time the user types a character into either of the input fields a state change is fired through redux and when the button is pressed a state change also fires.
I plan on changing the state directly when the button is pressed to be something like "LoginPending", and when the REST call returns, the state will change to either "LoggedIn" or "LoginFailed".
My current plan is to add an if statement to the MapStateToProps method that check the old LoginPending status to see if that changed, and if it did, then dispatch the proper action (In reality I will execute a toast notify message).
My question is, "Is this the proper way to check for something like "logged in""? It seems awkward to do it in MapStateToProps, but since there are so many state changes happening (because of the onchange in the input elements), I can't think of a better place to do it.
class App extends Component {
....
render() {
return (
<div className="App">
<input onChange={this.handleChange('username')}/><br/>
<input onChange={this.handleChange('password')}/><br/><br/>
<button onClick={this.handleClick}>Login</button>
</div>
);
}
}
There are several ways to do it. One of the most popular way is to use redux-thunk. docs and example / official recommendation
Then all the logic will reside on the action creator:
export const exampleAction = (username, password) => (dispatch, getState) => {
dispatch({type: "exampleLoading"}); // the store sets a state variable so the view can render something while this "loading" state is true
doApiCall(username,password).then(response => {
// here the feedback message is added on the store, so my feedback manager can render the new feedback message
dispatch({type: "addFeedback", payload:{message:"success!", type:"success"}});
// here the store cleans up the "loading" variable and uses the api response if needed
dispatch({type: "exampleLoaded", payload: response});
});
}
I am returning a list of forecasts from my back end, which is rendered in a dashboard (I'm making the API call in componentWillMount()). Each forecast is rendered as a presentational component and has a like and dislike button. Once the user clicks on the button, a an Axios action is called which posts an event to the back end REST API saving this feedback.
As I see it, I don't need to deal with Redux's state here, unless I want to disable the buttons or otherwise change them when clicked. All I want to do here is to 1) trigger a CSS animation on the button (some sort of bounce) to let the user know he clicked it, and if the Axios action returns an error, I need to display it in an error section.
The issue is that I have not been able to figure out how to do this without going through the rigamarole of dispatching the Axios result to state, putting it through a reducer, then mapping state to props in the component, etc. This seems overkill for something this transitory, no?
Yes it does. As #ReiDien stated
you can just use axios directly without using any reducers.
As for the animation and the error message you can use your component's state to store these. You may need a container component to handle the state and its changes. Your handler ends up as something like:
handleLike = () => {
this.setState({
liked: true,
});
axios.post(...)
.catch((error) => {
this.setState({
error: error,
liked: false
});
});
}
I want to set form loading state (spinner icon, disable input) when the user submits the form, and clear that state when the action completes or fails. Currently I am storing this state in my store, which involves creating an action creator and reducer, which is annoying for a few reasons:
It's a lot more work than simply calling this.setState
It's more difficult to reason about
I don't really want to store local component state in my store
Essentially I want to do this within my form component:
handleFormSubmitted() {
this.setState({ isSaving: true })
this.props.dispatchSaveForm({ formData: this.props.formData })
.finally(() => this.setState({ isSaving: false }))
}
You can't do that with redux-saga. What you are trying to do there goes against the basic principles of redux-saga.
redux-saga aims to be reactive by treating actions like events that describe what's happening in your app... So that other sagas (usually "watchers" using take) or the rootReducer can subscribe to those actions/events and do what they need to do...
Because of that redux-saga -unlike redux-thunk and redux-promise-
doesn't change the behaviour of the dispatch method... So, with redux saga when you dispatch, you dispatch, and the reducers and the sagas are subscribed to the dispatched actions. But the dispatch method won't return a promise like it happens when you use other middlewares/store-enhancers.
So, the only way that redux-saga has to let the rest of the app know that the request of your form has finished is by dispatching an action (using the put effect) whenever that request finishes or errors, right? So, how could you possibly know directly from inside the component if a specific action has been dispatched?
Unless you make your own middleware (or you use a separate one) with a connector component: there is no way for you to subscribe to concrete actions inside a component.
Sure, you could access the context directly in order to get a hold of your redux store, and then you could use the redux subscribe method directly, but the listener function won't tell you what's the action that got dispatched. It will just get invoked when an action (any action) gets dispatched... maybe you could check if some property of the state has changed, but that's insane. So, unless you want to go down that route, which is crazy: you can't do that using redux-saga.
If you wanted to do something like that (which IMHO is not a very good idea) you would have to do it without using redux-saga. A possible way to do it could be something along the lines of:
handleFormSubmitted() {
this.setState({ isSaving: true })
yourFetchCall({ formData: this.props.formData })
.then(payload => this.props.dispatchFormSaved(payload))
.catch(error => this.props.dispatchSavingFormErrored(error))
.finally(() => this.setState({ isSaving: false }))
}
I don't really know why I can't get this to work. All the evidence talks against it...This is the situation:
I have a grid of data and a search panel. When the search panel is changed the searchparams are updated and used for updating the data grid.
The thing which triggers the chain is when the user changes the search panel. In my component i handle search panel changes with this:
getPhotos(key, value) {
const change = [{ key: key, value: value},{ key: 'page', value: 1}]
this.props.dispatch(updateSearchParams(change))
console.log('payload.searchParams', this.props.searchParams);
this.props.dispatch(
getPhotos(
{ context:this.props.params.context,
searchParams: this.props.searchParams }
)
);
}
Thus two dispatch calls to action creators form the component. The problem is that the searchparams are not updated in time for the getPhotos call, so the grid is not updated accordingly.
I thought that dispatch calls were synchronous - thus one after the other. I guess that it is the round trip from the component, to the action creator, to the store and reducer which is "screwing" it up.
The first call does not involve any asynchronous calls.
What is the "right" way of doing this? Please be specific about what goes in the component, the action creator and the reducer.
Thanks
dispatch is synchronous (unless you are using some middleware like redux-thunk). But after this.props.dispatch(updateSearchParams(change))
, your component needs to be updated (a re-render) or the this.props.searchParams is still the old one.
You can write this.props.dispatch(getPhotos(...)) in componentWillReceiveProps(nextProps), so you can access the new props (nextProps)
If you are using redux-thunk and two actions updateSearchParams and getPhotos are always bind together, you can create another aggregated action creator for them.
const updateSearchParams = change => dispatch => {
// return a promise here
// or use callback style etc. whatever you prefered
}
const updateSearchParamsAndGetPhotos = (change, context) => dispatch => {
dispatch(updateSearchParams(change))
.then(res => {
dispatch(getPhotos({
context,
searchParams: res.data.searchParams
}))
})
}
So now after dispatching a single action, your component should receive the new photos.
I had it wrong from the beginning.
The searchparams should not go into the store. I can handle the in the component alone - in the state of the component.
This the simplifies and eliminates the problem I described above.
Of cause there could be a situation where the searchparams needed to be available for other components. In that case I would go for #CodinCat answer above with the thunk. It works, i managed to implement it before my realisation.
Thanks
I have a react/redux app which has a recharts chart which animates when data is changed.
I'm using Redux and most of my actions only change a single state property which results in a single props pass. However, some of my actions are now using thunks for some async actions and calling other actions.
For example, I might have an action getChartData which would be called when the user selects an axis.
export let getChartData = axis => dispatch => {
// trimmed for brevity
fetchJSON(url).then(data => {
dispatch(dataRetrievalSuccess(data));
dispatch(updateSelectedAxis(axis));
}).catch(error => {
dispatch(dataRetrievalError(error));
});
};
In this example the updateSelectedAxis value will change a local state property responsible for displaying the currently selected axis and the dataRetrievalSuccess function would be responsible for passing props.data to the chart.
The problem I'm trying to solve is to prevent the chart from updating when the selectedAxis props of the component change but the data hasn't.
I thought I would be able to use something like componentWillRecieveProps but the issue I have here with my above thunk example is that I get one call to componentWillRecieveProps when I call dataRetrievalSuccess which has the same data in both this.props.data and nextProps.data so I can prevent the update. However when I subsequently call updateSelectedAxis I don't have the data as part of the props as it's already changed, so I can't perform logic operations based on the two values.
I thought this was possibly an ordering issue, but even if I pack this into a single action I still get multiple setting of props.
Would I solve this issue by packaging up the data and the change of axis into a single object?
I'm not quite sure the best way to go about this architecturally and would welcome any suggestions.
EDIT:
Just to expand a little, I am dispatching two actions, both which change their own bit of state which causes two renders.
I've tried writing something like this:
shouldComponentUpdate(nextProps, nextState) {
if(this.dataHasChanged(nextProps)) {
return true;
}
return false;
}
Which almost works, but each time the data the chart shows is one render behind where it needs to be.
You can access the current State of store under action creator using thunk (as thunk inject the state for you.) , then compare ajax response data with previous state data to dispatch new action.
export let getChartData = axis => (getState, dispatch) => {
// trimmed for brevity
fetchJSON(url).then(data => {
if(getState().data !== data){
dispatch(dataRetrievalSuccess(data));
dispatch(updateSelectedAxis(axis));
}
}).catch(error => {
dispatch(dataRetrievalError(error));
});
};