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?

7
  • 1
    Does this help? Commented Jul 12, 2019 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 Commented Jul 12, 2019 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 Commented Jul 12, 2019 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... Commented Jul 14, 2019 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 Commented Jul 14, 2019 at 12:35

2 Answers 2

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

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

4 Comments

"from an object in application state." - I don't think the OP is using local state.
@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.
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 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.
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!

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.