ReactJs UseState : insert element into array not updating - reactjs

I am trying to use React Hooks but somehow my state is not updating. When I click on the checkbox (see in the example), I want the index of the latter to be added to the array selectedItems, and vice versa
My function looks like this:
const [selectedItems, setSelectedItems] = useState([]);
const handleSelectMultiple = index => {
if (selectedItems.includes(index)) {
setSelectedItems(selectedItems.filter(id => id !== index));
} else {
setSelectedItems(selectedItems => [...selectedItems, index]);
}
console.log("selectedItems", selectedItems, "index", index);
};
You can find the console.log result
here
An empty array in the result, can someone explain to me where I missed something ?

Because useState is asynchronous - you wont see an immediate update after calling it.
Try adding a useEffect which uses a dependency array to check when values have been updated.
useEffect(() => {
console.log(selectedItems);
}, [selectedItems])

Actually there isn't a problem with your code. It's just that when you log selectedItems the state isn't updated yet.
If you need selectedItems exactly after you update the state in your function you can do as follow:
const handleSelectMultiple = index => {
let newSelectedItems;
if (selectedItems.includes(index)) {
newSelectedItems = selectedItems.filter(id => id !== index);
} else {
newSelectedItems = [...selectedItems, index];
}
setSelectedItems(newSelectedItems);
console.log("selectedItems", newSelectedItems, "index", index);
};

Related

React Native add to state array if value does not exist

I would like to know the best way to add a value to an array of the value does not exist in my state variable. The current code works but I a newbie and not sure if I am doing this correctly. This function is fired when a button is pressed
const App = () => {
const [answered, setAnswered] = useState([])
const handleConnectionAnswer = deviceId => {
if (!anwsered.includes(deviceId)) {
setAnswered(prev => [...prev, deviceId]);
}
};
}
you're doing it right or you can simply do it like
if (!anwsered.includes(deviceId)) {
setAnswered([...answered, deviceId]);
}

Only rerender and fetch when api property has changed

Im trying to get the use effect to rerender only when the labels property has changed.
i want to fetch newest changes in the labels property only when there is a change.
My code below:
const usePrevious = (value: any) => {
const ref = useRef()
useEffect(() => {
ref.current = value
}, value)
return ref.current
}
const prevLabels = usePrevious(labels)
useEffect(() => {
if (labels !== prevLabels) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
const fetchUpdatedLabels = async () => {
await axios
.get(`/events/${printEvent.id}`)
.then((response) => {
const newLabels = response.data.labels
// filter response to empty array if there are no splits left
// if is null return empty string
let fetchedLabels =
newLabels !== null
? newLabels
.toString()
.split(',')
.filter((label: string) => {
return label !== ''
}) || ''
: ''
getLabels(fetchedLabels)
print.updateEvent(printEvent.id, 'value', printEvent.value)
})
.catch((err) => {
console.error(err, 'cant fetch labels from api')
})
}
it keeps refetching nonstop, how do i achieve this?
This is most likely in part because you are comparing an array with an array using the equivalence operator !==. It can be surprising to people new to JS but if you do this in a JS console:
[1,2,3] === [1,2,3]
It returns false. The reason is that an array is an object and what you are asking is "is the array on the left literally the same as the one on the right" -- as in the same variable. Whereas these are 2 separate array instances that happen to have the same contents. You might wonder why it works then on strings and numbers etc, but that's because those are primitive values and not objects which are instantiated. It's a weird JS gotcha.
There are several ways to compare an array. Try this:
useEffect(() => {
if (labels.sort().join(',') !== prevLabels.sort().join(',')) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
I'm not sure about your use case here though as when the labels are different, you fetch them, but the only way they can change is to be fetched? Is there something else in the code that changes the labels? If so, won't they get wiped out by the ones from the server now as soon as they change?
Something else is wrong here I think. Where do you set labels into state?

Why doesn't a useEffect hook trigger with an object in the dependency array?

I'm noticing something really strange while working with hooks, I've got the following:
import React, { useState, useEffect } from "react";
const [dependency1, setDependency1] = useState({});
const [dependency2, setDependency2] = useState([]);
useEffect(() => {
console.log("dependency 1 got an update");
}, [dependency1]);
useEffect(() => {
console.log("dependency 2 got an update");
}, [dependency2]);
setInterval(() => {
setDependency1(prevDep1 => {
const _key = "test_" + Math.random().toString();
if (prevDep1[_key] === undefined) prevDep1[_key] = [];
else prevDep1[key].push("foo");
return prevDep1;
})
setDependency2(prevDep2 => [...prevDep2, Math.random()]);
}, 1000);
for some reason only the useEffect with dependency2 (the array where items get added) triggers, the one with dependency1 (the object where keys get added) doesn't trigger..
Why is this happening, and how can I make it work?
setInterval(() => {
setDependency1(prevDep1 => {
const _key = "test_" + Math.random().toString();
return {...prevDep1, [_key]: [...(prevDep1[_key] || []), 'foo'] }
})
setDependency2(prevDep2 => [...prevDep2, Math.random()]);
}, 1000);
State should be updated in an immutable way.
React will only check for reference equality when deciding a dependency changed, so if the old and new values pass a === check, it considers it unchanged.
In your first dependency you simply added a key to the existing object, thus not changing the actual object. The second dependency actually gets replaced altogether when spreading the old values into a new array.
You're returning an assignment statement here:
setDependency1(prevDep1 => prevDep1["test_" + Math.random().toString()] = ["foo"]);
You should return an object. Maybe something like:
setDependency1(prevDep1 => ({ ...prevDep1, ["test_" + Math.random().toString()]: ["foo"] }));

How I add an object to an existing array? React

recently I started to work in a proyect and I notice that I need some way to modify a value without losing my previous state. Basically work with an array. However I don't know how to do that.
Can anyone help me by telling me how can I modify an state (I'm using react hooks by the way...) to add more values to the previous state in an array?
Sorry not posting code or something like that, I don't even know how to write that.
Thank you in advance.
Use ES6 spread operator to push new object to the existing array.
Adding a new object to an Array:
const [todo, setTodo] = useState([{ task: "Todo 1" }]);
const addTodo = () => {
let newTodoTask = { task: `Task ${todo.length + 1}` };
setTodo(tasks => [...tasks, { ...newTodoTask }]);
};
Modifying an object in an Array:
const editTask = (e, taskId = 0) => {
setTodo(tasks =>
tasks.map((task, idx) =>
idx === taskId ? { task: "Edited Todo 1" } : { ...task }
)
);
};
Deleting an object from an array
const deleteTask = (e, taskId = 0) => {
setTodo(tasks => tasks.filter((task, idx) => idx !== taskId));
};
Find the simple working example here.
https://codesandbox.io/s/unruffled-elgamal-h7fhg?file=/src/App.js:482-593
First, Learn basic ES6 before start working on the project.
You can use the previous value returned from the setState to update the existing Array
import { useState } from 'react';
export default function Test() {
const [state, setstate] = useState([1,2,3,4);
return <div onClick={() => setstate((prev) => [...prev,99])}>Heyy{state}</div>;
}

What is proper way to call function from onClick so it don't trigger wrong one?

When I click on button then onClick triggers correct function, run half through and jumps to other function which is not related to it and run through half of it and jumps back to first function, runs half trough again and drops error
Uncaught TypeError: _this.state.searchValue.toLowerCase is not a function
Interesting part is that I click other button before which triggers this function with toLowerCase() and there is no errors.
I dont have any idea whats going on here but so far i was trying to remove few lines to see which line cause it because I dont think that line with toLowerCase() realy is the reason. Everything works when I remove lines where is first this.setState.
Here is my function:
( Alerts is used to track where function is at, that how i know
that it run half through only. It never reach alert("DDD").
This function is which is triggered with button onClick like it should be )
onSelect = (e) => {
const data = e.target.getAttribute('data-id');
const itemId = e.target.getAttribute('data-id');
const itemIdState = !this.state[e.target.getAttribute('data-id')];
alert("AAA")
this.setState(state => { // <--- Somehow problem comes from this setState function
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
alert("CCC")
this.setState(State => ({
[itemId]: itemIdState,
}), function() {
alert("DDD")
if(this.state[itemId] === true){
this.setState({isAnySelected: true})
}else if(this.state[itemId] === false){
this.setState({isAnySelected: false})
}
})
}
This is other function which is triggered by mistake and is not related to other. It is just returning component which is displayed and when I press on its button then i have this issue.
filterSearch = (id, title, path) => {
let name = title.toLowerCase()
let filter = this.state.searchValue.toLowerCase()
if(name.includes(filter)){
return <SearchResult key={id} data-id={id} pName={path} onClick={this.onSelect} selected={this.state[id]} />
}
}
And here is from where filterSearch is triggered. Behind this.props.searchResult is Redux.
{this.props.searchResult ? this.props.searchResult.map(category =>
this.filterSearch(category.id, category.title, category.path)
) : null
}
I think I see what the problem is: in your problematic this.setState, you cast everything in your state to a boolean:
this.setState(state => {
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
Your for() statement ends up comparing searchValue to data (some kind of ID), which I imagine more often than not will not be the case, so searchValue ends up getting set to false.
And what happens when you try to do .toLowerCase() on a Boolean?
To fix this, consider structuring your state like this:
this.state = {
searchValue: '',
ids: {},
};
Then, replace your problematic this.setState with something like this:
this.setState((state) => {
const newIDs = {
// Create a clone of your current IDs
...state.ids,
};
Object.keys(newIDs).forEach(key => {
newIDs[key] = key === data
});
alert("BBB")
return {
// searchValue will remain untouched
...state,
// Only update your IDs
ids: newIDs,
}
});
What exactly are you wanting to do here?
this.setState(state => {
const newState = {}; // You are initializing an object
for (const dataId in state) {
newState[dataId] = dataId === data // You are putting in an array every property of state that is equal to data
}
return newState
});
So irrevocably, your this.state.searchValue property will be changed to something else, which is of boolean type. So toLowerCase being a function for string.prototype, you will get an error.
You should describe what you where aiming to get here.

Resources