React js onClick priority or UseState issues? [duplicate] - javascript

This question already has answers here:
React Native console.log old value useState
(4 answers)
Closed 3 months ago.
i have problem with onClick function on element in React.
const [selectedGenre, updateSelectedGenre] = React.useState("s");
function update(genre) {
updateSelectedGenre(genre);
console.log(selectedGenre);
}
const Genres = (genreIds) => {
return genreIds.map((genreId, index) => (
<span
style={{
cursor: "pointer",
}}
onClick={() => {
update(genreId);
}}
>
{genreId}{" "}
</span>
));
};
When i click on span first time, console log of selectedGenre is "s",which is default. WHen i click second time, its updated. Why is it like that ? my updateSelectedGenre is before console.log.
Thank you very much.

the setter in useState api is an asynchronous event, therefore you can not trust that calling console.log() will give you the new state.
The desired result can be achieved by
useEffect(() => {
console.log(selectedGenre);
},[selectedGenre]);

Usually inside functions you can't console.log the state right after setting it, because something called as asynchrony, if you really wan't to log the change you should use useEffect
useEffect(() => {console.log(selectedGenre)},[selectedGenre])

Related

Add element to array setState React

I have useState "setAnswers" (set) and "answers" (get) (answers is array with strings)
and click trigger:
onClick = () => {
setAnswers((prev) => [...prev, e])
setValue(questionKey, answers)
console.log(watch(questionKey))
}
but with ever click i got only previous value
In fact, your console.log is execute before the state finish to be calculated, if you put your console.log on an other place, normally, you find what you want.
Try it, and say me
Your console.log(watch(questionKey)) all time will go wrong on onChange.
you need use a useEffect to log or make anything else with state as base.
useEffect(() => {
console.log(watch(questionKey)
}, [questionKey]);
to more info you can read here:
https://dev.to/mpodlasin/react-useeffect-hook-explained-in-depth-on-a-simple-example-19ec
I think you are a little bit confused: watch function from useForm is used to
Watch specified inputs and return their values.
So console.log(watch(questionKey)) does make no sense.
watch should be used in this way:
React.useEffect(() => {
const subscription = watch((value, { name, type }) => console.log(value, name, type));
return () => subscription.unsubscribe();
}, [watch]);
You use a useEffect to subscribe/unsubscribe watch on some form fields, and then in component's body you could call:
const watchSomething = watch(<something>);
and, every time the field called <something> will change his value, watch will execute console.log(value, name, type).
If you want to print the very last value of a state variable, you should know that setValue, setAnswer are async functions and its absolutely not guaranteed that on next line you could log the last value.
To solve your problem you have 2 choices:
use watch correctly;
forget watch and use classic useEffect:
onClick = () => {
setAnswers((prev) => [...prev, e])
setValue(questionKey, answers)
}
useEffect(() => {
console.log(questionKey); //<-- here you will print the very last value of questionKey
}, [questionKey]);
Here a guide on how to use watch.

React.js | Infinite render if I pass setState as a callback function, even after destructuring props

Issue
I have a child component that gets some button id-name configs as props, renders selectable HTML buttons according to those configs and returns the selected button's value(id) to the callback function under a useEffect hook. However it causes an infinite render loop because I need to pass the props as a dependency array. Note that React.js wants me to destructure props, but it still causes an infinite render loop even if I do that.
Child Component
import React, {createRef, useState, useEffect} from "react";
const OptionButton = ({ buttons, buttonClass, callback }) => {
const [value, setValue] = useState()
const refArray = []
const buttonWidth = ((100 - (Object.keys(buttons).length - 1)) - ((100 - (Object.keys(buttons).length - 1)) % Object.keys(buttons).length)) / Object.keys(buttons).length
useEffect(() => {
if (callback) {
callback(value);
}
}, [value, callback])
const select = (event) => {
event.target.style.backgroundColor = "#10CB81"
refArray.forEach((currentRef) => {
if (currentRef.current.id !== event.target.id) {
currentRef.current.style.backgroundColor = "#F5475D"
}
})
setValue(event.target.id)
}
return(
<span style={{display: "flex", justifyContent: "space-between"}}>
{Object.entries(buttons).map((keyvalue) => {
const newRef = createRef()
refArray.push(newRef)
return <button ref={newRef} id={keyvalue[0]} key={keyvalue[0]} className={buttonClass} onClick={select} style={{width: `${buttonWidth}%`}}>{keyvalue[1]}</button>
})}
</span>
)
}
export default OptionButton
So as you can see here my child component gets button configs as key-value (button value-button name) pairs, renders these buttons and when user clicks one of these buttons it gets the id of that button, sets it to 'value' constant using useState hook and then passes that value to parent component callback.
Parent Component
return(
<OptionButton buttons={{"ButtonValue": "ButtonName", "Button2Value": "Button2Name"}} callback={(value) => this.setState({buttonState: value})} buttonClass="buttonclass"/>
)
It's just fine if I don't use this.setState at the callback function. For example if I just do
(value) => console.log(value)
there is no infinite loop. As far as I can tell it only happens if I try to use setState.
What am I doing wrong? How can I fix this? Thanks in advance.
The reason why there is infinite loop is because you have callback as dependency in useEffect. And what you are passing to the component is you pass a new callback function on each new render, hence it always enters useEffect because of that. Since you are using classes consider passing instance method as callback instead of arrow function as here.
Also I think you are overusing refs. You could also achieve what you are doing by just storing say the id of clicked button, and then dynamically styling all buttons, e.g. inside map if id of current button is same as the one stored, use #10CB81 bg color in style object, otherwise different one.
Also there are better ways to check which btn was clicked, see here.

Why useState hook changing the state as the previous click? [duplicate]

This question already has answers here:
Why does calling react setState method not mutate the state immediately?
(9 answers)
Closed 1 year ago.
I was using useState hook and it seems that the hook is not working properly. When I click on one of my radio buttons initially it logs nothing, but after my second click it logs the previous one that I clicked. Every time I click on any button It logs the button that I previously clicked. #sidenote: (The 'info' that I've imported is an array of objects and each 'incorrect_answers' property has an array as value) . Here is my code:
import React, {useState} from 'react'
import { info } from "./data";
const Quiz = () => {
const [i, setI] = useState(0);
const [value, setValue] = useState('');
const {correct_answer, incorrect_answers} = info[i]
const arr = [correct_answer, ...incorrect_answers].sort((a, b) => a.length - b.length);
console.log(arr)
const handleSubmit = (e) => {
e.preventDefault();
setI(i + 1);
}
const handleChange = (e) => {
setValue(e.target.value);
console.log(value);
}
return (
<div className="quiz">
<form className='quiz__form' onSubmit={(e) => handleSubmit(e)}>
<div className="form__body" >
{arr.map((item, index) => {
return (
<div className="form__item" key={index}>
<label htmlFor={`option-${index}`}>
<input
type="radio"
name="options"
id={`option-${index}`}
value={item}
onClick={(e) => handleChange(e)}
/> {item}
</label>
</div>
)
})}
</div>
</form>
</div>
)
}
Can anyone tell me where I am wrong?
setValue is asynchronus. So you will have to wait to see the change. One thing you can do to see the change is add this following code
useEffect(() => {
console.log(value)
}, [value])
This way you can see when the value of the value changes
The setState function in reactjs (setValue in your case) is an asynchronous function, it update the state at the end of the event handler (handleChange), so the value variable wil still refer the previous state inside the event handler (handleChange).
Try to add console.log before the returned JSX, then click on input radio you see log after the event handler was executed
const handleChange = (e) => {
setValue(e.target.value);
console.log(value);
}
console.log("value after handle event",value);
return (
For more information about hou reactjs update state please check here

Can I omit Return value in arrow function in JavaScript?

I've been struggled from this for a long time. we all know that arrow function simplifies the syntax, like this:
A. Arrow function:
onClick={() => toggleTodo(todo.id)}
B. Expand the Arrow function:
onClick={() => {
// we can see there is a return keyword here. can I just remove the return keyword ?
return toggleTodo(todo.id);
^^^^^^
}}
In the official redux tutorial, the file AddTodo.js:
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
const AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form
onSubmit={e => { // focus on this line
e.preventDefault()
dispatch(addTodo(input.value)). // focus on this line, if i put return here, the code below won't be executed. if not, how to explain the syntax in the beginning?
input.value = ''
}}
>
<input ref={node => (input = node)} />
<button type="submit">Add Todo</button>
</form>
</div>
)
}
export default connect()(AddTodo)
The question is: in the onSubmit, why we don't need to put a return inside the function body ? Thank you.
You only need to return something if that returned value will be used somewhere. In the case of onSubmit, there is no need to return anything. That (arrow) function is simply running some code when the form is submitted.
In point B in your question, yes, you can remove return if nothing needs to be done with the returned result of toggleTodo(todo.id).
They are both arrow functions
onClick={() => toggleTodo(todo.id)}
Short version
onClick={() => {
toggleTodo(todo.id);
}}
Long version
onClick={() => {
toggleTodo(todo.id);
toggleTodo2(todo.id);
toggleTodo3(todo.id);
}}
Long version can have multiple calls, return not required
You need a return value when you use a synchronized function and need its result.
Whenever you use an asynchronous function, it means that the calling function already continued with itself and moving to the next phase will be via a callback function.
In your case the window object is the caller of the onClick, so there's no reason to return a value to it, it will have nothing to do with it.
You do want to trigger react's rendering mechanism so you use the dispatch callback/trigger.

How to change button text in ReactJS

How to change the button text when someone click on?
Code:
<Button disabled={this.state.disabled}
type="primary"
htmlType="submit"
style={{
background: '#ff9700',
fontWeight: 'bold',
border: 'none',
float: 'center',
}}
loading={this.state.loading}
onClick={this.enterLoading}
value="Next"
id="buttontext"
onClick="changeText()"
>
Next
</Button>
Mayank is correct.
Create a variable called "text" (or whatever you choose) and put that instead of "Next".
state = {
text: "Next"
}
changeText = (text) => {
this.setState({ text });
}
render() {
const { text } = this.state //destucture state
return (
<Button
onClick={ () => { this.changeText("newtext")} }> {text} </Button> )...etc
Note: this method will always change the text to "newtext" when you click. You can pass a variable there as well to make it more dynamic.
Hope this helps.
Update: Just saw Mayank comment. That code is essentially what I have. Just a tip you no longer need a constructor and you don't have to bind your methods anymore.
Updated: React Hooks
Same thing but with the useState hook. Instead of calling the state variable text, I am using buttonText to be more explicit. The updated version would look like:
import { useState } from 'React';
const [buttonText, setButtonText] = useState("Next"); //same as creating your state variable where "Next" is the default value for buttonText and setButtonText is the setter function for your state variable instead of setState
const changeText = (text) => setButtonText(text);
return (
<Button onClick={() => changeText("newText")}>{buttonText}</Button>
)
You can omit the changeText function all together and have this:
return (
<Button onClick={() => setButtonText("newText")}>{buttonText}</Button>
)
Updated: How to Add Set Timeout
Adding an update to answer a question in the comments: "If I wanted to use a setTimout to bring the button back to the previous text after 1 second where would I add that in?"
There are two ways that comes to mind: add the setTimeout to the changeText function or create an effect that depends on the buttonText.
change text
You can just pop the setTimeout right in this function.
Goes from this
const changeText = (text) => setButtonText(text);
to this
const initialState = "Next";
const [buttonText, setButtonText] = useState(initialState); //same as creating your state variable where "Next" is the default value for buttonText and setButtonText is the setter function for your state variable instead of setState
const changeText = (text) => {
setButtonText(text);
setTimeout(() => setButtonText(initialState), [1000])
}
We add the initialState variable as a const to keep track of the "previous text". Since, it should never change we could define it in all caps snake case like const INITIAL_STATE meh your choice.
useEffect
We still need to define that initialState variable, again so we can keep track of the original. Then we can create a useEffect which is a React hook that allows you to "hook" into changes of a variable (that's only a part of useEffect, just enough to get us going here).
We can break the effect down into two essential parts: the body or callback of the effect, what do we want to do when the effect runs and the dependency or what triggers the effect to run. In this case, our callback will be setTimeout and set the button text inside that timeout and our buttonText will trigger the effect.
Here's the effect:
useEffect(() => {
if(buttonText !== initialState){
setTimeout(() => setButtonText(initialState), [1000])
}
}, [buttonText])
Anytime the state variable buttonText changes this effect will run.
We start at
buttonText = initialState // "Next"
the effect runs and checks the if. Since buttonText equals the initialState the conditions evaluates to false and we terminate the callback and the effect.
When the user clicks the button, changeText executes and sets the buttonText state which updates the variable triggering the effect. Now we run that if check again and this time it passes so we execute the setTimeout.
Inside the timeout we are setting state so the effect runs again and this time it fails because we just changed the state back to initialState.
I recommend throwing a debugger in there or some logs to follow the trail
Long winded explanation. Here's what the whole component would look like using the effect approach.
import React, { useState, useEffect } from "react";
const FancyButton = () => {
const initialState = "Next";
const [buttonText, setButtonText] = useState("Next"); //same as creating your state variable where "Next" is the default value for buttonText and setButtonText is the setter function for your state variable instead of setState
// the effect
useEffect(() => {
if(buttonText !== initialState){
setTimeout(() => setButtonText(initialState), [1000])
}
}, [buttonText])
const changeText = (text) => setButtonText(text);
return (
<button type="button" onClick={() => changeText("newText")}>{buttonText}</button>
)
};
I added the type on the button because that's a good practice. And changed "Button" to "button". You can certainly have any component there you want, this works better for copying and pasting
Where you wrote "Next", the button text, do this instead:
{this.state.disabled ? 'Disabled...' : 'Next'}
I this will display "Disabled..." when the state.disabled == true, and 'Next' when state.disabled == false.

Categories