How to implement an updateTodo function in a todo list? - reactjs

I wanna update the todo item in the todo list but I have some troubles understanding this function, like why does setState in the function submitTodo still set the id and value the same like in the useState above...can anybody help me to understand this function better? thank you so much!
This is the function:
TodoList.js:
const updateTodo = (todoId, newValue) => {
if (!newValue.text || /^\s*$/.test(newValue.text)) {
return;
}
setTodos(prev => prev.map(item => (item.id === todoId ? newValue : item)));
};
Todo.js:
const [edit, setEdit] = useState({
id: null,
value: ''
});
const submitUpdate = value => {
updateTodo(edit.id, value);
setEdit({
id: null,
value: ''
});
};
if (edit.id) {
return <TodoForm edit={edit} onSubmit={submitUpdate} />;
}

Because after you update the todo via updateTodo() function, you'll need to clear what is stored in state for future use, hence setting it back to what it was initialised to in the useState declaration. This is usually bundled after the actual update, as a good state management practice.

Related

Is it valid, to update the state by mapping to new Objects

Let's take this Update State example:
const initialState = [
{id: 1, country: 'Austria'},
{id: 2, country: 'Belgium'},
{id: 3, country: 'Canada'},
];
const [data, setData] = useState(initialState);
const updateState = () => {
setData(prevState => {
const newState = prevState.map(obj => {
if (obj.id === 2) {
return {...obj, country: 'Denmark'};
}
return obj;
});
return newState;
});
};
1. Is it also valid to update the state like this? (First example)
const updateState = () => {
const newState = data.map(obj => {
if (obj.id === 2) {
return {...obj, country: 'Denmark'};
}
return obj;
});
setData(newState);
};
2. Is it also valid to update the state like this? (Second example)
const updateState = () => {
setData(prevState => {
const newState = prevState.map(obj => {
if (obj.id === 2) {
let newObj = obj;
newObj.country = 'Denmark'
return newObj;
}
return obj;
});
return newState;
});
};
3. Do this specific versions also have performance impacts? Which one is the best?
The first and the second example are perfectly valid. I would, however, suggest you to use the first one and I will explain why:
With the first example you are using a callback as an argument of the function. And this form means that you are actually getting the last data state value (this is important because the state updates happen asynchronously). Whenever you need to update the state based on the previous value even React suggests to use the callback form to avoid side effects.
More infos here: https://reactjs.org/docs/hooks-reference.html#functional-updates
The third example is not valid because you are mutating directly the state. Something that in react is not allowed.
More infos here: https://dev.to/il3ven/common-error-accidentally-mutating-state-in-react-4ndg

How to synchronous useState with passing state to localstorage

I ran into an asynchronous useState problem.
I have a situation where I first need to add an object to the state array in the handler. And then add this state to the localStorage.
setFavoritedSongs ((prev) => [...prev, {name: "Lala", length: "3:20"}]);
localStorage.setItem("storageItemName", JSON.stringify(favoritedSongs));
If I delete the entire localStorage first and run the handler. So an empty array is added to my localStorage (the state shows me updated). After the action again, the required object is finally added to my array.
I tried something like this, but still the same problem.
const tempArray = favoritedSongs.push({ name: "Lala", length: "3:20" });
localStorage.setItem(storageItemName, JSON.stringify(tempArray));
How do you solve this, please?
/// EDIT
I have something like this
const FavoriteSong = () => {
const song = { id: 1, name: "Lala", length: "3:20" };
const [favoritedSongs, setFavoritedSongs] = useState([]);
const [isFavorited, setIsFavorited] = useState(false);
useEffect(() => {
if (localStorage.getItem("storageItemName")) {
const storageSongs = JSON.parse(
localStorage.getItem("storageItemName") || ""
);
setFavoritedSongs(storageSongs);
const foundSong = storageSongs?.find((song) => song.id === song.id);
foundSong ? setIsFavorited(true) : setIsFavorited(false);
}
}, [song]);
const handleClick = () => {
if (isFavorited) {
const filteredSong = favoritedSongs.filter(
(song) => song.id !== song.id
);
localStorage.setItem("storageItemName", JSON.stringify(filteredSong));
setIsFavorited(false);
} else {
setFavoritedSongs((prev) => [...prev, song]);
localStorage.setItem("storageItemName", JSON.stringify(favoritedSongs));
setIsFavorited(true);
}
};
return <div onClick={handleClick}>CLICK</div>;
};
export default FavoriteSong;
Just place your localStorage.set logic inside a useEffect to make sure it runs after the state actually changes.
useEffect() => {
localStorage.setItem(...);
}, [favoritedSongs]};
For that you can Use the condition If data in the array then It will set in localStorage otherwise not
const tempArray = favoritedSongs.push({ name: "Lala", length: "3:20" });
tempArray.length && localStorage.setItem(storageItemName, JSON.stringify(tempArray));
.
setFavoritedSongs ((prev) => [...prev, {name: "Lala", length: "3:20"}]);
FavoritedSongs.length(your state name) && localStorage.setItem("storageItemName", JSON.stringify(favoritedSongs));

Why useSelector is not selecting redux state

(state) => state?.clientAddress?.getAddress
);
useEffect(() => {
dispatch(GET_CLIENT_ADDRESS({}))
}, [dispatch]);
const onSavedAddressSelect = (addressId) => {
debugger
if (addressId) {
const { data: selectedAddress = [] } = clientAddress ?? {} ;
console.clear()
console.log(selectedAddress)
const addressObj = selectedAddress?.filter(
(addressItem) => addressItem?.value === addressId
)?.[0];
setAddress(addressObj);
setSelectedVenue(addressObj?.value);
}
props.drawer.setDrawer({
open: false,
});
};
My clientAddress inside onSavedAddressSelect is not getting updated with latest redux state even if I dispatch on first render on useEffect.
Please help.
isn't clientAddress is not updated because you call
dispatch(GET_CLIENT_ADDRESS({})) with empty object, so it's stays the same? Try to pass there some object with values

How to create multiple object in Reactjs?

I am having a onChange function i was trying to update the array optionUpdates which is inside of sentdata by index wise as i had passed the index to the onChange function.
Suppose i update any two values of the input field from option which is inside of postdata therefore the input name i.e. orderStatus with changed value and with order should be saved inside of optionUpdates
For example: Suppose i update the option 1 and option 3 of my postdata further inside of options of orderStatus values so my optionUpdates which is inside of sentdata should look like this
optionUpdates: [
{
Order: 1,
orderStatus: "NEW1"
},
{
Order: 3,
orderStatus: "New2"
}
]
here is what i tried
setSentData(oldValue => {
const curoptions = oldValue.sentdata.optionUpdates[idx];
console.log(curoptions);
curoptions.event.target.name = event.target.value;
return {
...oldValue,
sentdata: {
...oldValue.sentdata.optionUpdates,
curoptions
}
};
});
};
Demo
complete code:
import React from "react";
import "./styles.css";
export default function App() {
const x = {
LEVEL: {
Type: "LINEN",
options: [
{
Order: 1,
orderStatus: "INFO",
orderValue: "5"
},
{
Order: 2,
orderStatus: "INPROGRESS",
orderValue: "5"
},
{
Order: 3,
orderStatus: "ACTIVE",
orderValue: "9"
}
],
details: "2020 N/w UA",
OrderType: "Axes"
},
State: "Inprogress"
};
const [postdata, setPostData] = React.useState(x);
const posting = {
optionUpdates: []
};
const [sentdata, setSentData] = React.useState(posting);
const handleOptionInputChange = (event, idx) => {
const target = event.target;
setPostData(prev => ({
...prev,
LEVEL: {
...prev.LEVEL,
options: prev.LEVEL.options.map((item, id) => {
if (id === idx) {
return { ...item, [target.name]: target.value };
}
return item;
})
}
}));
setSentData(oldValue => {
const curoptions = oldValue.sentdata.optionUpdates[idx];
console.log(curoptions);
curoptions.event.target.name = event.target.value;
return {
...oldValue,
sentdata: {
...oldValue.sentdata.optionUpdates,
curoptions
}
};
});
};
return (
<div className="App">
{postdata.LEVEL.options.map((item, idx) => {
return (
<input
key={idx}
type="text"
name="orderStatus"
value={postdata.LEVEL.options[idx].orderStatus}
onChange={e => handleOptionInputChange(e, idx)}
/>
);
})}
</div>
);
}
If I've understood correctly then what you're looking to do is save a copy of the relevant options object in sentdata every time one changes. I think the best way to approach this is by doing all your state modification outside of setPostData, which then makes the results immediately available to both setPostData and setSentData. It will also make the setters easier to read, which is good because you have some quite deeply nested and complicated state here.
A few other things worth noting first:
Trying to use synchronous event results directly inside the asynchronous setter functions will throw warnings. If you do need to use them inside setters, then it is best to destructure them from the event object first. This implementation uses destructuring although it didn't end up being necessary in the end.
You seem to have got a bit muddled up with setSentData. The oldValue parameter returns the whole state, as prev in setPostData does. For oldValue.sentdata you just wanted oldValue. You also wanted curoptions[event.target.name], not curoptions.event.target.name.
So, on to your code. I would suggest that you change the way that your input is rendered so that you are using a stable value rather than just the index. This makes it possible to reference the object no matter which array it is in. I have rewritten it using the Order property - if this value is not stable then you should assign it one. Ideally you would use a long uuid.
{postdata.LEVEL.options.map(item => {
return (
<input
key={item.Order}
type="text"
name="orderStatus"
value={item.orderStatus}
onChange={e => handleOptionInputChange(e, item.Order)}
/>
);
})}
The handleOptionInputChange function will now use this Order property to find the correct objects in both postdata and sentdata and update them, or if it does not exist in sentdata then push it there. You would do this by cloning, modifying, and returning the relevant array each time, as I explained before. Here is the function again with comments:
const handleOptionInputChange = (event, orderNum) => {
const { name, value } = event.target;
/* Clone the options array and all objects
inside so we can mutate them without
modifying the state object */
const optionsClone = postdata.LEVEL.options
.slice()
.map(obj => Object.assign({}, obj));
/* Find index of the changed object */
const optionIdx = optionsClone.findIndex(obj => obj.Order === orderNum);
/* If the orderNum points to an existing object...*/
if (optionIdx >= 0) {
/* Change the value of object in clone */
optionsClone[optionIdx][name] = value;
/* Set postdata with the modified optionsClone */
setPostData(prev => ({
...prev,
LEVEL: {
...prev.LEVEL,
options: optionsClone
}
}));
/* Clone the optionUpates array and all
contained objects from sentdata */
const updatesClone = sentdata.optionUpdates
.slice()
.map(obj => Object.assign({}, obj));
/* Find the index of the changed object */
const updateIdx = updatesClone.findIndex(obj => obj.Order === orderNum);
/* If the changed object has already been
changed before, alter it again, otherwise push
a new object onto the stack*/
if (updateIdx >= 0) {
updatesClone[updateIdx][name] = value;
} else {
updatesClone.push({ Order: orderNum, [name]: value });
}
/* Set sentdata with modified updatesClone */
setSentData(prev => ({
...prev,
optionUpdates: updatesClone
}));
}
};

Duplication problem in TodoList application

I am creating a todo-list, the following function handleChange gets the id of the a todo component and changes its attribute of completed from true/false. This is then saved in state of allTodos
function handleChange(id) {
const updatedTodos = allTodos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
setTodos(updatedTodos)
}
const todoComponents = allTodos.map(item => <Todos key={item.id} item={item} handleChange={handleChange}/>)
the function updateDB takes that value from state and using it to update the database.
function updateDB(event) {
event.preventDefault()
const value = {
completed: false,
text: newTodo,
id: allTodos.length,
}
}
Here's where the problem arises: id: allTodos.length. If one of these are deleted, it will create a todo with a duplicate ID, crashing the whole thing. I don't know how to avoid this problem.
In updateDB, you are setting id to allTodos.length aka 1.

Resources