5

I’m using function components and hooks.

I have a hook that sends REST request to fetch a set of application data which involves multiple params (such as userId, productId etc) from an object in application state.

This hook uses a useEffect() along the lines of:

 useEffect(() => {
   fetch()
   …

}, [objectInState]); // {user: {id: ‘007’}, product: {id: 008}}

If the id of any resource change, I want to send this request again, hence the [objectInState]


The problem came up when I try to set up URL based routing.

When I set initial state based on id’s from URL, the useEffect hook triggers and sends one request to fetch common app data as expected.

The problem is, each component responsible for rendering particular resource in URL params (user component, product component etc) then sends a REST request on their own to fetch the details of it’s resource, and expands the details of resource in state to become something like

// {user: {id: ‘007’, name: ‘John Doe’, email:’’}, product: {id: 008, name: ‘unicorns’, price: ‘1$’}} 

but this changes are triggering the useEffect that is fetching the same application data again multiple times.

I do want to fetch the application data if there is an actual change in the id’s, for example

{user: {id: ‘007’, name: ‘John Doe’, email:’’}…

changes to

{user: {id: ‘008’, name: ‘John Wick’, email:’’}

But not when

{user: {id: ‘007’}… is expanded to {user: {id: ‘007’, name: ‘John Doe’, email:’’} by other components.

When I am expanding a current object in state from a component, if I can tell react to not trigger useEffect’s listening to that param with something like {silent: true} this problem can be avoided.

How can I make the useEffect() not trigger again in such cases? Or is there another way to implement such functionality?

T J
  • 42,762
  • 13
  • 83
  • 138
  • 1
    Does [this](https://stackoverflow.com/a/54608710/2767755) help? – Arup Rakshit Jul 12 '19 at 15:02
  • I think it is better if you create a new UUID based on the `user.id` and `product.id` and make that your useEffect dep – AngelSalazar Jul 12 '19 at 15:04
  • 1
    How do you "expand" an object? Presumable you are creating a completely new object? If you were to retain the same object instance then it wouldn't re-trigger `useEffect` – James Jul 12 '19 at 15:04
  • @James yes I'm creating a new object, like typical state update, the response is too big to set properties one by one... – T J Jul 14 '19 at 11:33
  • @TJ well that's the reason why `useEffect` reruns, it's fairly easy to update multiple properties on an object programmatically without having to be explicit with each property e.g.[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) – James Jul 14 '19 at 12:35
  • @James I have to update the object otherwise the components that actually fetches and expands the resource won't render properly. I just want to ignore a `useEffect` in specific component... – T J Jul 14 '19 at 14:24
  • @TJ well in that case then using the ID in the dependency is the way to go – James Jul 14 '19 at 14:53

2 Answers2

4

From what you described, you should separate the object into different useStates.

Use something like:

const [user, setUser] = useState({});
const [product, setProduct] = useState({});

/* one useEffect to get the user */
useEffect(() => {
  const user = fetchUser(userIdFromUrl);
  setUser(user);
}, [userIdFromUrl]);

/* one useEffect to get the product */
useEffect(() => {
  const product = fetchProduct(productIdFromUrl);
  setProduct(product);
}, [productIdFromUrl]);

You can even later put the useState and useEffect statements in an custom hook at will.

NOTE fetch calls are simplified for example purposes

João Cunha
  • 9,929
  • 4
  • 40
  • 61
  • _"from an object in application state."_ - I don't think the OP is using local state. – James Jul 12 '19 at 16:22
  • @James Still the dependency from the useEffect remains the id from the url. Using useState or another app state from top nodes is irrelevant. And having such an object built into state is bad on its own. – João Cunha Jul 12 '19 at 16:26
  • My point was you are duplicating state management here e.g. if this was a class component, would you store the user data in `this.state` if it was already coming in via `props`? Probably not, your solution would still work _without_ using `useState` in the component i.e. `useEffect(() => ..., [objectInState.user.id])` – James Jul 12 '19 at 16:39
  • @James I assumed initially he had it in the same file. If OP clarifies I will update my answer without refering useState. Because the state doesn't come into play then. If he fetches data based on the id from the url, the dependency is always gonna be said param. – João Cunha Jul 12 '19 at 22:32
0

You can add state like isChangeId

const [isChangeId, setIsChangeId] = useState({user: false, product: false});

and add useEffect like this:

useEffect(() => {
    if (isChangeId.user) {
    // Your code
    setIsChangeId({ ...isChangeId, user: false});
    }
}, [isChangeId, /* other dependency */]);

That is my solution!

Quốc Nhật
  • 31
  • 1
  • 4