React - Array are passed as their length - arrays

I'm new to react and i'm trying to use hooks for my project.
I need to pass an array from parent to child, just that when I put the array in the props it just pass its length.
ParentComponent.js
export default function NewHome() {
const [isReady, setReadyState] = useState(false);
const [regionWithValue, setRegionWithValue] = useState([]);
function getRegion() {
fetch("http://localhost:3000/region")
.then(res => res.json())
.then(
(result) => {
result.forEach((el) => {
setRegionWithValue(regionWithValue.push(el));
})
setReadyState(true);
},
(error) => {
setErrore(true);
}
);
};
useEffect(() => {
getRegion();
console.log(regionWithValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
},[]);
if (ReadyState) {
console.log(regionWithValue);
return(
<ChildComponent region={regionWithValue}/>
)
}
The console.log in useEffect() actually print the correct array with data fetched, but the second console.log right before the return, just print out the array size, so I can't pass it to the ChildComponent. I don't know if is cause lifecycle so I'm getting all wrong. Thank you.

This is the problem:
setRegionWithValue(regionWithValue.push(el));
push returns the length and not the array you pushed to
You can do it like this:
setRegionWithValue(v => ([...v, el]));
See: Why does Array.prototype.push return the new length instead of something more useful?
Stating from Array.prototype.push() MDN docs:
The push() method adds one or more elements to the end of an array and returns the new length of the array.

You cant use push on a React Hook. You need to get the previous state and add to that state (prevState is the industry standard naming convention) . This can be accomplished in your fetch like this:
setRegionWithValue(prevState => (
setRegionWithValue([...prevState, result])
));
This will always add to the state, if you want to just create a new state everytime you fetch then you should just use:
setRegionWithValue(result)

Related

React state not updated correctly when using useState hook

I have this example code. I have a state urls which I create using useState hook. I have initialized urls state to empty array. I have another array arr. For each item in arr, I am pushing that item to urls state. When I render contents of urls, only last item pushed is displayed. It seems while updating urls, only last update is taking effect. What could be wrong?
function Hi() {
const [urls, setUrls] = useState([]);
let arr = ["hi", "hello"];
React.useEffect(() => {
arr.forEach(item => {
let url_list = [...urls];
url_list.push(item);
setUrls(url_list)
})
}, []);
return (
<div>
{urls.map(item => (
<Text>{item}</Text>
))}
</div>
)
}
You're updating the state in each interaction of the array.
The problem here is that setState is asynchronous (read), i.e the update doesn't happen instantly. In other words, when you do let url_list = [...urls], on the second and last iteraction, urls is still [], so that's why you're only getting "hello" into it.
You have 2 main approachs in this case:
1. Update the state after you've mapped the entire array.
React.useEffect(() => {
let url_list = [...urls]
arr.forEach(item => {
url_list.push(item);
})
setUrls(url_list)
}, []);
2. Since setState returns the previous state (read), you can do something like this:
React.useEffect(() => {
arr.forEach(item => {
let url_list = [...urls];
url_list.push(item);
setUrls(prevUrls => ([...prevUrls, ...url_list]))
})
}, []);
You are defining url_list inside forEach. This is reset the values inside url_list on each iterations. Declare url_list outside of forEach and it should be working

React: Axios.get -> setState(response) -> use state data immediately in another function - how?

I have made a functional component, instead of class (too late to change, I've written almost 1000 lines already), and I have a big problem with using data in a function, because the state is not updated when the function is called, so I'm getting an empty array... I'm not sure what can be done here, so that the function doStuffWithIt() is able to use updated states that are not empty.
So basically this is how I have it setup:
const [objects, setObjects] = useState([])
const [processedObjects, setProcessedObjects] = useState([])
async function loadData() {
await axios.all([
axios.get("/api/objects"),
...etc //multiple other axios.get calls
]).then(axios.spread((...responses) => {
setObjects(responses[0].data)
...etc //multiple setStates
})).catch(errors => {
console.log(errors)
}
useEffect(() => {
loadData()
doStuffWithIt()
}, [])
function doStuffWithIt() {
console.log(objects) // <-- returns empty array []
//process the array with .map function and push the changes into processedObjects with setProcessedObjects,
//so I can use processed objects as options inside a react-select component
}
return (
<Select options={processedObjects} />
)
Note: I will have multiple components like this, that will need to have the fetched data to be processed and set in another state hook.
Just put the code of that function inside effect with needed dependency.
useEffect(()=>{
console.log(objects) // <-- returns empty array []
//process the array with .map function and push the changes into processedObjects with setProcessedObjects,
//so I can use processed objects as options inside a react-select component
}, [objects])

React useState, setState in useEffect not updating array

I've seen this problem on SO, but I cannot seem to work out why it exists.
I am following the tutorial from here
I am using useState but when I try to update the state, the array is empty. I am using state to create initially an empty array. on message received, I am trying to add the message to the array, using the spread operator, which I have used countless times to add an object to an array (but never inside useEffect).
If I uncomment the commented lines, "chat" gets updated as it should do, but I cannot understand why the spread operator is not working and I need to use a useRef to make this work. I don't want to have loads of useRef for every corresponding useState (at least not knowing why it is necessary)
Can anyone see what I am doing wrong, or explain why I need the useRef?
const [chat, setChat ] = useState([]);
//const latestChat = useRef(null);
//latestChat.current = chat;
// ...
useEffect(() => {
if (connection) {
connection.start()
.then(result => {
console.log('Connected!');
connection.on('ReceiveMessage', message => {
const newMsg = {
user: message.user,
message:message.message
}
setChat([...chat, newMsg]); // issue with this line. chat is always empty
//const updatedChat = [...latestChat.current];
//updatedChat.push(message);
//setChat(updatedChat);
});
})
.catch(e => console.log('Connection failed: ', e));
}
}, [connection]);
you have two option here
add chat state to useEffect dependency array so it knows it depends on chat.
useEffect(() => {
if (connection) {
//...
setChat([...chat, newMsg]); // issue with this line. chat is always empty
//...
}
}, [connection, chat]);
use setState callback to update chat so you won't get stale data
useEffect(() => {
if (connection) {
//...
setChat((ch) => [...ch, newMsg]); // issue with this line. chat is always empty
//...
}
}, [connection]);
which the second way is more appropriate.

Fetching data in useEffect with an array as dependency should only be called on new elements

I have an array, which is given as prop into my component named Child. Now, every time a new item is added to the array a fetch against an API should be made.
This array is held in a component named Parent using the useState hook. Whenever I want to add a new item, I have to recreate the array, since I'm not allowed to mutate it directly.
I tried to simplify my use case in the following code snippet:
const Parent = () => {
const [array, setArray] = useState([]);
///...anywhere
setArray(a => [...a, newItem]);
return <Child array={array} />;
}
const Child = ({ array }) => {
useEffect(() => {
array.forEach(element => {
fetch(...);
});
}, [array]);
return ...;
}
Now, my question is: How can I achieve to fetch new data from my API only for the new element but not for the whole array again?
I hope I described my issue good enough. If anything is unclear or missing, let me know.
How about instead fetching the API data in Parent and just passing the end result to Child? That refactoring would provide some benefits:
Parent owns the items array state and knows when and where a new item is added. That makes an incremental fetch very easy. You also get division of container and presentational components for free.
The fetched API data is related to the items array. You probably want to use them together in some way (save api data as state, combine them, etc.). This constellation would promote derived state, which is a more error prone pattern.
Something like following example could already do what you want - add an item via onClick (or somehow else), fetch its data and pass the whole array down to Child:
const Parent = () => {
const [array, setArray] = useState([]);
return (
<div onClick={addItem}>
<Child array={array} />;
</div>
);
function addItem(e) {
const item = createItemSomehow(...)
fetch(...).then(data => setArray([...array, { item, data }]));
}
};
Update:
If you want to keep your structure and API as is, an alternative would be to memoize the previous arrays prop in your Child with a usePrevious hook and look for item changes.
const Child = ({ array }) => {
const prevArray = usePrevious(array);
useEffect(() => {
if (array !== prevArray && array.length) {
//fetch(...)
console.log(`fetch data for index ${array.length - 1}`);
}
});
return (...);
};
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Codesandbox
You could, for example, keep a list of previously fetched items.
const Child = ({ values }) => {
const [fetched, setFetched] = useState([]);
useEffect(() => {
values.forEach(v => {
if (!fetched.includes(v)) {
setFetched(fetched => [...fetched, v]);
fetch(v);
}
});
}, [values, logged]);
https://codesandbox.io/s/affectionate-colden-sfpff

Infinite loop in useEffect

I've been playing around with the new hook system in React 16.7-alpha and get stuck in an infinite loop in useEffect when the state I'm handling is an object or array.
First, I use useState and initiate it with an empty object like this:
const [obj, setObj] = useState({});
Then, in useEffect, I use setObj to set it to an empty object again. As a second argument I'm passing [obj], hoping that it wont update if the content of the object hasn't changed. But it keeps updating. I guess because no matter the content, these are always different objects making React thinking it keep changing?
useEffect(() => {
setIngredients({});
}, [ingredients]);
The same is true with arrays, but as a primitive it wont get stuck in a loop, as expected.
Using these new hooks, how should I handle objects and array when checking weather the content has changed or not?
Passing an empty array as the second argument to useEffect makes it only run on mount and unmount, thus stopping any infinite loops.
useEffect(() => {
setIngredients({});
}, []);
This was clarified to me in the blog post on React hooks at https://www.robinwieruch.de/react-hooks/
Had the same problem. I don't know why they not mention this in docs. Just want to add a little to Tobias Haugen answer.
To run in every component/parent rerender you need to use:
useEffect(() => {
// don't know where it can be used :/
})
To run anything only one time after component mount(will be rendered once) you need to use:
useEffect(() => {
// do anything only one time if you pass empty array []
// keep in mind, that component will be rendered one time (with default values) before we get here
}, [] )
To run anything one time on component mount and on data/data2 change:
const [data, setData] = useState(false)
const [data2, setData2] = useState('default value for first render')
useEffect(() => {
// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
}, [data, data2] )
How i use it most of the time:
export default function Book({id}) {
const [book, bookSet] = useState(false)
const loadBookFromServer = useCallback(async () => {
let response = await fetch('api/book/' + id)
response = await response.json()
bookSet(response)
}, [id]) // every time id changed, new book will be loaded
useEffect(() => {
loadBookFromServer()
}, [loadBookFromServer]) // useEffect will run once and when id changes
if (!book) return false //first render, when useEffect did't triggered yet we will return false
return <div>{JSON.stringify(book)}</div>
}
I ran into the same problem too once and I fixed it by making sure I pass primitive values in the second argument [].
If you pass an object, React will store only the reference to the object and run the effect when the reference changes, which is usually every singe time (I don't now how though).
The solution is to pass the values in the object. You can try,
const obj = { keyA: 'a', keyB: 'b' }
useEffect(() => {
// do something
}, [Object.values(obj)]);
or
const obj = { keyA: 'a', keyB: 'b' }
useEffect(() => {
// do something
}, [obj.keyA, obj.keyB]);
If you are building a custom hook, you can sometimes cause an infinite loop with default as follows
function useMyBadHook(values = {}) {
useEffect(()=> {
/* This runs every render, if values is undefined */
},
[values]
)
}
The fix is to use the same object instead of creating a new one on every function call:
const defaultValues = {};
function useMyBadHook(values = defaultValues) {
useEffect(()=> {
/* This runs on first call and when values change */
},
[values]
)
}
If you are encountering this in your component code the loop may get fixed if you use defaultProps instead of ES6 default values
function MyComponent({values}) {
useEffect(()=> {
/* do stuff*/
},[values]
)
return null; /* stuff */
}
MyComponent.defaultProps = {
values = {}
}
Your infinite loop is due to circularity
useEffect(() => {
setIngredients({});
}, [ingredients]);
setIngredients({}); will change the value of ingredients(will return a new reference each time), which will run setIngredients({}). To solve this you can use either approach:
Pass a different second argument to useEffect
const timeToChangeIngrediants = .....
useEffect(() => {
setIngredients({});
}, [timeToChangeIngrediants ]);
setIngrediants will run when timeToChangeIngrediants has changed.
I'm not sure what use case justifies change ingrediants once it has been changed. But if it is the case, you pass Object.values(ingrediants) as a second argument to useEffect.
useEffect(() => {
setIngredients({});
}, Object.values(ingrediants));
As said in the documentation (https://reactjs.org/docs/hooks-effect.html), the useEffect hook is meant to be used when you want some code to be executed after every render. From the docs:
Does useEffect run after every render? Yes!
If you want to customize this, you can follow the instructions that appear later in the same page (https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects). Basically, the useEffect method accepts a second argument, that React will examine to determine if the effect has to be triggered again or not.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
You can pass any object as the second argument. If this object remains unchanged, your effect will only be triggered after the first mount. If the object changes, the effect will be triggered again.
I'm not sure if this will work for you but you could try adding .length like this:
useEffect(() => {
// fetch from server and set as obj
}, [obj.length]);
In my case (I was fetching an array!) it fetched data on mount, then again only on change and it didn't go into a loop.
If you include empty array at the end of useEffect:
useEffect(()=>{
setText(text);
},[])
It would run once.
If you include also parameter on array:
useEffect(()=>{
setText(text);
},[text])
It would run whenever text parameter change.
I often run into an infinite re-render when having a complex object as state and updating it from useRef:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients({
...ingredients,
newIngedient: { ... }
});
}, [ingredients]);
In this case eslint(react-hooks/exhaustive-deps) forces me (correctly) to add ingredients to the dependency array. However, this results in an infinite re-render. Unlike what some say in this thread, this is correct, and you can't get away with putting ingredients.someKey or ingredients.length into the dependency array.
The solution is that setters provide the old value that you can refer to. You should use this, rather than referring to ingredients directly:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients(oldIngedients => {
return {
...oldIngedients,
newIngedient: { ... }
}
});
}, []);
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect.
I believe they are trying to express the possibility that one could be using stale data, and to be aware of this. It doesn't matter the type of values we send in the array for the second argument as long as we know that if any of those values change it will execute the effect. If we are using ingredients as part of the computation within the effect, we should include it in the array.
const [ingredients, setIngredients] = useState({});
// This will be an infinite loop, because by shallow comparison ingredients !== {}
useEffect(() => {
setIngredients({});
}, [ingredients]);
// If we need to update ingredients then we need to manually confirm
// that it is actually different by deep comparison.
useEffect(() => {
if (is(<similar_object>, ingredients) {
return;
}
setIngredients(<similar_object>);
}, [ingredients]);
The main problem is that useEffect compares the incoming value with the current value shallowly. This means that these two values compared using '===' comparison which only checks for object references and although array and object values are the same it treats them to be two different objects. I recommend you to check out my article about useEffect as a lifecycle methods.
The best way is to compare previous value with current value by using usePrevious() and _.isEqual() from Lodash.
Import isEqual and useRef. Compare your previous value with current value inside the useEffect(). If they are same do nothing else update. usePrevious(value) is a custom hook which create a ref with useRef().
Below is snippet of my code. I was facing problem of infinite loop with updating data using firebase hook
import React, { useState, useEffect, useRef } from 'react'
import 'firebase/database'
import { Redirect } from 'react-router-dom'
import { isEqual } from 'lodash'
import {
useUserStatistics
} from '../../hooks/firebase-hooks'
export function TMDPage({ match, history, location }) {
const usePrevious = value => {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
const userId = match.params ? match.params.id : ''
const teamId = location.state ? location.state.teamId : ''
const [userStatistics] = useUserStatistics(userId, teamId)
const previousUserStatistics = usePrevious(userStatistics)
useEffect(() => {
if (
!isEqual(userStatistics, previousUserStatistics)
) {
doSomething()
}
})
In case you DO need to compare the object and when it is updated here is a deepCompare hook for comparison. The accepted answer surely does not address that. Having an [] array is suitable if you need the effect to run only once when mounted.
Also, other voted answers only address a check for primitive types by doing obj.value or something similar to first get to the level where it is not nested. This may not be the best case for deeply nested objects.
So here is one that will work in all cases.
import { DependencyList } from "react";
const useDeepCompare = (
value: DependencyList | undefined
): DependencyList | undefined => {
const ref = useRef<DependencyList | undefined>();
if (!isEqual(ref.current, value)) {
ref.current = value;
}
return ref.current;
};
You can use the same in useEffect hook
React.useEffect(() => {
setState(state);
}, useDeepCompare([state]));
You could also destructure the object in the dependency array, meaning the state would only update when certain parts of the object updated.
For the sake of this example, let's say the ingredients contained carrots, we could pass that to the dependency, and only if carrots changed, would the state update.
You could then take this further and only update the number of carrots at certain points, thus controlling when the state would update and avoiding an infinite loop.
useEffect(() => {
setIngredients({});
}, [ingredients.carrots]);
An example of when something like this could be used is when a user logs into a website. When they log in, we could destructure the user object to extract their cookie and permission role, and update the state of the app accordingly.
my Case was special on encountering an infinite loop, the senario was like this:
I had an Object, lets say objX that comes from props and i was destructuring it in props like:
const { something: { somePropery } } = ObjX
and i used the somePropery as a dependency to my useEffect like:
useEffect(() => {
// ...
}, [somePropery])
and it caused me an infinite loop, i tried to handle this by passing the whole something as a dependency and it worked properly.
Another worked solution that I used for arrays state is:
useEffect(() => {
setIngredients(ingredients.length ? ingredients : null);
}, [ingredients]);

Resources