0

I have a MongoDB with Express that serves the data on an endpoint, which is accessed by React's useEffect:

function App() { 
    // const [responsive, setResponsive] = useState("vertical");
    // const [tableBodyHeight, setTableBodyHeight] = useState("100%");
    const [data, setData] = useState([]);
    const [columns, setColumns] = useState([]);

    // const options =  {   
    //     filter:true,
    //     filterType:'dropdown',
    //     responsive,
    // }   

    // Fetch data / headers from express server 
    useEffect(() => {
        const fetchData = async () => {
            const resp = await fetch('http://localhost:4000/todos/');
            const respData = await resp.json();
            
            // Filter out irrelevant data
            const keysToFilterOut = ['_id', '__v']
            const firstDatum = respData[0]; 
            const filteredDatum = _.omit(firstDatum, keysToFilterOut);
            const filteredColumns = Object.keys(filteredDatum);

            setData(respData);
            setColumns(filteredColumns);
        };  
        fetchData()
    }, [data]);

The useEffect hook is called all the time, which means the data is somehow always changing. Indeed, I verified it by adding these 3 lines to the hook:

console.log(data === respData);
console.log(data);
console.log(respData);

and the 1st console log is indeed false. I don't get it, since the server didn't change the data, and moreover, I looked at the 2 other console logs - they seem identical. How is it that the data is different and how to fix it?

Here is an example from the console log of my app:

enter image description here

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
CIsForCookies
  • 12,097
  • 11
  • 59
  • 124

3 Answers3

3

Because in JS:

Primitives like strings and numbers are compared by their value, while objects like arrays, dates, and plain objects are compared by their reference.

and since those 2 objects are 2 different objects, they do not have the same referece

Alberto Sinigaglia
  • 12,097
  • 2
  • 20
  • 48
2

There are two things to discuss here -

Infinite Loop

When a state is mentioned in the dependency array of a useEffect, and we try to update the state inside that without any preventing condition, it results in an infinite loop. useEffect --> state update --> useEffect and this goes on.

Shallow Comparison

React's useEffect does a comparison of the dependency array to check if the callback needs to be called.

However, if an object is mentioned in the dependency array, which is renewed every render, even if their properties are unchanged, they will be compared by reference and effect will get called, as in your case.

The problem is addressed by Kent C. Dodds, and he introduced an alternative hook to useEffect - useDeepCompareEffect.

Usage is exactly similar to useEffect, and it would exactly do what it sounds like. It will trigger the callback only after deep comparing the object in dependency array.

For more details - check out this link.

himayan
  • 784
  • 3
  • 9
  • actually, this doesn't work as expected, because now the `tableData` always stays the same even though the fetch would yield a new value, meaning that when the DB updates, the react doesn't reflect that unless I use the original `useEffect` – CIsForCookies Aug 04 '20 at 05:00
  • The root cause of the re-render is this - updating data inside useEffect while having data as part of the dependency array. Can you explain the usecase and why you are using data in the dependency array. There might be an alternative way to achieve this. – himayan Aug 04 '20 at 08:40
0

Probably, because as @Berto99 mentioned, objects are compared by reference. What you can do, is to converto both objects to strings, and then compare them,

JSON.stringify(data) === JSON.stringify(respData); // true

It's not the safest option though, but for simple objects it should do. I let you to decide if it's good enough.

LorDex
  • 2,591
  • 1
  • 20
  • 32