Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

use object in useEffect 2nd param without having to stringify it to JSON

Tags:

In JS two objects are not equals.

const a = {}, b = {}; console.log(a === b); 

So I can't use an object in useEffect (React hooks) as a second parameter since it will always be considered as false (so it will re-render):

function MyComponent() {   // ...   useEffect(() => {     // do something   }, [myObject]) // <- this is the object that can change. } 

Doing this (code above), results in running effect everytime the component re-render, because object is considered not equal each time.

I can "hack" this by passing the object as a JSON stringified value, but it's a bit dirty IMO:

function MyComponent() {   // ...   useEffect(() => {     // do something   }, [JSON.stringify(myObject)]) // <- yuck 

Is there a better way to do this and avoid unwanted calls of the effect?

Side note: the object has nested properties. The effects has to run on every change inside this object.

like image 687
rap-2-h Avatar asked Apr 23 '19 10:04

rap-2-h


1 Answers

You could create a custom hook that keeps track of the previous dependency array in a ref and compares the objects with e.g. Lodash isEqual and only runs the provided function if they are not equal.

Example

const { useState, useEffect, useRef } = React; const { isEqual } = _;  function useDeepEffect(fn, deps) {   const isFirst = useRef(true);   const prevDeps = useRef(deps);    useEffect(() => {     const isFirstEffect = isFirst.current;     const isSame = prevDeps.current.every((obj, index) =>       isEqual(obj, deps[index])     );      isFirst.current = false;     prevDeps.current = deps;      if (isFirstEffect || !isSame) {       return fn();     }   }, deps); }  function App() {   const [state, setState] = useState({ foo: "foo" });    useEffect(() => {     setTimeout(() => setState({ foo: "foo" }), 1000);     setTimeout(() => setState({ foo: "bar" }), 2000);   }, []);    useDeepEffect(() => {     console.log("State changed!");   }, [state]);    return <div>{JSON.stringify(state)}</div>; }  ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script> <script src="https://unpkg.com/react@16/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>  <div id="root"></div>
like image 198
Tholle Avatar answered Sep 24 '22 15:09

Tholle