How to prevent useState from rerendering the component again? - javascript

import { nanoid } from 'nanoid';
import React from 'react';
export default function Question(props) {
const answers = [...props.incorrect_answers, props.correct_answer];
const [selectedAnswer, setSelectedAnswer] = React.useState();
function handleChange(event) {
setSelectedAnswer(event.target.value);
console.log(event.target.value);
}
// Randomize answer of the Question
function createRandom(arr) {
let myArr = [...arr];
let randomizedArr = [];
while (myArr.length > 0) {
let randomIndex = Math.floor(Math.random() * myArr.length);
randomizedArr.push(myArr[randomIndex]);
myArr.splice(randomIndex, 1);
}
return randomizedArr;
}
const randomizedArr = createRandom(answers);
// RadioButton for the answers
const RadioButton = ({ label, value, onChange }) => {
return (
<label>
<input
type="radio"
name="answers"
value={value}
checked={selectedAnswer === value}
onChange={onChange}
/>
{label}
</label>
);
};
const answersElements = randomizedArr.map((answer) => {
return (
<RadioButton
key={nanoid()}
label={answer}
value={answer}
onChange={handleChange}
/>
);
});
return (
<div className="question-row">
<div className="single-question">{props.question}</div>
<div className="answers">{answersElements}</div>
</div>
);
}
When I select a radiobutton answer , React rerender the RaidoButton component and call my Randomize function which randomize the answers.
I do not know how to prevent Randomize function from being called after the first time.
in the two pictures when I select an answer for the first question , the answers get randomized again , I want to prevent this behavior

This is an XY question.
You need the component to rerender when the state changes (if for no other reason than to update which radio button is checked).
The problem is that you need the randomisation function to run only on the initial load.
Create a state for the randomized array:
const [randomizedArr, setRandomizedArr] = useState([]);
Then populate it in an effect hook with an empty dependency array (so it only runs on the initial load of the component).
useEffect(() => {
const randomizedArr = createRandom(answers);
setRandomizedArr(randomizedArr);
}, []);

const [randomizedArr, setRandomizedArr] = React.useState([]);
const [selectedAnswer, setSelectedAnswer] = React.useState();
React.useEffect(() => {
const answers = [...props.incorrect_answers, props.correct_answer];
const randomizedArr = createRandom(answers);
setRandomizedArr(randomizedArr);
}, [props.incorrect_answers, props.correct_answer]);

Related

how to get current value from input field in react js

I am trying to get current value from input field, but after onclick I am getting preious value in colsole.
here is my code
import { React, useState } from "react";
const CompoundIntrest = () => {
const [capitalValue, setcapitalValue] = useState(1000);
const ChangeCapital = () => {
setcapitalValue(capitalValue - 100);
};
const Calculate = () => {
console.log(capitalValue);
};
return (
<>
<button
onClick={() => {
ChangeCapital();
Calculate();
}}
>
click
</button>
<input type="number" value={capitalValue} />
</>
);
};
export default CompoundIntrest;
State updates occur asynchronously, so you won't have the updated state value inside the event handler.
You can lift the new value i.e. capitalValue - 100 to a scope from where it can be passed down to both ChangeCapital & Calculate.
const CompoundIntrest = () => {
const [capitalValue, setCapitalValue] = React.useState(1000);
const handleClick = () => {
const newCapitalValue = capitalValue - 100;
ChangeCapital(newCapitalValue);
Calculate(newCapitalValue);
};
const ChangeCapital = (capitalValue) => {
setCapitalValue(capitalValue);
};
const Calculate = (capitalValue) => {
console.log(capitalValue);
};
return (
<React.Fragment>
<button onClick={handleClick}>click</button>
<input
type="number"
value={capitalValue}
onChange={(e) => setCapitalValue(e.target.value)}
/>
</React.Fragment>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<CompoundIntrest />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
Note: The state updater function is called synchronously but the state updates happen asynchronously.
This becomes more clear if you update the state by passing a state updater callback, you would see that the callback is fired synchronously. Notice the order of logs in the example below:
function App() {
const [count, setCount] = React.useState(0);
const handleClick = () => {
console.log("Before calling setCount");
setCount((currCount) => {
console.log("Inside setCount");
return currCount + 1;
});
console.log("After calling setCount");
};
return <button onClick={handleClick}>Count: {count}</button>;
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
You can use Use useEffect Like this:-
import React,{useState,useEffect} from "react";
const CompoundIntrest = () => {
const [capitalValue, setcapitalValue] = useState(1000);
const ChangeCapital = () => {
setcapitalValue(capitalValue - 100);
};
const Calculate = () => {
console.log(capitalValue);
};
useEffect(()=>{
console.log("afet chage",capitalValue);
},[capitalValue]);
return (
<>
<button
onClick={() => {
ChangeCapital();
Calculate();
}}
>
click
</button>
<input type="number" value={capitalValue} />
</>
);
};
You can use the onChange event in the input field to get current value.
const [currentValue, setCurrentValue] = useState['']
const changeHandler = (e:any) => {
e.preventDefault();
const { value } = e.target
console.log('value', value);
setCurrentValue(value)
}
<input type="string" value={currentValue} onChange={(e:any) => changeHandler(e)}/>
I think, you should add onChange method in input tag like below:
Then you get current value in onClick event in button tag.
import { React, useState } from "react";
const CompoundIntrest = () => {
const [capitalValue, setcapitalValue] = useState(1000);
const ChangeCapital = () => {
setcapitalValue(capitalValue - 100);
};
useEffect(() => {
const Calculate = () => {
console.log(capitalValue);
};
Calculate()
}, [capitalValue])
return (
<>
<button
onClick={() => {
ChangeCapital();
}}
>
click
</button>
<input type="number" value={capitalValue} onChange={(e) => setcapitalValue(e.target.value)} />
</>
);
};
export default CompoundIntrest;
In the case of controlled component, other members have already provided the answer, I just want to give you an idea about uncontrolled component.
Assuming that we are dealing with an uncontrolled component ( "input" element ) then how we can get the value.
1. import { React, useState, useRef, useEffect } from "react";
2.
3. const CompoundIntrest = () => {
4. const [capitalValue, setcapitalValue] = useState(1000);
5. const inputRef = useRef(null);
6.
7. useEffect(() => {
8. console.log(capitalValue);
9. }, [capitalValue]);
10.
11. const ChangeCapital = () => {
12. setcapitalValue(inputRef.current.value - 100);
13. };
14.
15. return (
16. <>
17. <button onClick={ChangeCapital}>click</button>
18. <input ref={inputRef} type="number" />
19. </>
20. );
21. };
22.
23. export default CompoundIntrest;
At line 5, we have created a ref with initial value null using useRef hook of react, which later will be used to store reference of input element.
At line 18, we have assigned the inputRef to the ref of input element, which will be use to get the value from the field.
At line 12, we are getting the value of input as inputRef.current.value .
To check the update in the value of capitalValue state onClick event of button we can use useEffect hook of react ( From Line 7 to Line 9 is doing the same ).
PS : Please let me know if this clear your doubt or not. Thanks for reading the answer.
your code is fine, and your state is successfuly updated, the problem is the timing of calling your console. react handles your code async, it means it starts your changeCapital, and before the change capital function is finished it calls the calculate function, so the value of your state, is the previous value.
you need to call your calculate function somewhere else:
you can call it in a UseEffect hook, this way your function gets called whenever your state has successfuly changed, or
you can call your calculate in 'onchange' event of your input feild
if you want the better solution, the first one is more reactly than the second one

How to get around React setState Delay?

I have been stuck on the simple issue of the common React setState delay. I am currently looking to update an object within an array, by saving it to a state variable "newStud" within a child component, and pass it into a parent component to be utilized for a filtering function. My current issue is that state only updates completely after the second submission of an entry on my site. Thus, when the filter function in the parent component aims to read the array being passed in, it throws errors as the initial declaration of state is what is passed in. My question is if there is some way I can adjust for that delay in updating that information without having to break apart my larger components into smaller more manageable components?
For reference, here is the code I am utilizing for the child component (the issue is present in my "addTag" function):
import React, {useState, useEffect} from 'react';
import './studentcard.css';
import { Tags } from '../Tags/tags.js';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faPlus } from '#fortawesome/free-solid-svg-icons';
import { faMinus } from '#fortawesome/free-solid-svg-icons';
export function StudentCard({student, upStuds}) {
const [newStud, setNewStud] = useState({});
const [clicked, setClicked] = useState(false);
const [tag, setTag] = useState('');
// switches boolean to opposite value for plus/minus icon display
const onClick = () => {
setClicked(!clicked);
};
// triggers the addTag function to push a tag to the array within the student object
const onSubmit = async (e) => {
e.preventDefault();
await addTag(tag);
};
// captures the values being entered into the input
const onChange = (e) => {
setTag(e.target.value);
};
// this pushes the tag state value into the array that is located in the student object being passed down from the parent component
// it is meant to save the new copy of the "student" value in "newStuds" state variable, and pass that into the callback func
// ********** here is where I am experiencing my delay ************
const addTag = () => {
student.tags.push(tag);
setNewStud({...student});
upStuds(newStud);
setTag('');
};
let scores;
if (clicked !== false) {
scores = <ul className='grades-list'>
{student.grades.map((grade, index) => <li key={index} className='grade'>Test {(index + 1) + ':'} {grade}%</li>)}
</ul>;
}
return (
<div className='studentCard' >
<div className='pic-and-text'>
<img className='student-image' alt='' src={student.pic}></img>
<section className='right-side'>
<h3 id='names'>{student.firstName.toUpperCase() + ' ' + student.lastName.toUpperCase()}</h3>
<h4 className='indent'>Email: {student.email}</h4>
<h4 className='indent'>Company: {student.company}</h4>
<h4 className='indent'>Skill: {student.skill}</h4>
<h4 className='indent'>Average: {student.grades.reduce((a, b) => parseInt(a) + parseInt(b), 0) / student.grades.length}%</h4>
{scores}
<Tags student={student}/>
<form className='tag-form' onSubmit={onSubmit}>
<input className='tag-input' type='text' placeholder='Add a tag' onChange={onChange} value={tag}></input>
</form>
</section>
</div>
<FontAwesomeIcon icon={clicked !== false ? faMinus : faPlus} className='icon' onClick={onClick}/>
</div>
)
};
And if necessary, here is the Parent Component which is attempting to receive the updated information (the callback function I am using to fetch the information from the child component is called "upStuds") :
import React, {useState, useEffect} from 'react';
import './dashboard.css';
import {StudentCard} from '../StudentCard/studentcard';
import axios from 'axios';
export function Dashboard() {
const [students, setStudents] = useState([]);
const [search, setSearch] = useState('');
const [tagSearch, setTagSearch] = useState('');
useEffect(() => {
const options = {
method: 'GET',
url: 'https://api.hatchways.io/assessment/students'
};
var index = 0;
function genID() {
const result = index;
index += 1;
return result;
};
axios.request(options).then((res) => {
const students = res.data.students;
const newData = students.map((data) => {
const temp = data;
temp["tags"] = [];
temp["id"] = genID();
return temp;
});
setStudents(newData);
}).catch((err) => {
console.log(err);
});
}, []);
const onSearchChange = (e) => {
setSearch(e.target.value);
};
const onTagChange = (e) => {
setTagSearch(e.target.value);
};
// here is the callback function that is not receiving the necessary information on time
const upStuds = (update) => {
let updatedCopy = students;
updatedCopy.splice(update.id, 1, update);
setStudents(updatedCopy);
};
// const filteredTagged = tagList.filter
return (
<div className='dashboard'>
<input className='form-text1' type='text' placeholder='Search by name' onChange={onSearchChange}></input>
<input className='form-text2' type='text' placeholder='Search by tag' onChange={onTagChange}></input>
{students.filter((entry) => {
const fullName = entry.firstName + entry.lastName;
const fullNameWSpace = entry.firstName + ' ' + entry.lastName;
if (search === '') {
return entry;
} else if (entry.firstName.toLowerCase().includes(search.toLowerCase()) || entry.lastName.toLowerCase().includes(search.toLowerCase())
|| fullName.toLowerCase().includes(search.toLowerCase()) || fullNameWSpace.toLowerCase().includes(search.toLowerCase())) {
return entry;
}
}).map((entry, index) => {
return (<StudentCard student={entry} key={index} upStuds={upStuds} />)
})}
</div>
)
};
Please let me know if I need to clarify anything! Thanks for any assistance!
setNewStud({...student});
upStuds(newStud);
If you want to send the new state to upStuds, you can assign it to a variable and use it twice:
const newState = {...student};
setNewStud(newState);
upStuds(newState);
Additionally, you will need to change your upStuds function. It is currently mutating the existing students array, and so no render will occur when you setStudents. You need to copy the array and edit the copy.
const upStuds = (update) => {
let updatedCopy = [...students]; // <--- using spread operator to create a shallow copy
updatedCopy.splice(update.id, 1, update);
setStudents(updatedCopy);
}

Is there a way to populate option html tag with an array in react?

I'm trying to make an option in jsx to be populated by the values in an array (currencyOptions). I used this approach but it is not working as the options still remain to be blank. The array is passed down to the component as a prop. I set the array using usestate and the data is gotten from an API. Please help.
import React from "react";
function Currencyrow(props) {
const {
currencyOptions,
selectedCurrency,
onChangeCurrency,
amount,
onChangeAmount,
} = props;
// console.log(currencyOptions);
return (
<>
<input
type="number"
className="input"
value={amount}
onChange={onChangeAmount}
></input>
<select value={selectedCurrency} onChange={onChangeCurrency}>
{currencyOptions.map((option) => {
<option key={option} value={option}>
{option}
</option>;
})}
</select>
</>
);
}
export default Currencyrow;
That is the component where I pass down currencyOptions as a prop from my main app.js
import "./App.css";
import React from "react";
import Currencyrow from "./Components/Currencyrow";
import { useEffect, useState } from "react";
const BASE_URL =
"http://api.exchangeratesapi.io/v1/latest?access_key=1fe1e64c5a8434974e17b04a023e9348";
function App() {
const [currencyOptions, setCurrencyOptions] = useState([]);
const [fromCurrency, setFromCurrency] = useState();
const [toCurrency, setToCurrency] = useState();
const [exchangeRate, setExchangeRate] = useState();
const [amount, setAmount] = useState(1);
const [amountInFromCurrency, setAmountInFromCurrency] = useState(true);
let toAmount, fromAmount;
if (amountInFromCurrency) {
fromAmount = amount;
toAmount = fromAmount * exchangeRate;
} else {
toAmount = amount;
fromAmount = amount / exchangeRate;
}
useEffect(() => {
fetch(BASE_URL)
.then((res) => res.json())
.then((data) => {
const firstCurrency = Object.keys(data.rates)[0];
setCurrencyOptions([Object.keys(data.rates)]);
setFromCurrency(data.base);
// console.log(currencyOptions);
setToCurrency(firstCurrency);
setExchangeRate(data.rates[firstCurrency]);
});
}, []);
function handleFromAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(true);
}
function handleToAmountChange() {
// setAmount(e.target.value);
setAmountInFromCurrency(false);
}
return (
<>
<h1>Convert</h1>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={fromCurrency}
onChangeCurrency={(e) => {
setFromCurrency(e.target.value);
}}
amount={fromAmount}
onChangeAmount={handleFromAmountChange}
/>
<div className="equals">=</div>
<Currencyrow
currencyOptions={currencyOptions}
selectedCurrency={toCurrency}
onChangeCurrency={(e) => {
setToCurrency(e.target.value);
}}
amount={toAmount}
onChangeAmount={handleToAmountChange}
/>
</>
);
}
export default App;
When I run the app the option element is still blank.
Is there a way to populate option html tag with an array in react?
This is possible. Just as a tip, you can always try hardcoding currencyOptions in your CurrencyRow and test it out.
Looking through your code, firstly it may be not what you want wrapping Object.keys() in an additional array in setCurrencyOptions([Object.keys(data.rates)]). Object.keys() already returns an array. You probably are not accessing the actual options in your currencyOptions.map((option) => ..). Try setting the keys array directly like this setCurrencyOptions(Object.keys(data.rates)).
Secondly, you should return the desired value inside map by either using it as an arrow function or adding the return keyword in front of the option JSX.
Other than that, is there any error displayed in the browser console? And it would certainly help you to log the mapped option to the console and see what you are actually getting from it.
Your map function should return a value.
<select>{numbers.map((m)=>{return(<option>{m}</option>)})}</select>

Clear button in React clears input but doesn't reset the array element

I try to make a simple meme generator where a user can add a text and change the image on click. Both is working but my clear-button only clears the input field and don't get back to the first image (array[o]).
I mean if I conole.log the "element" it says "0" but it don't change the image to the first one.
My code of App.js so far:
import React, { useEffect, useState } from "react";
import "./styles.css";
function useCounter(initialCount = 0) {
const [count, setCount] = React.useState(initialCount);
const increment = React.useCallback(() => setCount((c) => c + 1), []);
return { count, increment };
}
export default function App() {
let { count: element, increment } = useCounter(0);
const [memes, setMemes] = useState([]);
const [topText, setTopText] = useState("");
useEffect(() => {
async function asyncFunction() {
const initialResponse = await fetch("https://api.imgflip.com/get_memes");
const responseToJSON = await initialResponse.json();
setMemes(responseToJSON.data.memes);
}
asyncFunction();
}, []);
const clear = (e) => {
setTopText("");
element = 0;
console.log(element);
};
return (
<div className="App">
{memes[0] ? (
<div
style={{
height: "300px",
backgroundImage: `url(${memes[element].url})`
}}
>
<p>{topText}</p>
<input
value={topText}
onChange={(e) => setTopText(e.target.value)}
type="text"
/>
<button onClick={clear} type="reset">Clear</button>
<button onClick={increment}>Change Image</button>
</div>
) : (
"loading"
)}
</div>
);
}
What is wrong?
You are attempting to mutate state. You should never directly assign a new value to a stateful variable element = 0. You should use the provided updater function from useState (setCount).
One solution would be to add a reset function to your custom hook and use it:
function useCounter(initialCount = 0) {
const [count, setCount] = React.useState(initialCount);
const increment = React.useCallback(() => setCount((c) => c + 1), []);
const reset = () => setCount(initialCount);
return { count, increment, reset };
}
In your component:
const { count: element, increment, reset: resetCount } = useCounter(0);
const clear = (e) => {
setTopText("");
resetCount();
};
Notice I've also changed the custom hook to use a const instead of let. This is recommended to encourage immutable usage of state, and give helpful errors when breaking that rule.

Prevent checkboxes from re-rendering in React

I'm trying to render multiple checkboxes based on dynamic return data and have their checked status stored in a local state.
However the performance starts to degrade when higher number of checkboxes are generated. I noticed the issue is due to the constant re-rendering of ALL the checkboxes whenever any one of them is checked (checkbox states are all stored in the same object with different keys)
Here is my sample code and a codesandbox link to see the actual performance issue (notice the delay when a checkbox is selected)
export default function App() {
const [checkboxResponse, setCheckboxResponse] = useState([]);
const [checkboxList, setCheckboxList] = useState();
const [checkboxStates, setCheckboxStates] = useState({});
useEffect(() => {
//Generate dummy data
const dummyData = [];
for (let i = 0; i < 1000; i++) {
dummyData.push(i.toString());
}
//Set dummyData as checkbox dynamic data
setCheckboxResponse(dummyData);
}, []);
useEffect(() => {
//When checkbox is clicked, add to local checkbox states object
const checkboxChange = key => event => {
setCheckboxStates({ ...checkboxStates, [key]: event.target.checked });
};
//Check if data is available
if (checkboxResponse) {
const checkboxes = checkboxResponse.map(key => {
const value = checkboxStates[key] ? checkboxStates[key] : false;
//Render checkbox
return (
<FormControlLabel
key={key}
checked={value}
control={
<Checkbox
size="small"
value={key}
onChange={checkboxChange(key)}
/>
}
label={key}
/>
);
});
setCheckboxList(checkboxes);
}
}, [checkboxResponse, checkboxStates]);
return (
<div className="App">
{checkboxList}
</div>
);
}
CodeSandbox
It seems that whenever checkboxStates is changed, the useEffect hook is re-run, triggering a re-render of all the checkboxes again.
Is it possible to prevent React from re-rendering all the checkboxes again whenever the state is changed? Or do we have to create a separate state for every single checkbox dynamically?
Any help would be greatly appreciated.
You can use React.memo to prevent re-render of unchanged check-boxes. Like this:
import React, { useState, useEffect } from "react";
import { Checkbox, FormControlLabel } from "#material-ui/core";
import "./styles.css";
export default function App() {
const [checkboxResponse, setCheckboxResponse] = useState([]);
const [checkboxStates, setCheckboxStates] = useState({});
//When checkbox is clicked, add to local checkbox states object
const checkboxChange = key => event => {
setCheckboxStates({ ...checkboxStates, [key]: event.target.checked });
};
useEffect(() => {
//Generate dummy data
const dummyData = [];
for (let i = 0; i < 1000; i++) {
dummyData.push(i.toString());
}
//Set dummyData as checkbox dynamic data
setCheckboxResponse(dummyData);
}, []);
return (
<div className="App">
{checkboxResponse.map(key => {
const value = checkboxStates[key] ? checkboxStates[key] : false;
//Render checkbox
return (
<FormControlLabel
key={key}
checked={value}
control={<MemoCheckbox key={key} checkboxChange={checkboxChange} />}
label={key}
/>
);
})}
</div>
);
}
const CustomCheckbox = ({ key, checkboxChange }) => (
<Checkbox size="small" value={key} onChange={checkboxChange(key)} />
);
const MemoCheckbox = React.memo(
CustomCheckbox,
(prev, next) => prev.key === next.key
);
However, it might still be not fast enough as when you click the checkbox it still loops trough .map and creates elements.
Here is docs reference for Memo

Categories

Resources