React setState in componentDidMount not working - reactjs

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

Related

How to properly get rid of UNSAFE_componentWillMount

For a React app that I inherited from another developer, one of the pages includes:
import { getLogUser } from "../../appRedux/actions/authAction";
constructor(props) {
super(props);
this.state = {
user: null,
};
}
UNSAFE_componentWillMount() {
let user = getLogUser();
this.setState({ user });
// user state is used inside the render part
}
componentDidMount = () => {
let { username } = getLogUser();
// ... username is used inside some logic within the componentDidMount method.
I would like to get rid of the UNSAFE_componentWillMount method.
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
If that is indeed the correct way to do it, shouldn't I then also
replace let { username } = getLogUser(); inside
componentDidMount with let { username } = this.state.user?
To start, let me explain what is UNSAFE_componentWillMount first
By defination
UNSAFE_componentWillMount() is invoked just before mounting occurs. It is called before render(), therefore calling setState() synchronously in this method will not trigger an extra rendering.
So it means UNSAFE_componentWillMount() will be called before render() (the component has not been on UI yet). This is totally opposite of componentDidMount() which is called after render()
To go deeper into why React's team wanted to make it UNSAFE as for a deprecated function, you can check this RFC.
Following up on your questions
Can I remove the UNSAFE_componentWillMount part if I use user: getLogUser() inside the constructor?
The benefit to having your function calls in the constructor is similar to UNSAFE_componentWillMount which makes sure your data available before rendering trigger.
So I'd say yes for your case, you can do it as long as it's not an asynchronous function (like async/await)
constructor(props) {
super(props);
this.state = {
user: await getLogUser(), //YOU CANNOT DO THIS WAY
};
}
This is the correct way
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
So what if getLogUser() is asynchronous? componentDidMount comes in handy. It will be triggered after first rendering but you can wait for your data as much as you want and beyond that, it won't block your UI's interactions (or you can show a loading UI instead)
componentDidMount = async () => {
const user = await getLogUser()
setState({ user })
}
render() {
//show loading if `user` data is not populated yet
const { user } = this.state
if(!user) {
return <div>Loading</div>
}
}
If that is indeed the correct way to do it, shouldn't I then also replace let { username } = getLogUser(); inside componentDidMount with let { username } = this.state.user?
Yes, indeed. You can do it if you already populate user state in constructor, but you need to ensure your function will be executed in a small amount of time. If your function call takes too long, that will cause UI problems due to the blocked rendering.
//trigger before first rendering
constructor(props) {
super(props);
this.state = {
user: getLogUser(), //no asynchronous call
};
}
//trigger after first rendering
componentDidMount = () => {
const { username } = this.state.user;
}

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

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

Check New Route on componentWillUnmount?

I have some code that will run on componentWillUnmount() but I only want it run if they go back to the previous page. If they go forward to the next page I don't want what is inside the componentWillUnmount to run.
I am using React Router 4 but when I check it in the componentWillUnmount it still has not updated to whatever the next url is.
componentWillUnmount() {
const props = this.props;
const location = props.location;
}
React Router provides a history object which you can use to set some variables before the transition to a new location.
Try something like this:
componentDidMount() {
this.props.history.block((location, action) => {
this.isGoingBack = action === 'POP';
})
}
componentWillUnmount() {
if (this.isGoingBack) {
...
}
}
You might need to check the location aswell.

using componentWillUnmount in context of next js

I'm trying to run a method on componentWillUnmount(i'm using the next js framework).The issue is that the componentWillUnmount method does not fire. However componentDidMount is working fine.
class TeamMember extends Component {
constructor(props)
{
super(props);
this.state = {
teamMember: this.props.teamMember,
startDate: null,
}
}
static async getInitialProps ( context ) {
const { slug } = context.query;
const res = await fetch(``);
const teamMember = await res.json();
return {
teamMember:teamMember
}
}
async componentDidMount()
{
this.setState({
startDate: Date.now()
})
Tracker.pushObjectToStorage('profilesViewed',{
title:this.state.teamMember[0].title.rendered,
id:this.state.teamMember[0].id
})
}
async componentWillUnmount(props)
{
alert("ddffff");
console.log("ddsds");
}
}
this is my code for the page. when you leave the page I want the componentWillUnmount to fire. i've put an alert there for test purposes.
This is expected and intended behavior of Nextjs routing. For more information, you can check this issue: https://github.com/zeit/next.js/issues/2819.
When you go to another page, no unmounting of components occurs, but instead, a whole new page is rendered.
This is the same behaviour as if you were refreshing (or landing for the first time) on a page. A React component will not unmount when you hit F5 on a page, because it is not unmounting, the page is simply refreshing.

What is a best way to get value from AsyncStorage before whole app load

I have App.js file and it is root of my application (both ios and android reference to it).
I have a value that I keep in AsyncStorage that I need before app.js render method is called.
Problem is that as it is async it late and I can't get that value.
class App extends React.Component {
constructor(props) {
super(props);
this.init()
}
async init() {
try {
const value = await AsyncStorage.getItem('#myPoorValue:key');
if (value !== null){
...
}
} catch (error) {}
}
}
...
render (...
I hope that I explained good what is my issue here.
I know that there is no way to get it synchronous (I would like that) but don't know what to do in this situation.
To explain it a bit better I use I18n and I manually set I18n.locale to some value and other components get default value before I set it manually.
Just to note I also use redux and I pass selected value to it.
try the following:
...
constructor(props) {
super(props)
this state = {
isLoading: true
}
}
async componentDidMount() {
await this.init()
// you might want to do the I18N setup here
this.setState({
isLoading: false
})
}
async init() {
const value = await AsyncStorage.getItem('#myPoorValue:key')
...
}
...
the thing is that init() returns a promise and you need to wait until it gets resolved. That's when await comes to rescue.
you'll also need to set up some loader that will be there on first render, and toggle the state to replace it with actual markup after the AsyncStorage value has been fetched. I've put it in the code, but you might want to trigger a redux action instead, depending on your setup.

Resources