just after updating state using setState, if i log state , i am getting empty array even though state is not empty.
I have hook like shown in code below.
const [pets, setPets] = useState([]);
async function requestPets() {
const { animals: animalsProperty } = await pet.animals({
location,
breed,
type: animal
});
console.log(animalsProperty);
setPets(animalsProperty || []);
console.log(pets);
}
Inside requestPets function after setting setPets with animalsProperty, even if animalsProperty is not null, when i log pets i am seeing empty array in console.
may i know why pets is showing empty array instead of value like animalsProperty when i logged pets?
setPets does async update, so if you console.log the state value right after setting it, the previous value will still be shown because it will be updated on subsequent component's render.
Could you use callback see if it works as your function is async
setwww(b=>
{
return b||[];
});
I think pets will be updated in the render function as expected.
After you fire setPets, it won't update the value of pets immediately. In other words, you will get the same value of pets within that render. The following shown the sequence:
===Initial Render: ===
1. Pets is []
2. setPets(somePets) => trigger re-render
3. Pets is []
===Re-render: ===
4. Pets is somePets
Related
I am trying to update a nested state object (checkedObjects) in a react class component, to track when checkboxes are checked and unchecked. checkedObjects has the following structure:
checkedObjects: {
[assignmentName]: boolean,
}
verifyObjects is a local variable that checks if the new name property was actually received. When I console out the contents of these objects however, checkedObjects is empty, while the new property was added to verifyObjects (see screenshot below). Can anyone advise why the state variable checkedObjects is not updating immediately?
Screenshot:
Code Snippet:
this.state = {
checkedObjects: {},
};
incrementCount(totalCount, id, checked, assignmentName) {
console.log("increment: totalCount", totalCount, " ; id:", id, checked);
// If a checkbox is clicked with an assignment name store on the checkedObjects
if (assignmentName) {
let verifyObjects = { ...this.state.checkedObjects };
verifyObjects[assignmentName] = checked;
this.setState(prevState => {
let tempObj = {...prevState.checkedObjects}
tempObj[assignmentName] = checked;
return {checkedObjects: tempObj}
});
console.log("SelectedAssignmentsObj:", this.state.checkedObjects);
console.log("VerifiedObject:", verifyObjects);
} //if
}
State updates don't occur instantaneously. When we call the setState() function, it schedules a state update. Try console logging tempObj to see the value that is being put inside of this.state.checkedObjects.
In short, your code is working the way it should but you wont be able to see the state update right after calling this.setState() [because the state update is scheduled and didnt happen at that instant]. If you want to ensure that your state did update the way you wanted, can add a dummy button on the side that console logs the value of this.state.checkedObjects or you can use the chrome extension React Developer Tools to find out the values in the state object.
So i have a const with an array of objects, named "data", a state that receive that data so I can update the screen when I change it, and I have a function that filter that data.
Every time that the user change the state of an HTML select, I trigger a function named handleChange that calls the filter function, so the user user can see the filtered content.
The problem is that I want to reset the state that receives the data, with the original data before filtering it, so if the user change one of the selects, it filter based on the original data, not the previous changed one, but when I try to update the state with the const data value, it doesn't work.
Here is my code
const [list, setList] = useState<IData[]>([...data]);
const [filter, setFilter] = useState<IFilter>({
name: "",
color: "",
date: "",
});
function handleChange(
key: keyof IData,
event: React.ChangeEvent<{ value: string }>
): void {
const newFilter = { ...filter };
newFilter[key as keyof IData] = event.target.value;
setList([...data]); // reset the data
setFilter({ ...newFilter }); // set the filter value
filterList();
}
function filterList(): void {
const keys = Object.keys(filter) as Array<keyof IData>;
keys.forEach((key: keyof IData) => {
if (filter[key]) {
const newList = list.filter((item: IData) => item[key] === filter[key]);
setList([...newList]);
}
});
}
the problem is here
setList([...data]); // reset the data
setFilter({ ...newFilter }); // set the filter value
filterList();
apparently, when the filterList happens, the list state is not yet restarted with the data value, since if I console log it inside filterList(), it return me only the list that was previous filtered. Is there a way for me to make sure that the setList happens before filtering, so I'm sure that the list that is filtered is always based on the initial value?
You can access the updated values inside useEffect (more about useEffect), so instead of calling filterList() directly inside the handler you move it inside the hook:
React.useEffect(()=>{filterList();},[list,filter])
Alternatively, you can pass the values as parameters to the function, after you updated the state with them
filterList(newList, newFilter)
UPDATE
As you update list inside filterList you'll need some sort of a flag to indicate if you need to filter of not (I'd use useRef). Note that it would be preferable to pass parameters into filterList instead, because this way there will be one less rendering cycle. Here is a simplified example of how both can work, let me know if they make sense : https://jsfiddle.net/6xoeqkr3/
So I have a question regarding useEffect dependenices
This is from the react docs:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
What does this mean exactly, does React keep track of the count variable and its value, and reacts when the value changes, or does React keep track of the first element in the array and its value.
What do I mean by this? Let me explain more. So if we have something like this [name] as dependencies. At the moment of evaluation, the array might result with ['Bob'] or ['Steve']. Clearly this is a change and the useEffect will rerender the component. But how does it check it?
Does it keep track of name or does it keep track of dependencyArray[0]. If we take a look in the previous example, both of these would result to true, name and the first element both changed their values from 'Bob' to 'Steve'. But how does it actually work?
Currently in my code I am using something like this [employees[selectedEmployee].name], where selectedEmployee is something clickable on the UI and it becomes 'Bob' or 'Steve'
ex:
const employees = {
Bob: {
name: 'Bob'
},
Steve: {
name: 'Steve'
}
}
This means that in the end, when evaluated, the dependency array will still result with ['Bob'] --> ['Steve'], and if React is evaluating the dependencyArray[0] then that has clearly changed and component should rerender, but If it keeps track of the reference, then I am changing the reference altogether and it may cause problems.
So what's the correct approach? Can I use dynamic properties like employees[selectedEmployee].name as a dependency?
count is a value, not a reference.
It's just good old Javascript, nothing fancy:
const myArray = [ count ]; // new array containing the value of variable 'count'
const myFunction = () => {
document.title = `You clicked ${count} times`;
}
useEffect(
myFunction,
myArray
);
// Means actually:
// "Run this function if any value in the array
// is different to what it was last time this useEffect() was called"
does React keep track of the ... value, or ... the reference ?
React doesn't really 'keep track' of any of them. It only checks the difference to a previous call, and forgets about everything else.
Can I use dynamic properties as a dependency?
Yes, you can (because they are not as 'dynamic' as you think).
So what's the correct approach?
Better think less of any react-magic going on, but
understand that the component is a function, and believe React calls it when necessary and
think about the variables (properties and state) used inside it, from a plain Javascript perspective.
Then your 'dynamic properties' become 'constant variables during one function call'. No matter which variables change dynamically and how, it will always be one value last time and one value now.
Explaination:
The important 'trick' here is, that the component is just a javascript function, that is called like 'whenever anything might have changed', and consequently useEffect() is also called (as useEffect() is just a function call inside the component).
Only the callback function passed to useEffect is not always called.
useEffect does not render the component, useEffect is called when the component is called, and then just calls the function given to it, or not, depending on if any value in the dependencies array is different to what it was last time useEffect() was called.
React might rerender the component if in the function given to useEffect there are any changes made to the state or something (anything that makes React to think it has to rerender), but that's as a result of this state change, where ever it came from, not because of the useEffect call.
Example:
const MyComponent = (props) => {
// I'm assigning many const here to show we are dealing with local constants.
// Usually you would use this form (using array destructuring):
// const [ selectedEmployee, setSelectedEmployee ] = useState( someInitialValue );
const myStateValueAndSetter = useState( 'Bob' );
const selectedEmployee = myStateValueAndSetter[0];
const setSelectedEmployee = myStateValueAndSetter[1];
const employees = {
Bob: { name: 'Bob' },
Steve: { name: 'Steve' }
};
const currentName = employees[ selectedEmployee ].name;
useEffect(() => {
document.title = 'current name: ' + currentName;
}, [ currentName ]);
return <MyClickableComponent onClick={( newValue ) => {
setSelectedEmployee( newValue )
}}>;
};
click on MyClickableComponent calls the current setSelectedEmployee( newValue ) function.
(The constant selectedEmployee is not changed!)
MyComponent() is called again.
(This is a new function call. All the constants are gone! Only React stores some state in the background.)
useState() is called, the result is stored in a new constant selectedEmployee.
useEffect() is called, and decides if its callback should be called, depending on the previous and the current value of selectedEmployee.
If the callback is not called and nothing else is changed, you might not notice that anything has happened at all.
<MyClickableComponent ... /> is rendered.
I'm passing some params from a page to the other to make an API call. What i've notice is when i hard code the value i'm suppose to get into the http call, i get the desired results but when i inject the value from the params into the call, if fails to work. so i thought the params wasn't able to pass till i did an alert in the render function and what i realized was the alert prompts twice, the first one is empty and the second prompt brings the value from the previous page, so then meaning in my componentDidMount, the call
state = {
modalVisible: false,
posts : [],
itemID: ''
}
componentDidMount = () => {
const { navigation } = this.props;
this.setState({itemID : navigation.getParam('itemID')})
api.get('/items/'+`${this.state.itemID}`+'/test_items_details/'+`${userID}`+'/posts').then((response) => {
let data = response.data.data
this.setState({posts: data})
console.log(JSON.stringify(this.state.posts))
})
}
As per the docs, it states
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
Your code snippet is trying to access the state variable before it has in fact been updated.
You can do two things here:
Simply use navigation.getParam('itemID') so now your URL becomes /items/${navigation.getParam('itemID')}/test_item_details/${userID}/posts.
Use the setState callback, the snippet is added below.
Snippet:
this.setState({ itemID: navigation.getParam('itemID') }, () => {
// this.state.itemID is now set, you can make your API call.
});
If your only intention to use this.state.itemID is for the URL, I would opt for the first option.
How to bring the pushed array data outside the function,Every thing is
working fine in click function.But when Im trying to print this outside of
the function console.log(this.state.selectedSchedule[0].storeName) , It is
throwing the error:
Cannot read property 'storeName' of undefined.
Code:
this.state = {
selectedSchedule:[]
};
click(e){
console.log('e',e);
document.getElementById("detailView").style.display="block";
this.state.selectedSchedule=[];
for(let i=0;i<this.scheduless.length;i++){
if(this.scheduless[i].scheduleId === e){
this.state.selectedSchedule.push(this.scheduless[i]);
break;
}
}
console.log('cheudkl',this.state.selectedSchedule);
this.setState=({
selectedSchedule:this.state.selectedSchedule
});
console.log(this.state.selectedSchedule[0]);
console.log(this.state.selectedSchedule[0].storeName) //printing
};
//html render
render(){
console.log(this.state.selectedSchedule[0].storeName) // here it's throwing me the error(not printing)
}
Reason is, initial value of this.state.selectedSchedule is [] and during initial rendering you are trying to get the storeName of 0th items, and clear at that time 0th time doesn't exist so
this.state.selectedSchedule[0] ===> undefined
And you are trying to get the value from undefined that's why it is throwing the error. Simply put the check on length before printing the values.
Like this:
render(){
// now it will print the values once some item will be added
if(this.state.selectedSchedule.length)
console.log(this.state.selectedSchedule[0].storeName);
return (....)
}
Another issue is in the way you are updating the state value.
Changes:
1- Never mutate the state value directly, Treat this.state as if it were immutable.
2- setState is a function so we need to call it this.setState({}).
write it like this:
click(e){
document.getElementById("detailView").style.display="block";
let arr = [];
arr = this.scheduless.filter(el => el.scheduleId === e);
this.setState({
selectedSchedule: arr
});
};
You should not mutate the state, push is mutating the array, and you are re assigning the state object in the click function this.state = ....
Instead, use the builtin setState method of react to update it:
this.setState({key: value});
As for arrays you can add items with the ES6 spread operator without mutating it:
const nextState = [...this.state.selectedSchedule, newValue];
this.setState({selectedSchedule: nextState });
EDIT
As Mayank Shukla mentioned, in addition to this solution you should check to see if the length of your array is bigger then 0:
this.state.selectedSchedule.length > 0 && console.log(this.state.selectedSchedule[0].storeName);
In the first render call this array is empty hence its length is 0.
This is not correct way of updating the state
this.setState=({
selectedSchedule:this.state.selectedSchedule
});
You need to edit this as :
if(this.scheduless[i].scheduleId === e){
this.setState({selectedSchedule:[...this.state.selectedSchedule, this.scheduless[i]]});
break;
}
You need to set the state only then the render function will be called again and your changes to the state variable will be visible.
You should never mutate the state (changing the contents of this.state) directly.
That is, instead of assigning an empty array to part of the state and then push every item, create a new array:
const selectedSchedule = this.scheduless.filter(schedule => schedule.scheduleId === e)
this.setState({selectedSchedule});