Props is updating before useEffect side effect - reactjs

I have the following component which shows a question, there's a button within it that allows you to reveal the answer, this is handled through the revealedResults property/state.
const Question = ({
item
}: {
item: QuestionType;
}) => {
const [revealedResults, setRevealedResults] = useState(false);
const { question, answers } = item;
useEffect(() => {
setRevealedResults(false);
}, [item]);
const handleResultReveal = () => {
setRevealedResults(true);
};
return (
<section>
<h1>Question: {question}</h1>
<button onClick={() => handleResultReveal()}>Reveal Answer</button>
<div>
{revealedResults && answers.map((answer) => <p>{answer}</p>)}
</div>
</section>
);
};
export default Question;
const Questionaire = () => {
const [question, setQuestion] = useState(questions[0]);
const [correctAnswers, setCorrectAnswers] = useState(0);
const [incorrectAnswers, setIncorrectAnswers] = useState(0);
const handleQuestionAnswer = (isCorrect: boolean): void => {
if (isCorrect) {
setCorrectAnswers(correctAnswers + 1);
} else {
setIncorrectAnswers(incorrectAnswers + 1);
}
setQuestion(questions[1]);
};
return (
<>
<Question item={question} />
<section>
<div>
<p> Did you get the answer correct?</p>
<button onClick={() => handleQuestionAnswer(true)}>Yes</button>
<button onClick={() => handleQuestionAnswer(false)}>No</button>
</div>
</section>
</>
);
};
export default Questionaire;
The question updates through the item prop. The idea is that when the item prop updates setRevealedResults is ran again to hide the revealed result of the next question.
The problem I'm having is that the prop of the new question is being flashed right before the useEffect side effect is being ran. You can see this here:
What is the correct way to deal with this?

useEffect runs after the render is done. That's why you see the page change for a moment there.
Try to use useMemo instead. It should update during the render.

Related

How can I return my todos after I remove some through splice in this particular code

I was watching a tutorial on how to make todos, though my main focus was local storage use.
But when he made the delete button then I was a bit confused, the code below shows how he did it but I am not getting it.
Can anyone explain that I tried using the splice method to remove items from the array but I am not able to remove the items from the page?
Can you also suggest what should I do after using splice to return the array on the page?
Below is the code,
import "./styles.css";
import { useState, useEffect } from 'react'
import Todoform from './TodoForm'
export default function App() {
const [list, setlist] = useState("");
const [items, setitems] = useState([])
const itemevent = (e) => {
setlist(e.target.value);
}
const listofitem = () => {
setitems((e) => {
return [...e , list];
})
}
const deleteItems = (e) => {
// TODO: items.splice(e-1, 1);
// Is there any other way I can do the below thing .i.e
// to remove todos from page.
// this is from tutorial
setitems((e1)=>{
return e1.filter((er , index)=>{
return index!=e-1;
})
})
}
return (
<>
<div className='display_info'>
<h1>TODO LIST</h1>
<br />
<input onChange={itemevent} value={list} type="text" name="" id="" />
<br />
<button onClick={listofitem} >Add </button>
<ul>
{
items.map((e, index) => {
index++;
return (
<>
<Todoform onSelect={deleteItems} id={index} key={index} index={index} text={e} />
</>
)
})
}
</ul>
</div>
</>
)
}
And this is the TodoForm in this code above,
import React from 'react'
export default function Todoform(props) {
const { text, index } = props;
return (
<>
<div key={index} >
{index}. {text}
<button onClick={() => {
props.onSelect(index)
}} className="delete">remove</button>
</div>
</>
)
}
Here is the codeSandbox link
https://codesandbox.io/s/old-wood-cbnq86?file=/src/TodoForm.jsx:0-317
I think one issue with your code example is that you don't delete the todo entry from localStorage but only from the components state.
You might wanna keep localStorage in sync with the components state by using Reacts useEffect hook (React Docs) and use Array.splice in order to remove certain array elements by their index (Array.splice docs).
// ..
export default function App() {
const [list, setlist] = useState("");
const [items, setitems] = useState([])
/* As this `useEffect` has an empty dependency array (the 2nd parameter), it gets called only once (after first render).
It initially retrieves the data from localStorage and pushes it to the `todos` state. */
useEffect(() => {
const todos = JSON.parse(localStorage.getItem("notes"));
setitems(todos);
}, [])
/* This `useEffect` depends on the `items` state. That means whenever `items` change, this hook gets re-run.
In here, we set sync localStorage to the current `notes` state. */
useEffect(() => {
localStorage.setItem("notes", JSON.stringify(items));
}, [items])
const itemevent = (e) => {
setlist(e.target.value);
}
const listofitem = () => {
setitems((e) => {
return [...e , list];
})
}
const deleteItems = (index) => {
// This removes one (2nd parameter) element(s) from array `items` on index `index`
const newItems = items.splice(index, 1)
setitems(newItems)
}
return (
<>
{/* ... */}
</>
)
}
There are multiple ways to remove an item from a list in JS, your version of splicing the last index is correct too and it is able to remove the last item. What it can't do is update your state.
His code is doing two things at the same time: Removing the last item of the Todo array and then, setting the resulted array in the state which updates the todo list.
So, change your code as
const deleteItems = (e) => {
let newItems = [...items];
newItems.splice(e-1, 1);
setitems(newItems);
}

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

useState method not updating state with onClick?

I am creating a custom multiple choice question, but I am having difficulties updating my selection choice using useState.
const QuestionPage = ({ audioFiles }) => {
const [choice, setChoice] = useState(-1); // -1 is when none of the choices are selected
const updateChoice = val => {
setChoice(val);
}
return (
// each MultipleChoice contains an audio file and a radio button
<MultipleChoice audioFiles={audioFiles} choice={choice} updateChoice={updateChoice} />
)
};
const MultipleChoice = ({ audioFiles, choice, updateChoice }) => {
const answerOption = audioFiles.map((item, key) =>
<AudioButton file={file} index={key} choice={choice} updateChoice={updateChoice} />
);
return (
{answerOption}
);
}
const AudioButton = ({ file, index, choice, updateChoice }) => {
const handleClick = (val) => {
updateChoice(val);
};
const radioButton = (
<div className={`${index === choice ? "selected" : ""}`} onClick={() => handleClick(index)}>
</div>
);
return (
<>
{radioButton}
<Audio file={file} />
</>
);
}
In the first function, QuestionPage within updateChoice, when I use console.log(val), it updates according to the selections I make (i.e. 0 and 1). However, when I call console.log(choice), it keeps printing -1.
In addition, I keep getting an error message that says updateChoice is not a function.
Any advice? Thanks in advance!
Looks like you did not pass the value of audioFiles in MultipleChoice function

How to user useState hooks on different instances?

I am pretty new on JavaScript, React, and hooks and I have created a React useState's hook to increase the counter. Based on the following code, the number changes and it goes up on one record, but when I have more than one record all counters increase no matter which button I click. I would like any suggestion as to why it behaves like this will be greatly appreciated:
import React, {useState, useEffect } from 'react';
import { Link } from 'react-router-dom'
import vendors from './vendors.css'
const VendorsShow = (props) => {
const handleDelete = (vendor) => {
props.deleteVendor(vendor.id);
}
const [ count, setCount ] = useState(0)
// similar to componentDidMount:
useEffect(() => {
document.title = `You clicked ${count} times`
})
return (
<div className="vendor-show-div" style={vendors}>
{props.vendors.map((vendor) =>
<ul key={vendor.id}>
<Link to={`/vendors/${vendor.id}`}>{vendor.name}
- ${vendor.total_cost}
</Link>
- <button onClick={() => handleDelete(vendor)}>X</button>
- <button onClick={() => setCount(count + 1)}>{count}</button>
</ul>)}
</div>
);
}
export default VendorsShow;
Each element you are mapping would need its own counter state. Either an array/object of counters, or you abstract a component that maintains its own count state.
I suggest using an object to store count values using the vendor's id property.
const VendorsShow = (props) => {
const handleDelete = (vendor) => {
props.deleteVendor(vendor.id);
};
const [counts, setCounts] = useState({}); // <-- initial empty object for counts
// similar to componentDidMount:
useEffect(() => {
document.title = `You clicked ${Object.values(counts).reduce(
(total, { count }) => total + count,
0
)} times`;
});
// initialize/update state when vendors array changes
useEffect(() => {
setCounts(
props.vendors.reduce(
(counts, { id }) => ({
...counts,
[id]: 0 // <-- store counts by vendor id
}),
{}
)
);
}, [props.vendors]);
const handleCount = (id) => () =>
setCounts((counts) => ({
...counts,
[id]: counts[id] + 1 // <-- update specific vendor's count
}));
return (
<div className="vendor-show-div" style={vendors}>
{props.vendors.map((vendor) => (
<ul key={vendor.id}>
<Link to={`/vendors/${vendor.id}`}>
{vendor.name}- ${vendor.total_cost}
</Link>
- <button onClick={() => handleDelete(vendor)}>X</button>-{" "}
<button onClick={handleCount(vendor.id)}>{counts[vendor.id]}</button>
</ul>
))}
</div>
);
};
In your code, count is an independent state value not depends on your venders.
You need to include count variable for each vender and update them accordingly.
Something like this :
const [venders, setVenders] = setState(venders);
<button onClick={() => {
var temp = [...venders];
temp [i].count = temp [i].count + 1;
setVenders(temp);
}>{venders[i].count}</button>

How can I change 2 different variables independently with a single function in React?

I'm new to React and I have a short and stupid question, but my poor phrasing makes it so that I haven't been able to find the answer by searching for it.
Basically, I have 2 password fields. I want to show and hide each one independently, but I would like a more elegant way than having 2 different functions with their own variables like this:
const [showPassword1, setShowPassword1] = useState(false);
const [showPassword2, setShowPassword2] = useState(false);
const togglePasswordVisiblity1 = () => {
setShowPassword1(showPassword1 ? false : true);
};
const togglePasswordVisiblity2 = () => {
setShowPassword2(showPassword2 ? false : true);
};
With the respective buttons below:
<span onClick={togglePasswordVisiblity1}>Show/Hide</span>
<span onClick={togglePasswordVisiblity2}>Show/Hide</span>
I'm sure there's a way to regroup these into a single function that changes the right variable based on which span is clicked, but I haven't had any luck finding the syntax. Sorry again for this question, hopefully it can be answered quickly!
Thanks in advance for your help.
You can try to use an array for the state. Check this sandbox demo:
import React, { useState } from "react";
export default function App() {
const [showPassword, setShowPassword] = useState([false, false]);
return (
<div className="App">
<button
onClick={() => setShowPassword([!showPassword[0], showPassword[1]])}
>
{JSON.stringify(showPassword[0])}
</button>
<button
onClick={() => setShowPassword([showPassword[0], !showPassword[1]])}
>
{JSON.stringify(showPassword[1])}
</button>
</div>
);
}
Refactored version by extracting state update into a function:
import React, { useState } from "react";
export default function App() {
const [showPassword, setShowPassword] = useState([false, false]);
const togglePassword = (idx) => {
const newShowPassword = [...showPassword];
newShowPassword[idx] = !newShowPassword[idx]; // toggle
setShowPassword(newShowPassword); // update the state
};
return (
<div className="App">
<button onClick={() => togglePassword(0)}>
{JSON.stringify(showPassword[0])}
</button>
<button onClick={() => togglePassword(1)}>
{JSON.stringify(showPassword[1])}
</button>
</div>
);
}
const [state , setState] = useState({
showPassword1:false,
showPassword2: false
})
const togglePasswordVisiblity1= e => {
const {name , value} = e.target
setState( prevState => ({
...prevState,
[name]: prevState[name] ? false : true
}))
}
//
<span name='showPassword1' onClick={togglePasswordVisiblity1}>Show/Hide</span>
<span name='showPassword2' onClick={togglePasswordVisiblity1}>Show/Hide</span>

Resources