How to use useMemo with an external API?

12.3k Views Asked by At

I'm working with react-table library for filtering and pagination. I am requesting data from a custom API using Node and rendering it using useTable hook from react-table. The documentation for react-table says the table data should be memoized, so I use the useMemo hook. The code is as shown:

  const data = React.useMemo(() => {
    axios.get("/custom/api/endpoint")
      .then((res) => {
        return res.data;   //Returns an array of objects
      })
      .catch((err) => {
        console.log("err");
      });
  }, []);

However, I am getting the following error:

Uncaught TypeError: Cannot read property 'forEach' of undefined
    at accessRowsForColumn (useTable.js:591)
    at useTable.js:195
    .
    .

Can anyone help me figure this? Thanks in advance!

4

There are 4 best solutions below

2
On BEST ANSWER

You are memoizing the promise that axios creates. Instead make the call with useEffect(), and set the state. Unless you'll set the state again, the data would not change:

const [data, setData] = useState([]); // use an empty array as initial value

useEffect(() => {
  axios.get("/custom/api/endpoint")
    .then((res) => {
      setData(res.data); // set the state
    })
    .catch((err) => {
      console.log("err");
    });
}, []);
0
On

You're using this arrow function inside the useMemo call.

() => {
    axios.get("/custom/api/endpoint")
      .then((res) => {
        return res.data;   //Returns an array of objects
      })
      .catch((err) => {
        console.log("err");
      });
  }

And it returns undefined. It's because axios.get returns a Promise and you're not assigning it to anything and your arrow function implicitly returns undefined as it doesn't have an explicit return statement.

Try this:

const [data, setData] = useState([]);

useEffect(() => {
  axios.get("/custom/api/endpoint")
    .then((res) => {
      setData(res.data);
    })
    .catch((err) => {
      console.log("err");
    });
}, []);

Here useEffect is called after the initial render of the component, which calls axios.get. It is an async function so the useEffect call immediately returns after calling it. When the axios.get async call succeeds it calls the arrow function within the then call which assigns the data from the response to the data state variable.

1
On

You have defined a promise and not returning it, so the code is not waiting for the promise to resolve. Try with async await syntax, which is easier to visualize the flow.

  const data = React.useMemo(async () => {
    
      try {
        const res = await axios.get("/custom/api/endpoint") 
        return res.data
      } catch(err) {
        throw err
      }
 
  }, []);
0
On

These solutions not succesful need for me, because I structured my pattern for project. My data request with redux dispatch (so redux-thunk) actually I used redux-toolkit for fast development.

I solved like this;

function Table ({ columns, data }) {
    // ...
    return /* standart react-table basic component */
}

function MyTable(props) {
  const columns = useMemo(() => props.columns, [props.columns]);
  const data = useMemo(() => props.data, [props.data]);

  return <Table columns={columns} data={data} />;
}

const PageListTable = () => {
    // ...
    const columns = [...]; // static variable

    useEffect(
      async () => {
        const payload = {};

        // some codes

        dispatch(getAllData(payload));
      },
      [activeId, isAuth] // listen from with useSelector
    );

    // some codes..

    return (
        {/* ..... */}

        <HOC.Async status={loading}>
            <MyTable
                columns={columns}
                data={getAllDataState.data}
            />
        </HOC.Async>

        {/* ..... */}
    );
}

My package.json specs like this;

"react": "^17.0.2",
"react-redux": "^7.2.4",
"react-table": "^7.7.0",
"@reduxjs/toolkit": "^1.6.1",