7

I am using react-router-v4 along with react 16.

I want to reset the component's internal state when the user go to a different route or comes back to the same route . Route change should destroy the internal state of a component but it doesn't . And I can't even find a way to notify the component when the route changes as it's a nested component not a direct render of a Route component. Please help.

Here's the code or live codepen example --

const initialProductNames = {
    names: [
        { "web applications": 1 },
        { "user interfaces": 0 },
        { "landing pages": 0 },
        { "corporate websites": 0 }
    ]
};

export class ProductNames extends React.Component {

state = {
    ...initialProductNames
};

animProductNames = () => {
    const newArray = [...this.state.names];
    let key = Object.keys(newArray[this.count])[0];
    newArray[this.count][key] = 0;

    setTimeout(() => {
        let count = this.count + 1;

        if (this.count + 1 === this.state.names.length) {
            this.count = 0;
            count = 0;
        } else {
            this.count++;
        }

        key = Object.keys(newArray[count])[0];
        newArray[count][key] = 1;
        this.setState({ names: newArray });
    }, 300);
};

count = 0;

componentDidMount() {
    this.interval = setInterval(() => {
        this.animProductNames();
    }, 2000);
}

componentWillUnmount() {
    clearInterval(this.interval);
}

componentWillReceiveProps(nextProps) {
    console.log(nextProps.match);
    if (this.props.match.path !== nextProps.match.path) {
        this.setState({ ...initialProductNames });
        this.count = 0;
    }
}

render() {
    return (
        <section className="home_products">
            <div className="product_names_container">
                I design & build <br />
                {this.createProductNames()}
            </div>
        </section>
    );
}

createProductNames = () => {
    return this.state.names.map(nameObj => {
        const [name] = Object.keys(nameObj);
        return (
            <span
                key={name}
                style={{ opacity: nameObj[name] }}
                className="product_names_anim">
                {name}
            </span>
        );
    });
};
}
Ruhul Amin
  • 451
  • 1
  • 6
  • 12
  • Could you please provide your routing code – Gaurav joshi Feb 01 '18 at 04:48
  • yeah why not . But there's no catch in that – Ruhul Amin Feb 01 '18 at 04:49
  • See I just thinking that you can set some global state for location and while onenter hook of route we can modify that state and can be used in the component – Gaurav joshi Feb 01 '18 at 04:50
  • does react-router's route change destroy the component state? – Ruhul Amin Feb 01 '18 at 04:51
  • If route change doesn't reset the component's internal state then this is the default behavior of react-router and in that case we should find a way to notify component whenever the route changes – Ruhul Amin Feb 01 '18 at 04:53
  • this is such a basic feature how come react-router doesn't have that? – Ruhul Amin Feb 01 '18 at 04:54
  • like for example - you have a page containing forms that is controlled and the state is component state not redux or anything .In that case if the user goes to a different route entering some details into the form , the form's state will be still there if the user comes back to the form's route . And then the user will see a half populated form field – Ruhul Amin Feb 01 '18 at 04:57
  • i got it!!!!!!!!!! yeah . Drinks all around . shavvy!! – Ruhul Amin Feb 01 '18 at 05:41

3 Answers3

4

I got the solution . I didn't quit understood why state as property initializer doesn't reset/intialize on remount. I think it only initialize once, not on every route change] -

I wanted to know how to reset a component's state on route change. But it turns out that you don't have to . Each route renders a specific component . When route changes all other components are unmounted and all the state of those components are also destroyed. But see my code. I was using es7+ property initializer to declare state,count . That's why the state wasn't resetting/initializing again when the component remounted on route change.

To fix it, all i did is i put the state,initialProductNames,count; all of those into constructor. And now it's working perfectly .

Now fresh state on every mount and remount!!

Ruhul Amin
  • 451
  • 1
  • 6
  • 12
  • Hi, I think the issue was not the property initializer. The issue why it does not work after route changes may be because you define initialProductNames outside of the class which makes it a global javascript variable which will be mutated by whatever you do within your component. – schrodinger's code May 17 '18 at 21:46
  • na. I copied the initialProductNames array in the state with spread operator. So no chance of mutating the constant. – Ruhul Amin May 18 '18 at 05:55
  • Hey, if you can, please see my answer. I am sending it because a colleague of my work came across this answer, and assumed that the state is not re-created every remount. In my answer, I show that the state is recreated. If I can put my answer right or correct your text on that point, I would appreciate it. – Gabriel Katakura Nov 21 '18 at 12:08
  • With that, I hope that other people do not have problems this, as my colleague had (which we managed to correct, after showing the facts above). – Gabriel Katakura Nov 21 '18 at 12:09
  • Ah, also, remember that the spread operator is a shallowCopy, not a deepCopy. – Gabriel Katakura Nov 21 '18 at 12:10
3

You can use a listener on the Route change as the example on this previous question And there you can add a function to update the main state.

  componentDidUpdate(prevProps) {
    if (this.props.location !== prevProps.location) {
      this.onRouteChanged();
    }
  }

  onRouteChanged() {
    console.log("ROUTE CHANGED");
  }
Juan P. Ortiz
  • 518
  • 6
  • 20
1

The problem is not the state, it's the initialProductNames. Property initializer is a sugar syntax, in fact it is the same as creating a constructor and moving the code into the constructor. The problem is in the initialProductNames, which is created outside the component, that is, only once for the whole system.

For create a new initialProductNames for any instance of ProductNames, do that:

export class ProductNames extends React.Component {

  initialProductNames = {
    names: [
        { "web applications": 1 },
        { "user interfaces": 0 },
        { "landing pages": 0 },
        { "corporate websites": 0 }
    ]
  };

  state = {
    ...this.initialProductNames
  };

  // more code

  componentWillReceiveProps(nextProps) {
    console.log(nextProps.match);
    if (this.props.match.path !== nextProps.match.path) {
        this.setState({ ...this.initialProductNames });
        this.count = 0;
    }
  }

Here is an example showing that the state is always recreated every remount: https://codesandbox.io/s/o7kpy792pq

class Hash {
  constructor() {
    console.log("Hash#constructor");
  }
}

class Child extends React.Component {
  state = {
    value: new Hash()
  };

  render() {
    return "Any";
  }
}

class App extends React.Component {
  state = {
    show: true
  };
  render() {
    return (
      <div className="App">
        <button
          type="button"
          onClick={() =>
            this.setState({
              show: !this.state.show
            })
          }
        >
          Toggle
        </button>
        {this.state.show && <Child />}
      </div>
    );
  }
}