How to prevent state change in React after page refresh/view change? - reactjs

My onClick-event handler adds/removes an id to a database field and changes the button color according to the toggle state true/false.
While the data update works correctly, the color state resets upon a page refresh/view change.
I guess the state needs to be passed on (the relationship is child to parent in this case) with a callback function but I am not sure.
I thought it would be necessary to 'preserve' the current state in LocalStorage but this did not solve the issue. While the LocalStorage values 'true' and 'false' remained their state (as displayed in the console), the color of the button still reset when refreshing the page.
I attached the code sections that may be of importance to assess the issue:
// initialization of toggle state
let toggleClick = false;
...
this.state = {
fav: props.favorite
};
this.toggleClass = this.toggleClass.bind(this);
}
...
componentDidUpdate(prevProps) {
if (this.props.favorite !== prevProps.favorite) {
this.setState({
fav: this.props.favorite
});
}
}
toggleClass() {
toggleClick = true;
if (!this.state.fav) {
this.addId(this.props.movie._id);
} else {
this.removeId();
}
}
...
<span
onClick={() => this.toggleClass()}
className={this.state.fav ? "favme active" : "favme"}
>★</span>

I have encountered your same problem for my React Apps. For this, I always use componentDidMount because it is called just after the render. But, if you call the setState function, it will automatically call the render, so your changes will appear. If page gets refreshed, state will be reset to default. So, the best way to achieve this is for example:
componentDidMount() {
const getDbAnswer = //fetch a signal from the db
if (getDBAnswer === true) {
this.setState({ fav: true })
else
this.setState({ fav: false })
}
You could even set a variable with localStorage. Remember, localStorage accepts only string values, no boolean.
componentDidMount() {
const getDbAnswer = //fetch a signal from the db
if (getDBAnswer === true)
localStorage.setItem("fav", "true");
else
localStorage.setItem("fav", "false");
}
And then you could just use it like this:
<div className={localStorage.getItem("fav") === "true" ? "favme active" : "favme"}>

I had a similar issue when refreshing the view. It was because the component render before the rehydration was complete. I solved my problem by using Redux Persist. We can delay the render by using Redux Persist, I put the persistStore in my main component:
componentWillMount() {
persistStore(store, {}, () => {
this.setState({rehydrated: true})
})
}
This will ensure that the component re-render when the store is rehydrated.
https://github.com/rt2zz/redux-persist

Related

Why is my if statement always giving the same result in React?

I have a button that changes the state of sort to either true or false. This is being passed to the App component from a child using callbacks. The first console log in the refinedApiRequest gives the value of true and then false and then true and so on as the button is click. However, the if statement keeps resulting in the else result regardless of the state of sort (true or false). What is causing this?
I want to assign a variable a value depending on the state of sort so that I can feed this value into the params of a async api get request. Thanks in advance.
class App extends React.Component {
state = { pictures: '', sort: '' };
onSortSelect = sort => {
this.setState({ sort: sort }, () => this.refinedApiRequest(sort));
}
async refinedApiRequest(sort) {
console.log(sort);
if(sort == 'true') {
console.log('its true');
} else {
console.log('its false');
}
const res = await axios.get('url', {
params: {
order: {a variable}
}
});
this.setState({ pictures: res.data });
console.log(this.state.pictures);
}
While the optional callback parameter to this.setState is guaranteed to only be executed when the component is re-rendered, in your case it still closes over the sort value of the previous render (ref: How do JavaScript closures work?).
I'd suggest following reacts advice:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
from: https://reactjs.org/docs/react-component.html#setstate
(emphasis by me)

React setState in componentDidMount not working

Any idea why following setState for popUpBurned is not working?
componentDidMount() {
const { user } = this.props
const { popUpBurned } = this.state
const visits = user.visitsCounter
if (visits === 6 && !popUpBurned) {
this.setState({ popUpBurned: true })
this._popupFeedbackVisits.show()
}
}
I need my _popupFeedbackVisits to trigger only once when the visit-number is 6, which works just fine. But when in the same visit, the user navigates to another page and comes back to dashboard, the popup triggers again (as the visits are still 6).
How can I make the popup to trigger once and only once? My though was to add that boolean, but it does not seems to work inside componentDidMount. What am I missing?
Thanks!
My suggestion would be to store the popUpBurned somewhere else and pass it in as a prop to this component. It seems like from your post user is a global object and this should only ever happen once. Perhaps storing this on the user would be appropriate?
componentDidMount() {
const { user } = this.props
const visits = user.visitsCounter
const popUpBurned = user.popUpBurned
if (visits === 6 && !popUpBurned) {
this.setState({ popUpBurned: true })
this._popupFeedbackVisits.show()
}
}
You can try changing the visit number in if statement, so that it does not trigger the related function again. Because in any refresh or coming back to the page will make componentDidMount work, and then it continues to popup.
If popUpBurned is not used in render, it probably doesn't need to be in state and you could make it a class property:
class ... extends React.Component {
constructor(props) {
...
this.popUpBurned = false
}
...
componentDidMount() {
const { user } = this.props
const visits = user.visitsCounter
if (visits === 6 && !this.popUpBurned) {
this.popUpBurned = true
this._popupFeedbackVisits.show()
}
}
}
This work-around using sessionStorage solved the issue:
if (user.visitsCounter === 6 && !sessionStorage.getItem('popUpBurned')) {
sessionStorage.setItem('popUpBurned', true)
this._popupFeedbackVisits.show()
}

React Redux, load data from the API on first page load ONLY. Render component once data is loaded

I am DEFINITELY doing something wrong at the core of my app.
I think its where I am making a request to my API. I'm also not happy with the firstLoad flagging to tell the app to call the API.
I am using React/Redux - building from this boiler plate https://www.npmjs.com/package/create-react-app-redux.
It uses mapStateToProps, mapDispatchToProps and connect to glue everything together.
So I have a component called "Shop"
// only true when user first visits url OR presses F5 to refresh.
// Its conveniently false when page rerenders
var firstLoad = true;
const Shop = ({ stateProps, dispatchProps }) => {
if(firstLoad) {
firstLoad = false;
// dispatchProps contains a method called changeState that will update state.shops to the result from the API. It will also set state.loading to true while calling and false when finished.
// Note, I am not modifying state directly. The dispatchProps wraps the action that does so
server.GetShops({url: "api/shops", updateField:"shops", loading:true, token:"abc-xyz"}, dispatchProps);
}
if(stateProps.loading) {
return (
<div>loading...</div>
);
}
var shopUrl = window.location.href; // extract the part of the url after the domain
var selectedShop = stateProps.shops.find(s => {
return s.url === shopUrl;
});
if(!selectedShop) {
window.location.href = "/";
}
return (
<div>
{selectedShop.welcomeMessage}
</div>
);
}
The problem im having is I have to refresh TWICE to see when the welcomeMessage is changed.
So if its originally "hello world" from the database and I change it to "hello UK" in the database.
When I refresh the page, i expect the app to fetch data and display loading.
When it finished fetching data, a re-render occurs and it should show Hello UK in the welcome message.
However this doesn't happen until the second page refresh.
Does what I described make sense to anyone?
You are not making any change based on the value of selectedShop
you should keep the value of selectedShop in a local state variable
const [ selectedShop , setSelectedShop ] = useState({});
then whenever the value is changed from api call update the local state value
useEffect( () => {
var selectedShop = stateProps.shops.find(s => {
return s.url === shopUrl;
});
setSelectedShop(selectedShop);
} , [stateProps.shops])
now when the value changes in stateProps it will update the local state value and trigger a re render .

fetching data after useReducer dispatch changes local state

Trying to rewrite this method using react hooks:
this.setState({
isPerformingAuthAction: true
}, () => {
auth.signInWithEmailAndPassword(emailAddress, password).then((value) => {
this.closeSignInDialog(() => {
const user = value.user;
const displayName = user.displayName;
const emailAddress = user.email;
this.openSnackbar(`Signed in as ${displayName || emailAddress}`);
});
}).catch((reason) => {
const code = reason.code;
const message = reason.message;
switch (code) {
case 'auth/invalid-email':
case 'auth/user-disabled':
case 'auth/user-not-found':
case 'auth/wrong-password':
this.openSnackbar(message);
return;
default:
this.openSnackbar(message);
return;
}
}).finally(() => {
this.setState({
isPerformingAuthAction: false
});
});
});
isPerformingAuthAction is a component property that disables buttons while performing various actions.
<Button disabled={state.isPerformingAuthAction} ... />
The Problem: I am using useReducer to manage local state and isPerformingAuthAction is a property of the reducerState. useReducer doesn't return a promise, so I can't do this:
dispatch({type:"isPerformingAuthAction", payload: true}).then(auth.stuff)
I thought about using useState instead, but it doesn't have a call back like this.setState with classes does.
I have been trying to wrap my head around some of the useEffect ways around fetching data and async. I am pretty confused about it, but I think I could have "isPerformingAuthAction" or the state from the reducer as a dependency and have useEffect update the component when it changes but the "isPerformingAuthAction" property changes for other reasons as well, not wtih just auth.signInWithEmailAndPassword(emailAddress, password), but signUp, signOut and a few others.
So when "isPerformingAuthAction" changes to true, it disables buttons so the user is forced to wait for the response from the server. I want to make sure those buttons are disabled (state updated/component re-rendered with greyed-out buttons) before I call the auth.whatever.

Issues with state-changes in submit method of AtlasKit Form

In the submit method of an Atlaskit Form, I want to change a value of a state property that results in the form being hidden:
<Form onSubmit={data => {
return new Promise(resolve => {
setShowForm(false);
resolve();
})
}}>
</Form>
However, this results in a React error:
Can't perform a React state update on an unmounted component. This is
a no-op, but it indicates a memory leak in your application. To fix,
cancel all subscriptions and asynchronous tasks in the
componentWillUnmount method.
The error disappears when i set that value a little later:
setTimeout(() => setShowForm(false));
So apparently the form is still unmounting while i change state (although i don't know why that should affect on the form, but i am not too familiar with React yet). What is the approach i should be taking here?
This is because you made an asynchronous request to an API, the request (e.g. Promise) isn’t resolved yet, but you unmount the component.
You can resolve this issue by maintaining a flag say _isMounted to see if component is unmounted or not and change the flag value based on promise resolution.
// Example code
class Form extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
this._isMounted = true;
axios
.get('my_api_url')
.then(result => {
if (this._isMounted) {
this.setState({
data: result.data.data,
});
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
...
}
}

Resources