React state missing - reactjs

I am working with React and I can't seem to define properly the state of a component (handlePoints). When I want to assign a new state using shallow copy it creates a new entry (key).
Apologies that part of the question is not in code. I couldn't edit it here. If you need more information please reach out. Thanks! I've added an image where you can see that there is a new entry added instead of a modification happening to an existing entry.
cons anecdotes = ['Anecdote A', 'Anecdote B', 'Anecdote C', 'Anecdote D', 'Anecdote E', 'Anecdote F']
const Button = ({text, onClick}) => <div>
<button onClick={onClick}>{text}</button>
const records = () => {
const temp = {};
for (let i = 0; i < anecdotes.length; i++) {
temp[i] = 0;
}
return temp}
const copy = {...records()};
const App = (props) => {
console.log('Copy', copy);
const [selected, setSelected] = useState(0);
const [points, setPoints] = useState(copy);
const handleSelection = () => {
setSelected((Math.random() * (anecdotes.length) ) << 0);
};
const handlePoints = () => {
setPoints({...copy, selected: copy[selected] += 1})
};
return (
<div>
<p>{JSON.stringify({copy})}</p>
<p>{JSON.stringify({points})}</p>
<p>Selected: {selected}</p>
<Button onClick={handleSelection} text={'next anecdote'}/>
<Button onClick={handlePoints} text={'vote'}/>
has {copy[selected]} votes <br/>
{props.anecdotes[selected]} <br/>
</div>
);};
The line where I cannot set the state correctly is:
const handlePoints = () => {
setPoints({...copy, selected: copy[selected] += 1})
};
As you can see, a new entry was added instead of updating one of the existing one.

In terms of why a new field with key 'selected' is being added, is due to how you are creating the new object.
If you want to to update the key with the variable selected, you would need to modify the handlePoints function to be:
const handlePoints = () => {
setPoints({...copy, [selected]: copy[selected] += 1})
};
The brackets around the variable selected is how it determines to use the value of the variable selected. Instead of interpreting it as a the key 'selected'. This is making use of the computed property names introduced in emca2015. More info - Computed property names
Hopefully this solves your problem :)
Good luck :D
Also:
As for what I can see, your copy function it isn't actually copying any object, it seems to be a default? Or is the purpose just to create the keys for the number of anecdotes there are?
Just curious XD

Related

State array not updating when removing an array item in React

When I remove an array item from my state array, I'm also updating the prices after removing the array item. But prices are not updating. I have tried every thing, but didn't get any solution.
export default function CustomizerState(props) {
const initialTextItem = {
text: "Hello",
neonPrice: 0,
backplatePrice: 0,
neonPower: 0,
totalPrice: 0
}
const [settings, setSettings] = useState({
textItems: [initialTextItem],
libraryItems: [],
accessories: [...],
finalPrice: null
})
const removeItem = (id, itemType = "textItems") => {
const filteredItems = settings[itemType].filter((item) => {
return item.id !== id
})
setSettings((prevState) => (
{...prevState, [itemType]: filteredItems}
))
finalPrice()
}
const finalPrice = () => {
const textItemsPrice = getTotalPrice()
const libraryItemsPrice = getTotalPrice("libraryItems")
const accessoriesPrice = getTotalPrice("accessories", "unitPrice")
console.log(textItemsPrice, libraryItemsPrice, accessoriesPrice)
const finalPrice = textItemsPrice + libraryItemsPrice + parseInt(accessoriesPrice)
setSettings((prevState) => (
{...prevState, finalPrice}
))
}
const getTotalPrice = (itemType = "textItems", priceKey = "totalPrice") => {
let price = 0
settings[itemType].map((item) => (
price = price + (item[priceKey] * item.quantity)
))
return price
}
return (
<CustomizerContext.Provider value={{settings, addTextItem,
removeItem}}>
{props.children}
</CustomizerContext.Provider>
)
}
For now, it is behaving like when I remove any item, it doesn't update the finalPrice object item, but when I remove another item then it updates the prices for previous items. I don't know why it is behaving like this.
Can someone please have a look on my code and tell me what is wrong with it and how can I fix this?
You're calling finalPrice()right after you set your state
that triggers a re-rendering. You have to trigger the change using useEffect hook like this:
useEffect(() => {
finalPrice()
}, [settings]
You should probably consider separating your price from the rest of your settings.
Instead of calling a function right after updating the list, do the calculations before and update the state altogether. The problem with your approach is that when the calculus is being made, the state haven't updated yet, so when the function finalPrice() runs it takes the previous value.
I sincerely recommend you to use a Reducer instead, a single state with so many parameters may be troublesome.
Refer to useReducer, it will make your life easier.

React state is not updating immediately after setState is being called

I am building the frontend of a web based software and I want to add new note every time I press add button.
But it's simply not happening. New note is being rendered only when I change the state of another object. Right below I ma attaching the code. Please help, I am stuck here.
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = allnotes;
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
setAllNotes(notesAllTemp)
}
});
}
If anyone can figure this out, please help.
Please don't make mistake by directly updating the array element by its index you should first copy the array into a new array, otherwise, it will directly update the array which will cause reacjs to not to re-render the component.
Check this out
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = [...allnotes]; // !IMPORTANT to copy array
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
}
});
setAllNotes(notesAllTemp);
}
Better if you first modify the array then update at once
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = allnotes;
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
}
});
setAllNotes(notesAllTemp)
}

Dynamically create React.Dispatch instances in FunctionComponents

How can I create an array of input elements in react which are being "watched" without triggering the error for using useState outside the body of the FunctionComponent?
if I have the following (untested, simplified example):
interface Foo {
val: string;
setVal: React.Dispatch<React.SetStateAction<string>>;
}
function MyReactFunction() {
const [allVals, setAllVals] = useState<Foo[]>([])
const addVal = () => {
const [val, setVal] = useState('')
setAllVals(allVals.concat({val, setVal}))
}
return (
<input type="button" value="Add input" onClick={addVal}>
allVals.map(v => <li><input value={v.val} onChange={(_e,newVal) => v.setVal(newVal)}></li>)
)
}
I will get the error Hooks can only be called inside of the body of a function component.
How might I dynamically add "watched" elements in the above code, using FunctionComponents?
Edit
I realise a separate component for each <li> above would be able to solve this problem, but I am attempting to integrate with Microsoft Fluent UI, and so I only have the onRenderItemColumn hook to use, rather than being able to create a separate Component for each list item or row.
Edit 2
in response to Drew Reese's comment: apologies I am new to react and more familiar with Vue and so I am clearly using the wrong terminology (watch, ref, reactive etc). How would I rewrite the code example I provided so that there is:
An add button
Each time the button is pressed, another input element is added.
Each time a new value is entered into the input element, the input element shows the value
There are not excessive or unnecessary re-rendering of the DOM when input elements have their value updated or new input element is added
I have access to all the values in all the input elements. For example, if a separate submit button is pressed I could get an array of all the string values in each input element. In the code I provided, this would be with allVals.map(v => v.val)
const [val, setVal] = useState('') is not allowed. The equivalent effect would be just setting value to a specific index of allVals.
Assuming you're only adding new items to (not removing from) allVals, the following solution would work. This simple snippet just shows you the basic idea, you'll need to adapt to your use case.
function MyReactFunction() {
const [allVals, setAllVals] = useState<Foo[]>([])
const addVal = () => {
setAllVals(allVals => {
// `i` would be the fixed index of newly added item
// it's captured in closure and would never change
const i = allVals.length
const setVal = (v) => setAllVals(allVals => {
const head = allVals.slice(0, i)
const target = allVals[i]
const tail = allVals.slice(i+1)
const nextTarget = { ...target, val: v }
return head.concat(nextTarget).concat(tail)
})
return allVals.concat({
val: '',
setVal,
})
})
}
return (
<input type="button" value="Add input" onClick={addVal} />
{allVals.map(v =>
<li><input value={v.val} onChange={(_e,newVal) => v.setVal(newVal)}></li>
)}
)
}
React hooks cannot be called in callbacks as this breaks the Rules of Hooks.
From what I've gathered you want to click the button and dynamically add inputs, and then be able to update each input. You can add a new element to the allVals array in the addVal callback, simply use a functional state update to append a new element to the end of the allVals array and return a new array reference. Similarly, in the updateVal callback use a functional state update to map the previous state array to a new array reference, using the index to match the element you want to update.
interface Foo {
val: string;
}
function MyReactFunction() {
const [allVals, setAllVals] = useState<Foo[]>([]);
const addVal = () => {
setAllVals((allVals) => allVals.concat({ val: "" }));
};
const updateVal = (index: number) => (e: any) => {
setAllVals((allVals) =>
allVals.map((el, i) =>
i === index
? {
...el,
val: e.target.value
}
: el
)
);
};
return (
<>
<input type="button" value="Add input" onClick={addVal} />
{allVals.map((v, i) => (
<li key={i}>
<input value={v.val} onChange={updateVal(i)} />
</li>
))}
</>
);
}

React useState with default array value does not rerender

If I use a number or string as default value, it rerenders the app after use of setVotes just like with setSelected. With array it does not work (only would display real renewed array after page is rerendered, as it is easy to check with setSelected button)
const App = (props) => {
const [votes, setVotes] = useState([0,0,0,0,0,0])
const [selected, setSelected] = useState(0)
const handlenext = () => {
setSelected(Math.floor(Math.random() * 6))
}
const handlevote = () => {
let newvotes=votes
newvotes[selected]+=1
setVotes(newvotes)
}
return (
<div>
<button onClick={handlenext}> next anecdot</button>
<p>{props.anecdotes[selected]}</p>
<button onClick={handlevote}> vote</button>
<p>votes {votes[selected]}</p>
</div>
)
}
const anecdotes = [
'I',
's',
'T',
'A',
'l',
'.'
]
try this instead
const handlevote = () => {
let newvotes= [...votes]
newvotes[selected]+=1
setVotes(newvotes)
}
You need go build a new array. Do not use old array. Should you const a=[…b]
You need to pass a new reference. Arrays and object are reference based, if you don't create a new one is the same reference and react don't go deep to compare differrence. Besides you are mutating state directly which is bad.
Create a new copy from the state as const newvotes= [... votes]. Now you can pass next state without mutating the original and passing a new reference as well

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>;
}

Resources