admin管理员组

文章数量:1026989

Let's say I have an array like this:

[
  {
    country: '',
    'city/province': '',
    street: ''
  },
  {
    country: '',
    'city/province': '',
    street: ''
  }
]

How do I have the useEffect() hook run every time the value of the 'country' field in any item inside the array changes?

Let's say I have an array like this:

[
  {
    country: '',
    'city/province': '',
    street: ''
  },
  {
    country: '',
    'city/province': '',
    street: ''
  }
]

How do I have the useEffect() hook run every time the value of the 'country' field in any item inside the array changes?

Share Improve this question edited Nov 18, 2021 at 13:37 Ashish Kamble 2,6254 gold badges22 silver badges29 bronze badges asked Nov 18, 2021 at 13:35 user13841165user13841165 3
  • Do you need like this? codesandbox.io/s/react-hooks-useeffect-forked-1ow31 – Maniraj Murugan Commented Nov 18, 2021 at 13:49
  • I think that is close @ManirajMurugan - but wouldn't it trigger useEffect if street changes too? – Chris Commented Nov 18, 2021 at 13:54
  • 2 Interesting problem, personally I would go with trying to explicitly invoke the code than needs to run when you change an address, say from a onChangeAddress function, rather than trying to react to deep state changes using useEffect – andy mccullough Commented Nov 18, 2021 at 14:16
Add a ment  | 

3 Answers 3

Reset to default 1

Just map the countries into the effect dependency array.

const countries = data.map((x) => x.country);

useEffect(() => {
  console.log(countries);
}, countries);

Normally you wouldn't want to do that, but just to answer your question, it can be done, so let me propose the following assuming your list is called items:

  useEffect(() => {
    
  }, [...items.map(v => v.country)])

What the above code does is to spread all items (with its country property) into the useEffect dependency array.

The reason why this can be adhoc is mainly because React doesn't like to have a variable length of dependency. In the source code, when the length changes, it only appreciates the element change from the existing elements. So you might run into problem if you switch from 1 elements to 2 elements.

However if you have fixed number of elements, this should do what you wanted. Keep in mind the items has to be an array at all time.

NOTE: to acmodate the length issue, maybe we can add an additional variable length to the dependency array :)

  }, [items.length, ...items.map(v => v.country)])

As i mentioned, most of time, you should avoid doing this, instead try to change the entire items every time when an item changes. And let the Item display to optimize, such as React.memo.

I don't think you can specifically tackle it in the dependency array, however, you can do your check inside the useEffect to have the same overall oute.

Basically, the dependency array is passed the full data state, which will trigger the effect every change, then you do a further check if the sub property has changed.

I'm leverage lodash for brevity, but you can run any function to determine if the data has changed.

Codepen: https://codepen.io/chrisk7777/pen/mdMvpvo?editors=0010

const { useState, useEffect, useRef } = React;
const { render } = ReactDOM;
const { isEqual, map } = _;

const App = () => {
  const [data, setData] = useState([
    {
      country: "",
      "city/province": "",
      street: ""
    },
    {
      country: "",
      "city/province": "",
      street: ""
    }
  ]);
  const prevData = useRef(data);

  // hacky updates just to demonstrate the change
  // change country - should trigger useEffect
  const update1 = () => {
    setData((s) => [s[0], { ...s[1], country: s[1].country + "a" }]);
  };

  // change street - should not trigger useEffect
  const update2 = () => {
    setData((s) => [s[0], { ...s[1], street: s[1].street + "a" }]);
  };

  useEffect(() => {
    if (!isEqual(map(prevData.current, "country"), map(data, "country"))) {
      console.log("country changed");
    }

    prevData.current = data;
  }, [data]);

  return (
    <div>
      <button onClick={update1}>change country - trigger effect</button>
      <br />
      <button onClick={update2}>change street - do not trigger effect</button>
    </div>
  );
};

render(<App />, document.getElementById("app"));

Let's say I have an array like this:

[
  {
    country: '',
    'city/province': '',
    street: ''
  },
  {
    country: '',
    'city/province': '',
    street: ''
  }
]

How do I have the useEffect() hook run every time the value of the 'country' field in any item inside the array changes?

Let's say I have an array like this:

[
  {
    country: '',
    'city/province': '',
    street: ''
  },
  {
    country: '',
    'city/province': '',
    street: ''
  }
]

How do I have the useEffect() hook run every time the value of the 'country' field in any item inside the array changes?

Share Improve this question edited Nov 18, 2021 at 13:37 Ashish Kamble 2,6254 gold badges22 silver badges29 bronze badges asked Nov 18, 2021 at 13:35 user13841165user13841165 3
  • Do you need like this? codesandbox.io/s/react-hooks-useeffect-forked-1ow31 – Maniraj Murugan Commented Nov 18, 2021 at 13:49
  • I think that is close @ManirajMurugan - but wouldn't it trigger useEffect if street changes too? – Chris Commented Nov 18, 2021 at 13:54
  • 2 Interesting problem, personally I would go with trying to explicitly invoke the code than needs to run when you change an address, say from a onChangeAddress function, rather than trying to react to deep state changes using useEffect – andy mccullough Commented Nov 18, 2021 at 14:16
Add a ment  | 

3 Answers 3

Reset to default 1

Just map the countries into the effect dependency array.

const countries = data.map((x) => x.country);

useEffect(() => {
  console.log(countries);
}, countries);

Normally you wouldn't want to do that, but just to answer your question, it can be done, so let me propose the following assuming your list is called items:

  useEffect(() => {
    
  }, [...items.map(v => v.country)])

What the above code does is to spread all items (with its country property) into the useEffect dependency array.

The reason why this can be adhoc is mainly because React doesn't like to have a variable length of dependency. In the source code, when the length changes, it only appreciates the element change from the existing elements. So you might run into problem if you switch from 1 elements to 2 elements.

However if you have fixed number of elements, this should do what you wanted. Keep in mind the items has to be an array at all time.

NOTE: to acmodate the length issue, maybe we can add an additional variable length to the dependency array :)

  }, [items.length, ...items.map(v => v.country)])

As i mentioned, most of time, you should avoid doing this, instead try to change the entire items every time when an item changes. And let the Item display to optimize, such as React.memo.

I don't think you can specifically tackle it in the dependency array, however, you can do your check inside the useEffect to have the same overall oute.

Basically, the dependency array is passed the full data state, which will trigger the effect every change, then you do a further check if the sub property has changed.

I'm leverage lodash for brevity, but you can run any function to determine if the data has changed.

Codepen: https://codepen.io/chrisk7777/pen/mdMvpvo?editors=0010

const { useState, useEffect, useRef } = React;
const { render } = ReactDOM;
const { isEqual, map } = _;

const App = () => {
  const [data, setData] = useState([
    {
      country: "",
      "city/province": "",
      street: ""
    },
    {
      country: "",
      "city/province": "",
      street: ""
    }
  ]);
  const prevData = useRef(data);

  // hacky updates just to demonstrate the change
  // change country - should trigger useEffect
  const update1 = () => {
    setData((s) => [s[0], { ...s[1], country: s[1].country + "a" }]);
  };

  // change street - should not trigger useEffect
  const update2 = () => {
    setData((s) => [s[0], { ...s[1], street: s[1].street + "a" }]);
  };

  useEffect(() => {
    if (!isEqual(map(prevData.current, "country"), map(data, "country"))) {
      console.log("country changed");
    }

    prevData.current = data;
  }, [data]);

  return (
    <div>
      <button onClick={update1}>change country - trigger effect</button>
      <br />
      <button onClick={update2}>change street - do not trigger effect</button>
    </div>
  );
};

render(<App />, document.getElementById("app"));

本文标签: