0

I am setting the state of a state variable in reactjs when a press a button. I call the following procedure (which is actually being called).

nextSuggestion() {
        console.log(this.state.suggestions_index);
        this.state.suggestions_index = this.state.suggestions_index + 1;
        console.log(this.state.suggestions_index);
    }

The above function is called and the page is not rerendered, even though I have the following componentDidMount():

async componentDidMount(){

        const query = new URLSearchParams(this.props.location.search);

        const token = query.get('token');


        const url = "http://www.mocky.io/v2/" + token;


        this.setState({url_prod: url});    

        const response = await fetch(url);
        const data = await response.json();
        this.setState({produto: data.suggestions[this.state.suggestions_index], loading_produto: false})
        this.setState({alternativas: data.choices, loading_alternativas: false})
        this.setState({atributos: data.suggestions[this.state.suggestions_index].product_data.attributes, loading_atributos: false})

    }

The button is created through this code in render() function:

  if(!this.state.loading_alternativas){

    this.texts = lista_alternativas.map((text, key) => {


        return  <div id="center-button"><button type="button" className="btn btn-primary" key={text.id} onClick={this.nextSuggestion.bind(this)}>{text.text}</button></div>
    });
}

the state:

state = {
        produto: null,
        alternativas: null,
        atributos: [],
        suggestions_index: 0,
        loading_produto: true,
        loading_alternativas: true,
        loading_atributos: true,
        showPopupDescProd: false,
        showPopupAnswer: false,
        url_prod: null,
    };

What am I missing to make it work?

Siqueira
  • 423
  • 1
  • 7
  • 29

1 Answers1

4

You're mutating state:

this.state.suggestions_index = this.state.suggestions_index + 1;

Which is a bad thing. More specifically, it's not expected by the ReactJS framework so it has no reason to re-render.

Don't set state values directly. Update the state with a new state object. For example:

this.setState((prevState) => ({
    suggestions_index: prevState.suggestions_index + 1
}));

Calling setState() with notify the framework to replace the current state with the new one, triggering a re-render where applicable.


Edit: In response to Andy's comment (from which I learned something new, thanks Andy!), note also that setState() is asynchronous in nature. Since you're looking to log to the console after setting the state, you'd want to do that in a callback. For example:

const newState = {
    ...this.state
    suggestions_index: this.state.suggestions_index + 1
};
this.setState(newState, () => console.log(this.state.suggestions_index));

(Though I suspect your console.log use is temporary for debugging only and will probably which removed, so you'd just omit the callback.)

David
  • 208,112
  • 36
  • 198
  • 279
  • 1
    Worth mentioning the `setState` callback so that the OP can `console.log` the change in state. – Andy Nov 10 '19 at 17:22
  • 1
    @Andy: Interesting, I wasn't aware of that callback. Just found some info here: https://stackoverflow.com/questions/42038590/when-to-use-react-setstate-callback Which I'm taking a look at now. I may indeed update the answer as a result. Thanks! – David Nov 10 '19 at 17:25
  • 1
    ah, class version of `setState` should NOT contain `...this.state` as it will merge the keys automatically, and it should NOT refer to `this.state` (see https://stackoverflow.com/a/51817868/1176601), `this.setState((prevState) => ({suggestions_index: prevState.suggestions_index + 1}))` – Aprillion Nov 10 '19 at 17:31
  • @Aprillion: I'm learning a lot today apparently :) Admittedly my time spent on class components was very limited, my team moved into Redux and hooks and whatnot almost right away. Never saw that way of setting state before, but it makes sense. – David Nov 10 '19 at 17:36
  • even with hooks, [functional updates](https://reactjs.org/docs/hooks-reference.html#functional-updates) like `setCount((prevCount) => prevCount + 1)` are useful inside Closures (e.g. if you click 2x on a button to increase `count` by 2, but it does not have time to re-render after 1st click) – Aprillion Nov 10 '19 at 17:42
  • The page still nor rerendering. – Siqueira Nov 10 '19 at 20:33
  • @Siqueira: Can you provide a more complete example demonstrating the problem? – David Nov 10 '19 at 20:34
  • The system is not calling the "async componentDidMount()" again. – Siqueira Nov 10 '19 at 20:34
  • @Siqueira: Well, no, it wouldn't re-mount the component. But that's not what's being asked in the question. Can you clarify the problem? – David Nov 10 '19 at 20:35
  • That is it ... I am pressing the button .. I can see that the state is changing because of the console.log output, but the page is not rerendering with the next item of the list. – Siqueira Nov 10 '19 at 20:36
  • @Siqueira: Please provide a more complete example to demonstrate the problem. The extended comment thread pretty strongly implies that you have other mistakes in your React code. – David Nov 10 '19 at 20:37
  • How can I re-mount the component? I need to get the next item of the list... I have this suggestions list, which holds the data that fulfills the page... when the user clicks the button it goes to the next item of the list and the page data needs to be refreshed. – Siqueira Nov 10 '19 at 20:38
  • @Siqueira: You *shouldn't* re-mount the component. There's no need to do that. Your component should simply display data based on the current state (in `render()`) and update the state (in invoked methods which call `.setState()`). The component isn't re-mounting because it doesn't, and shouldn't, re-mount. You're using ReactJS incorrectly. You are encouraged to walk though some React tutorials again. – David Nov 10 '19 at 20:40