0

I am using Axios to fetch data from an API in React. I have a search component that runs the request on input change. I am then making a table with the rows data using MDB Datatables

The first time the table is rendered, it works great. However, it doesn't render with fresh data despite Axios retrieving it when the search box changes. I discovered that I could manually trigger a table refresh by making any edit to datatable.columns (eg. changing a width property from 50 to 55)

I am using App.js and two components: Search.js and DataTable.js

I believe I need to update the state of the rows property of datatable. The columns section are the hardcoded table headers which don't change.

How can I accomplish this?

App.js

import './App.css';
import {useState, useEffect} from "react"
import axios from 'axios'

import Search from './components/UI/Search'
import DataTable from "./components/UI/DataTable";

function App() {
    const [items, setItems] = useState([])
    const [query, setQuery] = useState(true)

    useEffect(() => {
        const fetchItems = async () => {
            const result = await axios(`https://services1.arcgis.com/0MSEUqKaxRlEPj5g/arcgis/rest/services/ncov_cases_US/FeatureServer/0/query?where=Province_State%20%3D%20'${query}'&outFields=*&returnGeometry=false&&outSR=4326&f=json`)
            const cleanResult = result.data.features
            setItems(cleanResult)
        }
        fetchItems()

        // fires off every time ~query~ changes because dependency
    }, [query])


    return (
        <div className="container">
            <div className='text-4xl px-10 py-4'>Covid data</div>
            <Search getQuery={(q) => setQuery(q)}/>
            <DataTable items={items}/>
        </div>
    );
}

export default App;

DataTable.js

import React from 'react';
import {useState} from "react";
import {MDBDataTableV5} from 'mdbreact';


export default function Basic({items}) {

    let newItems = items.map(function (item, index) {
        return {
            id: (index + 1),
            Admin2: item.attributes.Admin2,
            State: item.attributes.Province_State,
            Incident_Rate: Math.round(item.attributes.Incident_Rate),
            Confirmed: item.attributes.Confirmed,
            Deaths: item.attributes.Deaths,
            Last_Update: new Date(item.attributes.Last_Update).toLocaleDateString(),
        }
    });

    let [datatable, setDatatable] = React.useState({
        columns: [
            {
                label: 'Region',
                field: 'Admin2',
                width: 140,
                attributes: {
                    'aria-controls': 'DataTable',
                    'aria-label': 'Name',
                },
            },
            {
                label: 'State',
                field: 'State',
                width: 120,
            },
            {
                label: 'Incident Rate',
                field: 'Incident_Rate',
                width: 100,
            },
            {
                label: 'Confirmed',
                field: 'Confirmed',
                width: 120,
            },
            {
                label: 'Deaths',
                field: 'Deaths',
                width: 55,
            },
            {
                label: 'Last Update',
                field: 'Last_Update',
                width: 55,
            },
        ],
        // use fetched content for table rows
        rows: newItems
    }
    );
    // return MDB DataTable
    return <MDBDataTableV5 hover entriesOptions={[5, 20, 25, 100]} entries={25} pagesAmount={4} data={datatable}
                           fullPagination/>;
}

Search.js (I don't think this needs to change)

import React, {useState} from 'react';

const Search = ({getQuery}) => {
    const [text, setText] = useState('')

    const onChange = (q) => {
        setText(q)
        getQuery(q)
    }

    return (
        <section className='search px-10 py-4'>
            <form>
                <label className="block">
                    <span className="block text-xl font-medium text-slate-700">Search</span>
                <input
                    type='text'
                    className='form-control mt-1 block w-full px-3 py-2 bg-white border border-slate-300 rounded-md text-sm shadow-sm placeholder-slate-400
      focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500'
                    placeholder='Georgia'
                    value={text}
                    onChange={(e) => onChange(e.target.value)}
                    autoFocus/>
                </label>
            </form>
        </section>
    );
};

export default Search;
karhu
  • 119
  • 1
  • 9
  • 1
    The value you pass to useState() will only be used on the initial render of your component. For subsequent rerenders, the initial object that was used when useState() was first called is set as the value of datatable. Consider removing the datatable state and using just a variable for it? – Nick Parsons Jun 01 '22 at 22:26
  • @NickParsons That fixed it! I was using the state because they had that in the MDB Datatable documentation despite never setting it. I wonder why they did that... – karhu Jun 01 '22 at 22:48
  • 1
    They most likely did that so that they can change the `rows` array by calling `setDatatable`. I would probably instead move your `datatable` state up to `App`, & remove the `items` state. When you do your axios call, you can use `setDatatable()` & update the `rows` state by mapping your axios data like you're currently doing. Then you can pass your `datatable` state directly to `Base`, or remove the `` component and instead just render the ``. Otherwise, there is [this](https://stackoverflow.com/a/54626764/5648954) simpler option (but not sure if it's best practice) – Nick Parsons Jun 01 '22 at 23:24

0 Answers0