Reactjs: useState cannot update - reactjs

I am just learning react,
Here I have set the Initial value and onClick I want to change the value, but nothing happens, react does't rerender the screen, no value changes.
const app = () => {
const [person, setPerson] = useState([
{name:'sugumar',age:23},
{name:'vijay',age:25}
]
)
const changeNameHandler = () => {
console.log('change name called');
setPerson((person)=>{
console.log('set property called');
person[0].name = 'Arun';
return person;
})
}
return (
<div className='App'>
<button onClick={changeNameHandler}>Change Name</button>
<Person name={person[0].name} age={person[0].age}></Person>
<Person name={person[1].name} age={person[1].age}></Person>
</div>
)
}
export default app;

person[0].name will reference the array created from initializing the state. Changing its identifier will not help either. You need to clone the array to prevent mutating it. You can read more on functional programming & immutable objects
function Clone(obj) {
if (obj === null || typeof obj !== "object" || "isActiveClone" in obj)
return obj;
var temp;
if (obj instanceof Date) temp = new obj.constructor();
//or new Date(obj);
else temp = obj.constructor();
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj["isActiveClone"] = null;
temp[key] = Clone(obj[key]);
delete obj["isActiveClone"];
}
}
return temp;
}
const changeNameHandler = () => {
let newPerson = Clone(person);
newPerson[0].name = "Arun";
setPerson(newPerson);
};
In this context however, you can also use the spread syntax (ES6 feature) so you will no longer need the aforementioned Clone function. You may opt to use the function I shared when your object gets more complex & you need to deep clone.
const changeNameHandler = () => {
let newPerson = [...person];
newPerson[0].name = "Arun";
setPerson(newPerson);
};
CodeSandBox: https://codesandbox.io/s/lively-fire-qmvil?file=/src/App.js:198-331

Try this
import React, { useState } from "react";
const Person = ({ name, age }) => (
<div>
{name} - {age}
</div>
);
const App = () => {
const [person, setPerson] = useState([
{ name: "sugumar", age: 23 },
{ name: "vijay", age: 25 }
]);
const changeNameHandler = () => {
person[0].name = "Arun, Updated On: " + new Date().getTime();
setPerson([...person]);
};
return (
<div className="App">
<button onClick={changeNameHandler}>Change Name</button>
<Person name={person[0].name} age={person[0].age} />
<Person name={person[1].name} age={person[1].age} />
</div>
);
};
export default App;
I think you're missing what is stated here https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

Create a second copy of the array before modifying it ...
const changeNameHandler = () => {
console.log('change name called');
let persons = person;
persons[0].name = 'Arun';
setPerson(persons);
}
For what it's worth ... I don't think binding a form to a single element in an array is a good choice for updating state ... but it's what you asked for.
.. and you don't need to return the value from the event handler.

Related

Why does ReactJS render twice

Ref: https://stackblitz.com/edit/react-ts-t7ynzi?file=App.tsx
When the Simple Add button is clicked, why does the component renders twice?
This causes problem when the the state has nested data and arrays because each render causes the event to be handled multiple times (see https://stackblitz.com/edit/react-ts-t7ynzi?file=App.tsx)
How can I prevent the rendering and duplicate processing of the onclick event?
interface SimpleFormProps {
data: number;
onAdd: () => void;
}
const SimpleForm = ({ data, onAdd }: SimpleFormProps) => {
return (
<form>
{data}
<button type="button" onClick={() => onAdd()}>
Simple Add
</button>
</form>
);
};
export default function App() {
const [simpleData, setSimpleData] = React.useState(10);
const handleSimpleAdd = () => {
setSimpleData((prev) => {
const newData = prev + 1;
// expect to be called once each click,
// but actually it is called twice each click
console.log('handleSimpleAdd');
return newData;
});
};
return (
<div>
<SimpleForm data={simpleData} onAdd={() => handleSimpleAdd()} />
</div>
);
}```
Change this:
class ComplexFormData {
stuff: number[];
constructor(prev: ComplexFormData | undefined = undefined) {
if (prev) {
this.stuff = prev.stuff;
} else {
this.stuff = [];
}
}
}
to this:
class ComplexFormData {
stuff: number[];
constructor(prev: ComplexFormData | undefined = undefined) {
if (prev) {
this.stuff = [...prev.stuff];
} else {
this.stuff = [];
}
}
}
The problem is that you are modifying the existing reference instead of creating a new one which causes bugs like that
fixed example

How can I make syntax less repetitive (DRY)?

The object of this app is to allow input text and URLs to be saved to localStorage. It is working properly, however, there is a lot of repeat code.
For example, localStoredValues and URLStoredVAlues both getItem from localStorage. localStoredValues gets previous input values from localStorage whereas URLStoredVAlues gets previous URLs from localStorage.
updateLocalArray and updateURLArray use spread operator to iterate of previous values and store new values.
I would like to make the code more "DRY" and wanted suggestions.
/*global chrome*/
import {useState} from 'react';
import List from './components/List'
import { SaveBtn, DeleteBtn, DisplayBtn, TabBtn} from "./components/Buttons"
function App() {
const [myLeads, setMyLeads] = useState([]);
const [leadValue, setLeadValue] = useState({
inputVal: "",
});
//these items are used for the state of localStorage
const [display, setDisplay] = useState(false);
const localStoredValues = JSON.parse(
localStorage.getItem("localValue") || "[]"
)
let updateLocalArray = [...localStoredValues, leadValue.inputVal]
//this item is used for the state of localStorage for URLS
const URLStoredVAlues = JSON.parse(localStorage.getItem("URLValue") || "[]")
const tabBtn = () => {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
const url = tabs[0].url;
setMyLeads((prev) => [...prev, url]);
// update state of localStorage
let updateURLArray = [...URLStoredVAlues, url];
localStorage.setItem("URLValue", JSON.stringify(updateURLArray));
});
setDisplay(false)
};
//handles change of input value
const handleChange = (event) => {
const { name, value } = event.target;
setLeadValue((prev) => {
return {
...prev,
[name]: value,
};
});
};
const saveBtn = () => {
setMyLeads((prev) => [...prev, leadValue.inputVal]);
setDisplay(false);
// update state of localStorage
localStorage.setItem("localValue", JSON.stringify(updateLocalArray))
};
const displayBtn = () => {
setDisplay(true);
};
const deleteBtn = () => {
window.localStorage.clear();
setMyLeads([]);
};
const listItem = myLeads.map((led) => {
return <List key={led} val={led} />;
});
//interates through localStorage items returns each as undordered list item
const displayLocalItems = localStoredValues.map((item) => {
return <List key={item} val={item} />;
});
const displayTabUrls = URLStoredVAlues.map((url) => {
return <List key={url} val={url} />;
});
return (
<main>
<input
name="inputVal"
value={leadValue.inputVal}
type="text"
onChange={handleChange}
required
/>
<SaveBtn saveBtn={saveBtn} />
<TabBtn tabBtn={tabBtn} />
<DisplayBtn displayBtn={displayBtn} />
<DeleteBtn deleteBtn={deleteBtn} />
<ul>{listItem}</ul>
{/* displays === true show localstorage items in unordered list
else hide localstorage items */}
{display && (
<ul>
{displayLocalItems}
{displayTabUrls}
</ul>
)}
</main>
);
}
export default App
Those keys could be declared as const and reused, instead of passing strings around:
const LOCAL_VALUE = "localValue";
const URL_VALUE = "URLValue";
You could create a utility function that retrieves from local storage, returns the default array if missing, and parses the JSON:
function getLocalValue(key) {
return JSON.parse(localStorage.getItem(key) || "[]")
};
And then would use it instead of repeating the logic when retrieving "localValue" and "URLValue":
const localStoredValues = getLocalValue(LOCAL_VALUE)
//this item is used for the state of localStorage for URLS
const URLStoredVAlues = getLocalValue(URL_VALUE)
Similarly, with the setter logic:
function setLocalValue(key, value) {
localStorage.setItem(key, JSON.stringify(value))
}
and then use it:
// update state of localStorage
let updateURLArray = [...URLStoredVAlues, url];
setLocalValue(URL_VALUE, updateURLArray);
// update state of localStorage
setLocalValue(LOCAL_VALUE, updateLocalArray)

React listen to child's state from parent

Damn, two days, two noob questions, sorry guys.
Yesterday, I spent the whole afternoon reading the docs but my fart-ey brain cannot process how to use react hooks to pass data from a child to a parent.
I want to create a button on my parent that can listen to his child's state to check on it and change the background color depending on its value.
Thing is, the child component is mapping some stuff so I cannot create a button (otherwhise it would be rendered multiple times and not only once like I want).
I've thought about moving all the data to my parent component but I cannot understand how since I'm fairly new to React and it's been only two months of learning how to code for me basically.
I will now provide the code for the parent and the child component.
The parent :
import React from "react";
import Quizz from "./components/Quizz";
export default function App() {
const [quizz, setQuizz] = React.useState([]);
React.useEffect(() => {
async function getData() {
const res = await fetch(
"https://opentdb.com/api.php?amount=5&category=27&type=multiple"
);
const data = await res.json();
setQuizz(data.results)
}
getData();
}, []);
function checkOnChild(){ /* <== the function I'd like to use to check on my Quizz component's "activeAnswer" state */
console.log(quizz);
}
const cards = quizz.map((item, key) => {
return <Quizz {...item} key={key}/>;
});
return (
<div>
{cards}
<button onClick={checkOnChild}>Check answers</button> /* <== the button that will use the function */
</div>
);
}
and the child :
import React from "react";
import { useRef } from "react";
export default function Quizz(props) {
const [activeAnswer, setActiveAnswer] = React.useState('');/* <== the state I'd like to check on from my parent component */
function toggle(answer) {
setActiveAnswer(answer);
}
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
let answers = props.incorrect_answers;
const ref = useRef(false);
if (!ref.current) {
answers.push(props.correct_answer);
shuffleArray(answers);
ref.current = true;
}
const answerDiv = answers.map((answer, key) => (
<div key={key} className="individuals" onClick={()=> toggle(answer)}
style={{background: answer == activeAnswer ? "#D6DBF5" : "transparent" }}>
{answer}
</div>
));
console.log(answers);
console.log(activeAnswer);
console.log(props.correct_answer);
return (
<div className="questions">
<div>
<h2>{props.question}</h2>
</div>
<div className="individuals__container">{answerDiv}</div>
<hr />
</div>
);
}
I'm really sorry If it seems dumb or if I'm making forbidden things lmao, but thanks in advance for your help guys!
This should get you a bit further I think.
export default function App() {
const [quizData, setQuizData] = useState([])
const [quizState, setQuizState] = useState({})
useEffect(() => {
async function getData() {
const res = await fetch('https://opentdb.com/api.php?amount=5&category=27&type=multiple')
const data = await res.json()
const results = data.results
setQuizData(results)
setQuizState(results.reduce((acc, curr) => ({ ...acc, [curr.question]: '' }), {}))
}
getData()
}, [])
function checkOnChild() {
console.log(quizState)
}
const cards = quizData.map((item) => {
return <Quizz {...item} key={item.question} quizState={quizState} setQuizState={setQuizState} />
})
return (
<div>
{cards}
<button onClick={checkOnChild}>Check answers</button>
</div>
)
}
export default function Quizz(props) {
function handleOnClick(answer) {
props.setQuizState(prevState => ({
...prevState,
[props.question]: answer,
}))
}
const answers = useMemo(() => {
const arr = [...props.incorrect_answers, props.correct_answer]
return shuffleArray(arr)
}, [props.incorrect_answers, props.correct_answer])
const answerDiv = answers.map((answer) => (
<div
className="individuals"
key={answer}
onClick={() => handleOnClick(answer)}
style={{ background: answer == props.quizState[props.question] ? '#D6DBF5' : 'transparent' }}
>
{answer}
</div>
))
return (
<div className="questions">
<div>
<h2>{props.question}</h2>
</div>
<div className="individuals__container">{answerDiv}</div>
<hr />
</div>
)
}

React onClick update useState variable and render updated

I know this question has been asked before, but the solutions were different from the way I structure/code my React app as shown below. How use one update/render based on the updated listData?
import React, { useState } from "react";
const PageName = () => {
const [listData, setlistData] = useState([]);
function add(){
let newrow = {};
listData.push(newrow);
setlistData(listData);
}
return (
<div>
{listData.length}
<button onClick={() => add()}>Add</button>
{
listData.map(function (row, i) {
return (
<p key={i}>row</p>
);
})
}
</div>
);
};
export default PageName;
If it is a state variable it is react's job to rerender. Your job is to ensure you are passing in a new reference to setState() without mutating the state.
Use ... spread operator.
import React, { useState } from "react";
const PageName = () => {
const [listData, setlistData] = useState([]);
function add(){
let newrow = {};
let newListData = [...listData];
newListData.push(newrow);
setlistData(newListData);
}
return (
<div>
{listData.length}
<button onClick={() => add()}>Add</button>
{
listData.map(function (row, i) {
return (
<p key={i}>row</p>
);
})
}
</div>
);
};
export default PageName;
The reference stays the same if you only push to the same object .
If you push to the list , under the hood the list gets the data but React cannot rerender because the object listData does not save value but reference , which doesn't change . Creating a new object by copying all the data and putting it in a new object changes the reference of newreferenceListData ,which causes re render.
function add(){
let newrow = {};
let newreferenceListData = [...listData,newrow];
setlistData(newreferenceListData);
}

Modifying object inside array with useContext

I've been having trouble using React's useContext hook. I'm trying to update a state I got from my context, but I can't figure out how. I manage to change the object's property value I wanted to but I end up adding another object everytime I run this function. This is some of my code:
A method inside my "CartItem" component.
const addToQuantity = () => {
cartValue.forEach((item) => {
let boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
setCartValue((currentState) => [...currentState, item.quantity++])
} else {
return null;
}
});
};
The "Cart Component" which renders the "CartItem"
const { cart, catalogue } = useContext(ShoppingContext);
const [catalogueValue] = catalogue;
const [cartValue, setCartValue] = cart;
const quantiFyCartItems = () => {
let arr = catalogueValue.map((item) => item.name);
let resultArr = [];
arr.forEach((item) => {
resultArr.push(
cartValue.filter((element) => item === element.name).length
);
});
return resultArr;
};
return (
<div>
{cartValue.map((item, idx) => (
<div key={idx}>
<CartItem
name={item.name}
price={item.price}
quantity={item.quantity}
id={item.id}
/>
<button onClick={quantiFyCartItems}>test</button>
</div>
))}
</div>
);
};
So how do I preserve the previous objects from my cartValue array and still modify a single property value inside an object in such an array?
edit: Here's the ShoppingContext component!
import React, { useState, createContext, useEffect } from "react";
import axios from "axios";
export const ShoppingContext = createContext();
const PRODUCTS_ENDPOINT =
"https://shielded-wildwood-82973.herokuapp.com/products.json";
const VOUCHER_ENDPOINT =
"https://shielded-wildwood-82973.herokuapp.com/vouchers.json";
export const ShoppingProvider = (props) => {
const [catalogue, setCatalogue] = useState([]);
const [cart, setCart] = useState([]);
const [vouchers, setVouchers] = useState([]);
useEffect(() => {
getCatalogueFromApi();
getVoucherFromApi();
}, []);
const getCatalogueFromApi = () => {
axios
.get(PRODUCTS_ENDPOINT)
.then((response) => setCatalogue(response.data.products))
.catch((error) => console.log(error));
};
const getVoucherFromApi = () => {
axios
.get(VOUCHER_ENDPOINT)
.then((response) => setVouchers(response.data.vouchers))
.catch((error) => console.log(error));
};
return (
<ShoppingContext.Provider
value={{
catalogue: [catalogue, setCatalogue],
cart: [cart, setCart],
vouchers: [vouchers, setVouchers],
}}
>
{props.children}
</ShoppingContext.Provider>
);
};
edit2: Thanks to Diesel's suggestion on using map, I came up with this code which is doing the trick!
const newCartValue = cartValue.map((item) => {
const boolean = Object.values(item).includes(props.name);
if (boolean && item.quantity < item.available) {
item.quantity++;
}
return item;
});
removeFromStock();
setCartValue(() => [...newCartValue]);
};```
I'm assuming that you have access to both the value and the ability to set state here:
const addToQuantity = () => {
cartValue.forEach((item) => {
let boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
setCartValue((currentState) => [...currentState, item.quantity++])
} else {
return null;
}
});
};
Now... if you do [...currentState, item.quantity++] you will always add a new item. You're not changing anything. You're also running setCartValue on each item, which isn't necessary. I'm not sure how many can change, but it looks like you want to change values. This is what map is great for.
const addToQuantity = () => {
setCartValue((previousCartValue) => {
const newCartValue = previousCartValue.map((item) => {
const boolean = Object.values(item).includes(props.name);
console.log(boolean);
if (boolean) {
return item.quantity++;
} else {
return null;
}
});
return newCartValue;
});
};
You take all your values, do the modification you want, then you can set that as the new state. Plus it makes a new array, which is nice, as it doesn't mutate your data.
Also, if you know only one item will ever match your criteria, consider the .findIndex method as it short circuits when it finds something (it will stop there), then modify that index.

Resources