redux state in componentWillRecieveProps issue with forward and back button - reactjs

I have problem showing an alert dialog using redux. I want to show a success message after user has edited says a form. I set a flag through action > reducer > store.
Then I do
componentWillReceiveProps(nextProps) {
if(nextProps.data.updated_form) {
alert('form has been updated!');
}
}
There's problem in this approach. User updated the form, then he click somewhere to go to other route, then he click back to my form, that alert will trigger too.

The problem with your existing solution is that updated_form variable of the store doesn't change over time. So getting back to the page retriggers the alert. One simple solution would be to create another action creator to reset those changes on componentWillUnmount()`.
sample store reducer could like this
case 'RESET_CHANGES':
return {...state,updated_form:null}
And the react component will have similar code like this
componentWillUnmount() {
this.props.resetChanges() // assuming you have created action creator
}

Related

Redirecting after successful action

I am currently working on a authentication project within React Native using the react-navigation package. So far so good, I have 3 Navigators setup, one for loading, one for auth and one for the application.
I have also got the navigation setup with redux so I can access it from any component via a prop, which is exactly what I wanted to do.
However, I have a question. I have done some research and can't seem to figure out the best way to do this.
I am dispatching an action from a press of a button attemptLogin() the attempt login then does what it says on the tin, it attempt to login.
After a successful login, I store the access_token in SecureStore using expo.
Now, upon the successful login I need to navigate away from the current stack onto the new one.
What would be the best way to do this, can you return a promise from a redux action? If so would the best way to be inside of the component and then inside of the component do something like
this.props.login(username, password).then(() => { this.props.navigation... });
Or would you do it inside of the action?
A recommended way as below:
componentDidUpdate(prevProps) {
if(this.props.loggedInSuccessfully && !prevProps.loggedInSuccessfully) {
this.props.navigation.navigate....
}
}
onLoginButtonPress = () => {
const { username, password } = this.state;
this.props.login(username, password);
}
Upon successful logged in, update a state loggedInSuccessfully in your reducer and implement logic in componentDidUpdate. This way is the clearest for whoever gonna maintaining your code, in my humble opinion
you can also navigate from redux action or everywhere you want.
read the official docs about navigation service.

How to clear error message coming from redux actions

I have an API, which may return an error. In the simplistic form, the component reads as below. The question is, when an error occurs, what is the steps to dismiss/clear the error message?
class LoginError extends Component {
render() {
return (
{
this.props.error != null ?
<h2>this.props.error</h2> :
undefined
}
)
}
}
const mapStateToProps = (state) => {
return {
error: state.error
};
}
export default connect(mapStateToProps, null)(LoginError);
There is no straight forward way to do this, you basically have two options: set the error to undefined in the reducer when another action fires, or provide a close button on the error itself using a reusable error component, that dispatches an action that will set the error to undefined in the reducer.
Personally i've always used the first method, lets take the submit of a form as an example. When the user submits the form you fire form-submit/request and maybe show a loading message. If the form is sent correctly you fire form-submit and if an error happens you fire form-submit/error, setting the error in the reducer. Then in form-submit/request you clear the error, so the user gets feedback if an error happens but if the form is submitted again the error is cleared. If you don't want to do anything when the form is submitted, which is odd, you can clear the error when form-submit is fired. The downside of this approach is that if for example you want to clear the error when any field of the form is changed, you'll have to add another action and make the error undefined for that action as well.
Now if you put a close button in the error component you can reuse the error React component, but you'll have to have a action/dismiss-error action for every API call and set the error to undefined on the reducer for each one of them, this can get very tedious quickly.
The best approach for me is to use the first method, but choosing carefully how much errors you display for each page or section. This way each page can have its error section that will be displayed for any API call that is associated with the page and you only need an error action for each page, not for every API call.
Quote from docs:
A store holds the whole state tree of your application. The only way
to change the state inside it is to dispatch an action on it.
That's it. If you keep something in redux state then to change it's value you have to dispatch an action.

Persist user activity in state after reload

I have created an application where user can search information related to movies using react and redux. While searching user can apply some filter(For eg. time duration). I want this filter to be active till user unselect them even after user reloads the page.
Problem:
Current scenario user apply filter application will dispatch an event and store the filter information in Redux state.
But as soon as user refresh the page information about the filter get lost.
Solution Tried:
I have tried one solution using session storage and local storage, but I am not convinced with the solution.
It would be great if somebody can show better way of solving this problem if available.
For some simple states, like current value of filter, it would be better to use location.
For example, you have the following page: http://example.com/users.
Then you can preserve filter like this: http://example.com/users?group=admin.
The benefit of this approach is simple: you explicitly say to user the actual state of the page, he can copy that, save bookmark, or send to somebody else.
To achieve this in React code, you can do the following (I assume that you have React-router in your app):
class UsersPage extends React.Component {
// should be called somewhere in onClick
filterUserGroup(groupName) {
this.props.router.push({
pathname: this.props.location.pathname,
query: {
group: groupName
}
});
}
componentWillReceiveProps(nextProps) {
if(nextProps.location !== this.props.location) {
//filter was changed, you can apply new filter value
this.setState({
selectedGroup: nextProps.location.query.group
});
}
}
}

Getting an error on using setRouteLeaveHook (withRouter)

Currently I'm building a Web App using ReactJS. The app has a registration form.
Now consider, user has started with the registration process. But before submitting the form user leaves this registration page. At this point, say form contains unsaved data and I would like to display a confirmation message saying that Save Changes you have made before leaving this screen.
Below is my code to achieve this
componentDidMount () {
this.props.router.setRouteLeaveHook('/enterprise/enterprise-area/enterprise-details', this.routerWillLeave);
}
routerWillLeave(nextLocation) {
// return false to prevent a transition w/o prompting the user,
// or return a string to allow the user to decide:
if (true) {
return 'Your work is not saved! Are you sure you want to leave?';
}
}
export default withRouter(connect(
mapStateToProps,{
initializeVendorDetails
})(VendorRegistration));
I get the error shown below:
Uncaught TypeError: Cannot assign to read only property '__id__' of /enterprise/enterprise-area/enterprise-details
I went through official documentation and github issues but found nothing. Thanks in anticipation.
#NobuhitoKurose Thanks for your reply. Finally I manage to figure out the problem here.
Yes, My component was not directly connected to route.
I went through withRouter doc where I found that I actually need to provide a route object(this.props.route) as a first parameter instead of route as a string(as I mentioned in above code).
Since my component is not directly connected to route I was getting this.props.route as undefined.
I checked parent component (which is connected to route) and this component has its route prop. So I just pass this route prop from parent component to this current component (where I'm using withRouter) and everything has worked well.
Below is my an updated code
In parent component (which is connected to route)
<VendorRegistration route={this.props.route}/>
Component where I'm using withRouter
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
}
routerWillLeave(nextLocation) {
// return false to prevent a transition w/o prompting the user,
// or return a string to allow the user to decide:
// FIXME: update condition as per requirement
if (true) {
return 'You have unsaved information, are you sure you want to leave this page?';
}
}
export default withRouter(connect(
mapStateToProps,{
initializeVendorDetails
})(VendorRegistration));

How to update a child component state after an ajax call

To learn react + flux, I am building a simple compose box where the paragraph is broken down into multiple tweets and then by clicking a button you can tweet the series of tweets. The high level app has the following components:
<div>
<ComposeBox tweetParaText={this.state.tweetParaText} ref='composeBox' />
<DisplayTweets tweetCollection={this.state.tweetCollection} ref='displayTweets' />
<TweetButton signedInSignature={this.state.signedInSignature} ref='tweetButton' />
</div>
Once you are done composing the text, you click on the TweetButton. The TweetButton has 4 UI states:
Ready (Show the Tweet Button)
Tweeting (in which case I show a
loading gif as well as change the text from "Tweet" to "Tweeting..")
Tweeted Succesfully (In which case I want to revert text of the button to "Tweet", hide the ajax laoder gif and show a message "Tweets sent successfully!" for a duration of 7 seconds.)
Error (In which case I want to revert text of the button to "Tweet", hide the ajax loader gif and show an error message)
These states are unique to the tweet button and are different from the application state which I am storing in the store.
The onclick of the TweetButton component is as follows:
_onClick:function(){
this.setState({buttonState:tweeting});
ActionCreator.tweet(this.props.tweetCollection,this.props.signedInSignature);
}
On changing the state here, the UI changes for this state happen, as that is how the component has been written. However, I am a bit stumped after this.
The flow of data being unidirectional, the action upon completion dispatches an event to the store which updates its state and thus the state of the ComposeBox component. But how and where do I set the state of the TweetButton component? Should I write a different store for this? Or is there a better way of achieving this?
I use the same store for the status/state of the call.
Lets say the result of your call will be stored in _Tweets = {}; inside the TweetStore object.
I register the results uniquely (not mandatory I guess) inside the store like this
_Tweets["someresultid/tag"] = {
data: result.data,
status: result.status
}
TweetStore is listening for 2 events : case : "Loading" and case : "ReceiveResults/answer/wtv"
When the user initiate the call you'll dispatch "loading" at the same time than your call. Then when it receive the answer you dispatch "answer".
Your view is listening for any change of your store, when a change occurs the callback will check the status and rerender accordingly (lording,error or result).
I hope it helps

Resources