React - Can't update state property

I am trying to update a state property on a click event and use it in another method, code:

class Movies extends Component {
  state = {
    movies: [],
    pageNumber: 1
  };

  async componentDidMount() {
    const queryString =
      "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=<<api_key>>&page=";
    const query = queryString + this.state.pageNumber;
    const { data: movies } = await axios.get(query);
    this.setState({ movies: movies.results });
 
  }

The method to handle the page change:

handlePageChange = () => {
    let pageNumber = 2;
    this.setState({ pageNumber });
  };

the pageNumber property is updating in the state, but the state of this property remains the same in the componentDidMount method, not sure why.

Have you looked at your console and see any logs there?

The pageNumber key updates in the state, I know this because I have a console.log(this.state.pageNumber) in the render method, but it’s not updating in the componentDidMount method for some reason.

Shouldn’t the state be updated in the componentDidMount method whenever I update pageNumber?

I don’t have a demo, I could perhaps upload the whole project on GitHub. But is there any particular reason why the pageNumber in the componentDidMount method would not update ?

I am checking in the console and the key pageNumber does update in the state.

  handlePageChange = () => {
    let pageNumber = this.state.pageNumber + 1;
    this.setState({ pageNumber });
  };

  render() {
    console.log(this.state.pageNumber);
    return (...)
}

How am I supposed to implement pagination if I can’t make the querystring dynamic ?.

Hi @Gilbert1391.

Be careful of the lifecycle methods you use. Here is what the React docs say:

componentDidMount() is invoked immediately after a component is mounted (inserted into the tree). Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.

componentDidMount will be triggered once the component have been mounted in the DOM and that’s all. Therefore, every time you update the state, it won’t be triggered. Maybe you should take a look to componentDidUpdate instead of componentDidMount. Indeed, componentDidUpdate is triggered every time the component re-renders. Here are the docs.

1 Like

Indeed that was the problem, As soon I as used componentdidUpdate instead it worked, however I wonder if it’s a good idea to use componentDidUpdate for something like this? I also have a small bug, the first page does not display unless I click an anchor with the click event.

Thanks guys btw.

I think it’s pretty fine as long as you make sure your new network requests are triggered just when your pageNumber changes and not every time the component re-renders.

If componentDidUpdate really bothers you, you can move the network request in your handlePageChange method.

I didn’t get the bug you had? Could you provide an example? :slight_smile:

Sure, I will provide the whole code as I made a few changes to make my life easier, that way we can be on the same page, thank you very much. This was driving me crazy lol

Ok, first of all I want to display only a maximum of 10 pages for my pagination component. I created an array of objects in a different file that looks as followed:

export const pages = [];

let queryString =
  "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=";

for (let i = 1; i < 11; i++) {
  pages.push({ number: i, query: queryString + i });
}

This is just to create queries from page 1 to 10. The pagination component structure:

const Pagination = props => {
  const { onPageChange } = props;

  return (
    <nav className="pagination-nav">
      <ul className="pagination">
        {pages.map(page => (
          <li
            key={page.number}
            className="pagination__item"
            onClick={() => onPageChange(page.query)}
          >
            <a className="pagination__link">{page.number}</a>
          </li>
        ))}
      </ul>
    </nav>
  );
};

Originally I had prev and next buttons but I deleted them, I don’t think they are necessary for only 10 pages. Here you can see that I’m mapping through the pages array of objects that I previously created. I can display the number of pages based on the number of items in my pages array, plus I can get dynamically the querystring that corresponds to each page.

Now the component that controls the state:

class Movies extends Component {
  state = {
    movies: [],
    page:
      "https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=###&page=1"
  };

  async componentDidUpdate() {
    const { data: movies } = await axios.get(this.state.page);
    this.setState({ movies: movies.results });
  }

handlePageChange = page => {
  this.setState({ page });
};

As for the bug, when I use componentDidUpdate only, the thing is that when the page loads, I should be able to see 20 items from the page 1 (it’s 20 items per page), instead nothing shows up on the screen until I click one of the anchors. Here’s an image:

When I click any of the anchors from the pagination nav, then the corresponding 20 movies of the clicked anchor will be displayed. Hopefully you this helps to clarify the situation.

BTW, I can fix the bug if I just add componentDidMount with the exact same code that I use in componentDidUpdate, but that can’t be right or good practice, right? I am just repeating myself and I’m sure there must be a better way to do this.

Your Movie component will cause an infinite loop since you’re calling setState in it. From the React docs:

“You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop. It would also cause an extra re-rendering which, while not visible to the user, can affect the component performance”

Don’t know if that is the cause of your bug since I don’t have the full code but it may be that :thinking: