Ruhul Amin
Ruhul Amin

Reputation: 491

reset component's internal state on route change

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

Upvotes: 8

Views: 16971

Answers (3)

Gabriel Katakura
Gabriel Katakura

Reputation: 111

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

Upvotes: 1

Ruhul Amin
Ruhul Amin

Reputation: 491

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!!

Upvotes: 5

Juan P. Ortiz
Juan P. Ortiz

Reputation: 589

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

Upvotes: 3

Related Questions