0

I have a project, using Create-react-app on the front end, I have an API which serves data from a MongoDB,

Now in my React project something extremely bizarre is happening. I have a component which renders a dynamically populated table of data. I Have pagination set up so that the user can navigate 10 items per page. Herein lies the problem.

The first AND ONLY the first time you press Next the page DOES NOT update, but if you press prev or next again it works seemingly fine, except it causes an error with the order of the data being mapped on the table. enter image description here

You can see the table above, the component loads fine initially, but that first attempt to navigate pages does not work. I have tested the API route with Postman & it returns fine, I actually have another front end with Jquery that does the same thing & it works perfectly fine. Below is the code where the error is occurring. One more Note, if you view the state in the react dev tools, the states. page is updated when next is pressed, but the sales array does not change, Any help is immensity appreciated. I believe it has something to do with the handleChange method or the fetchdata method, but I cannot figure out why. Please help.

import React, { Component } from 'react';
import Moment from 'react-moment';
class Sales extends Component
{
    constructor(props)
    {
        super(props);
        this.state =
        {
            sales : [],
            loaded : false,
            page : 1
        }
    }

    componentDidMount()
    {
        this.fetchData()
        .then((sales) =>
        {
            this.setState(
            {
                sales: sales,
                loaded : true
            })
        })
    }


     handleChange = (e) =>
    {        
        const target =  e.target;
        const value = target.value
        console.log(value)
        if(value === 'Next')
        {
            this.setState({page : this.state.page + 1});
            this.fetchData()
                .then((sales) =>
                {
                    this.setState({sales : sales})
                })
        }
        if(value === 'Previous')
        {
            if(this.state.page > 1)
            {
                this.setState({page : this.state.page - 1});
                this.fetchData()
                .then((sales) =>
                {
                    this.setState({sales: sales})
                })

            }
        }     
        this.forceUpdate();
    }

    fetchData()
    {
        console.log("Loading")
        let promise  = new Promise((resolve, reject)=>
        {            
            fetch(`https://sales-supplies-api.herokuapp.com/api/sales?page=${this.state.page}&perPage=10`)
                .then(response => response.json())
                .then(results =>
                    {
                        console.log(results)
                        const sales = results.map(sale =>
                            {                                                                
                                return sale;
                            });
                        resolve(sales)                    
                        console.log(sales)
                    });
        })
        return promise;

    }

    render()
    {
        const {sales,loaded, page } = this.state;
        if(loaded)
        {
        return(
            <>
            <div className='container-fluid'>
                <table className='table table-hover'>
                    <thead>
                        <tr>
                            <th>Customer</th>
                            <th>Store Location</th>
                            <th>Number of Items</th>
                            <th> Sale Date</th>
                        </tr>
                    </thead>
                    <tbody>
                        {sales.map((sale, i)=>(
                            <tr key={i}>
                                <td>{sale.customer.email}</td>
                                <td>{sale.storeLocation}</td>
                                <td>quantity</td>
                                <td>
                                    {new Date(sale.saleDate).toLocaleDateString()}
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>
                <nav aria-label="Page navigation example">
                    <ul className="pagination">
                    <li className="page-item">
                        <button className="page-link" value="Previous" id="prevPage" onClick={this.handleChange}> 
                             Previous 
                        </button>
                    </li>
                    <li className="page-item"><button id='page'className="page-link" href="">{this.state.page}</button></li>          
                    <li className="page-item">
                        <button className="page-link" value="Next" id="nextPage" type="button" onClick={this.handleChange}>
                             Next 
                        </button> 
                    </li>
                    </ul>
                </nav>
                </div>
            </>
        )
    }
    return(
        <>
        </>
    )
}
}
export default Sales;
d0rf47
  • 409
  • 8
  • 20
  • Missing some of the most obvious things: what does your dev console show when you click next? Because "nothing happens the first time" is almost always not true: a network call happens, a JS error occurs because you're accessing something that's undefined until an async-amount-of-time-later, etc. etc. – Mike 'Pomax' Kamermans Feb 12 '20 at 22:28
  • 3
    I read the title and immediately knew what the issue is. Extremely common beginner's mistake. Setting state is async. Which means increasing `this.state.page` then calling `fetch()` using the new value isn't what's happening, your fetch call is using the old value. –  Feb 12 '20 at 22:28
  • Okay I understand that but I did it the way the react docs show here https://reactjs.org/docs/faq-ajax.html So how do I ensure the new state is used then? – d0rf47 Feb 12 '20 at 22:30
  • @Mike'Pomax'Kamermans It shows that Next is changed but the array is the same – d0rf47 Feb 12 '20 at 22:31
  • 1
    A quick fix is to do this: `this.setState({page : this.state.page + 1}, () => { /* fetch call in here */ });` –  Feb 12 '20 at 22:33
  • 1
    or call `fetch(page)` ... explicitely pass param ;) ... and use `async ... await` – xadm Feb 12 '20 at 22:35
  • wow that works Thank you so much. I cant believe it. So if I may ask, why was the previous page button working after that what was really confusing me – d0rf47 Feb 12 '20 at 22:36
  • Another way is to use `componentDidUpdate()`. Or you do this: `const newPage = this.state.page + 1` then `fetch(.../newPage).then(...).then(sales => this.setState({ page: newPage, sales });` –  Feb 12 '20 at 22:37
  • I actually was trying the ComponentDidUpdate cause logically it made sense, but it kept refreshing every second, I guess cause I was still calling the fetchdata with the previous state, but should the state be changed before the didupdate runs? – d0rf47 Feb 12 '20 at 22:38
  • 1
    Yeah, that happens when you don't compare old state and new state for changes first. If you keep setting state in `componentDidUpdate()`, you're creating an infinite loop. –  Feb 12 '20 at 22:38
  • Okay I understand now, thank you very much – d0rf47 Feb 12 '20 at 22:39

0 Answers0