How to Differentiate input vs button state changes in react redux - reactjs

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});
});
}

Related

React not rendering updated UI on state change

I'm working on a mini project where a user shall be able to edit book info. The app presents the user with a filter to choose Fiction or Science subject category. Once chosen, he is shown a list of books where he can change the details of the book by clicking the edit button. On editing, he can choose to either save or cancel the form details. This entire workflow is on a single page! I'm using React, Redux, and Redux-form for this purpose where component I- Selection category (Fiction, Science), component II- list of books for that category and component III- form to edit book details. See the attached image to imagine the app.
Working of the app: Once the user chooses Fiction/Science, an action is dispatched to list available books. Once he clicks Edit button for a book, another action is dispatched to display the form pre-filled with the book's values (so that the user can edit it).
The Problem: Unfortunately, my Edit form component is not rendering desired values due to unavailability of props to this component; even though I can see the success of my redux action with correct state/props.
Anatomy of code:
SubjectChoice.jsx
const mapDispatchToProps = dispatch => ({
fetchBooks: favoriteSubject => dispatch(getBooksList(favoriteSubject)),
fetchSubjects: _ => dispatch(getSubjectsList())
});
So, this component correctly dispatches getSubjectsList and I can use fetchSubjects as props on componentDidMount. Similarly, it dispatches another action getBooksList(favoriteSubject) according to the chosen subject and fetchBooks can be used as props on handleChange callback (These actions make API request; in fact, all does)
BookList.jsx
const mapDispatchToProps = dispatch => ({
fetchBookDetails: bookId => dispatch(getBookDetails(bookId))
});
So, this component correctly dispatches getBookDetails action on handleEdit which is Edit button callback.
BookEdit.jsx
const mapStateToProps = state => ({
book: state.book.bookDetails,
...
}...
render() {
const { bookDetails, loading, error } = this.props.book;
...
}
Now, this component keeps on waiting to re-render but it never receives the props. I see the actions, reducers dispatching correctly in the console (from BooksList.jsx component) but never see them reflecting on screen.
How SO answers are not helping me get out of this situation: I saw answers where it's specified that redux does a shallow comparison and if it doesn't find a subtle difference in the previous and next state then it skips rendering the app. However, as mentioned in the third step I see the action and reducer with previous and next state correctly.
If BookEdit isn't connected to the store or the props aren't being passed from a parent component you will not have access to them. Either connect your BookEdit...
import { connect } from 'react-redux;
...
const mapStateToProps = ({ book }) => {
const { bookDetails, loading, error } = book;
return { bookDetails, loading, error };
}
export default connect(mapStateToProps)(BookEdit);
Or pass them in from a parent connected component that is bringing in props from your store.
Every time Redux actions are dispatched, mapStateToProps will be executed. The connect function from react-redux will only re-render your component when the props from mapStateToProps are not equal to the previous props.

Clear form after redux action succeeds

I'm storing my form inputs in React component state. When I submit the form, I trigger a Redux action. And when this action succeeds, I want to update the state again - to clear the form. But how to do it?
I mean, I can easily store form state in Redux too and everything will be resolved, but I'd prefer to store component specific things in component state.
You should be using something like redux-thunk to delay the dispatching until the API call succeeds:
const postForm = data => dispatch => fetch(...).then((...) => dispatch(...))
Since fetch returns a Promise, you can then wait until it's resolved (api call succeeded) before performing the form clearing in your component:
props.postForm(...)
.then(() => this.setState(<clear the form state>))
.catch(<do something to warn the user api call failed?>)
What does that action update on the state exactly?
One way would be to add an extra case in your componentWillReceiveProps that handle that update of the form. If the action let say updates the list, you could have something like the following on your componentWillReceiveProps method inside you component:
componentWillReceiveProps(nextProps) {
if (nextProps.list !== this.props.list) {
this.setState({
formFields: this.getNewClearFormFields()
})
}
}
Where getNewClearFormFields is a function that returns your new form fields
If you want to update the state after redux action succeeds, then I would suggest go ahead and put it in componentWillReceiveProps by comparing prevState and nextState
use mapStateToProps() to map redux state to component
and then update the component state like below
componentWillReceiveProps(nextProps) {
this.setState({
...
});
}

React-redux: allowing users to give feedback on a component

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
});
});
}

Multiple dispatch calls from component react/redux

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

Preventing component updates from multiple props in react

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));
});
};

Resources