0

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 !

5
  • 1
    I think the issue is that ‘datas’ isn’t tied into the react lifecycle at all Commented Oct 8, 2022 at 9:08
  • useMemo will only check to see if datas change during rerenders and with datas not doing anything to trigger rerenders, the likelyhood of the useMemo not catching changes in datas is pretty high Commented Oct 8, 2022 at 9:28
  • Is there a way to perform a rendering when the value of datas has changed ? Either a react or a typescript mecanism ? I think about rxjs mecanism, observables for instance. But I prefere to use React if it is possible Commented Oct 8, 2022 at 10:23
  • What was your reason for taking the class approach for creating a fetcher? Did you want to be able to fetch once and provide that value to multiple components? Commented Oct 8, 2022 at 11:13
  • Yep, exacly. My first Idea was to provide an instance of this service to the App component and inject it as a dependency using a context. But currently i only want to import this instance into a component and watch for the value changing. The idea is that fetch will be called once when the app is begining, and when fetch has ended, components which use the data will see that it has changed (in fact it has been intanciated), so they can render a new time. i don't want to perform a fetch at every render Commented Oct 8, 2022 at 12:15

2 Answers 2

0

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.

Sign up to request clarification or add additional context in comments.

1 Comment

With this solution, a fetch will be called every time a render will be performed, no ? So if i click on a button in my component, we will fetch datas again while they haven't changed
0

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

1 Comment

I have also spend more time using react native than react so my html knowledge is a little lacking

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.