Home > Blockchain >  React hook "useMemo" with array as dependency
React hook "useMemo" with array as dependency

Time:10-09

I am new to react (that I use with typeScript) and I am facing an issue with the use of the useMemo hook.

Here is my fetching service:

export default class FetchingService  {
    datas: Data[] = [];

    constructor() {
        this.fetch();
    }

    async fetch(): Promise<Data[]> {        
        const d = // await an async array from an api, using Array.flat()
        this.datas = d;
        console.log(this.datas);
        return d;
    }
}

In a component, I try to watch for change of the datas attribute of my service:

import fetchingService from '../services/fetchingService.ts';

const Home: React.FC = () => {
  const ds: Data[];
  const [datas, setDatas] = useState(ds);

  const fetchDatas = useMemo(() => {
        console.log('Render datas', fetchingService.datas?.length)
        setDatas(fetchingService.datas);
        return fetchingService.datas;
    }, [fetchingService.datas]);


  return (
    <ul>{datas.map(d => {
      return (
        <li key={d.id}>{d.id}</li>
      );
    </ul>
  );
}

The problem I am facing is that the useMemo hook is not recompouted when the datas attribute changes within my fetchService. I am pretty sure that my FetchingService.fetch() function works because the console.log within the fetch function always display the fetched datas.

The observed behavior is that sometimes datas are well rendered (when fetch ends before rendering ...), but sometimes it isn't.
The expected one is that datas are rendered every time and only on refresh, exept when datas are modified

I also tried to put the length of the data array as a dependency in useMemo, but in both cases it doesn't work and I have a warning in my IDE, telling me it is an unnecessary dependency.

I don't really understand if it is a typescript or a specific react behavior issue. I think the reference of the datas attribute should change at the end of the fetch (or at least its length attribute ...), but tell me if I am wrong.

I do appreciate every help !

CodePudding user response:

in fetchingService, when datas change, probably the dependency cannot be accepted. You can use a custom hook in stead of it.

You can use this source about useMemo: useMemo with an array dependency?

import { useState, useLayoutEffect, useCallback } from "react";

export const useFetchingService = () => {
  const [fetchedData, setFetchedData] = useState([]);

  const fetch = useCallback(async () => {
    const d = await new Promise((res, rej) => {
      setTimeout(() => {
        res([1, 2, 3]);
      }, 5000);
    }); // await an async array from an api, using Array.flat()
    setFetchedData(d);
  }, []);

  useLayoutEffect(() => {
    fetch();
  }, []);

  return [fetchedData];
};

useLayoutEffect runs before rendering using:

const [fetchData] = useFetchingService();

  const fetchDatas = useMemo(async () => {
    console.log("Render datas", fetchData.length);
    setDatas(fetchData);
    return fetchData;
  }, [fetchData]);

You can also use this directly without 'datas' state.

I hope that this will be solution for you.

CodePudding user response:

So I put together a codesandbox project that uses a context to store the value:

App.tsx

import React, { useState, useEffect, createContext } from "react";
import Home from "./Home";

export const DataContext = createContext({});

export default function App(props) {
  const [data, setData] = useState([]);

  useEffect(() => {
    const get = async () => {
      const d = await fetch("https://dummyjson.com/products");
      const json = await d.json();
      const products = json.products;
      console.log(data.slice(0, 3));
      setData(products);
      return products;
    };
    get();
  }, []);

  return (
    <div>
      Some stuff here
      <DataContext.Provider value={{ data, setData }}>
        <Home />
      </DataContext.Provider>
    </div>
  );
}

Home.tsx

import React, { FC, useMemo, useState, useEffect, useContext } from "react";

import { DataContext } from "./App";
import { Data, ContextDataType } from "./types";
const Home: FC = () => {
  const { data, setData }: ContextDataType = useContext(DataContext);
  return (
    <>
      <ul>
        {data.map((d) => {
          return (
            <li key={d.id}>
              {d.title}
              <img
                src={d.images[0]}
                width="100"
                height="100"
                alt={d.description}
              />
            </li>
          );
        })}
      </ul>
    </>
  );
};
export default Home;

This was my first time using both codesandbox and typescript so I apologize for any mistakes

  • Related