if(state){useState} delay by one Event - reactjs

this is, what I want: I click on the div as a checkbox, and if it's clicked, the NEXT-Button should be enabled. But with if (checkboxClass === "checkbox-clicked") the update comes one click later.
I click on the check: nothings happend,
I uncheck: the button is enabled.
how can i get an instand update ?
import React, { useState } from "react";
import "../css/MainPages.css";
import "../css/Request.css";
function Test() {
const [checkboxClass, setClassName] = useState("checkbox");
const [buttonState, setButtonState] = useState(true);
function handleClass() {
if (checkboxClass == "checkbox") {
setClassName("checkbox-clicked");
}
checkButton();
}
function checkButton() {
if (checkboxClass === "checkbox-clicked") {
setButtonState(false);
}
}
return (
<>
<div onClick={handleClass} className={checkboxClass} />
<button disabled={buttonState} className="btn btn-transparent">
Next
</button>
</>
);
}
export default Test;

Yes, It's because setButtonState is not executed immediately. You must think, that setState is request to change the value and react by self will decides when it will be executed.
Your checkButton() method is immediately after setState, therefore when checkButton method is executes, that state is still not changed (but will change, few miliseconds later).
To solve you situation, you can use useEffect react hook, which will be listening on checkbox state change, and when state will be changed, useEffect will by automaticaly executed:
// Second parameter is, to which states will be useEffect listening
useEffect(() => { checkButton(); }, [checkboxClass])

You're using derived state, which generally leads to unexpected behaviors. You can (and imo you should) disable the button based on the checkboxClass state variable:
import React, { useState } from "react";
import "../css/MainPages.css";
import "../css/Request.css";
function Test() {
const [checkboxClass, setClassName] = useState("checkbox");
const isButtonDisabled = checkboxClass === "checkbox-clicked"
function toggleCheckboxClass() {
setClassName(prevClassName => prevClassName === "checkbox" ?
"checkbox-clicked" : "checkbox")
}
return (
<>
<div onClick={toggleCheckboxClass} className={checkboxClass} />
<button disabled={isButtonDisabled} className="btn btn-transparent">
Next
</button>
</>
);
}
export default Test;
Another opinion:
I would not use a div if you are actually rendering a checkbox and set the state variable based on e.target.checked since it is not semantically correct.

Related

loading components twice, probably because of useEffect wrong set-up

I have built a ToDo React App (https://codesandbox.io/s/distracted-easley-zjdrkv) that does the following:
User write down an item in the input bar
User hit "enter"
Item is saved into the list below (local storage, will update later)
There is some logic to parse the text and identify tags (basically if the text goes "#tom:buy milk" --> tag=tom, text=buy milk)
The problem I am facing are:
useEffect runs twice at load, and I don't understand why
After the first item gets saved, if I try saving a second item, the app crashes. Not sure why, but I feel it has to do with the point above...and maybe the event listener "onKeyDown"
App
import { useState, useEffect } from 'react'
import './assets/style.css';
import data from '../data/data.json'
import InputBar from "./components/InputBar/InputBar"
import NavBar from "./components/NavBar/NavBar"
import TabItem from "./components/Tab/TabItem"
function App() {
const [dataLoaded, setDataLoaded] = useState(
() => JSON.parse(localStorage.getItem("toDos")) || data
)
useEffect(() => {
localStorage.setItem("toDos", JSON.stringify(dataLoaded))
console.log('update')
}, [dataLoaded])
function deleteItem(id){
console.log(id)
setDataLoaded(oldData=>{
return {
...oldData,
"items":oldData.items.filter(el => el.id !== id)
}
})
}
return (
<div className='container'>
<NavBar/>
<InputBar
setNewList = {setDataLoaded}
/>
{
//Items
dataLoaded.items.map(el=>{
console.log(el)
return <TabItem item={el} key={el.id} delete={deleteItem}/>
})
}
</div>
)
}
export default App
InputBar
import { useState, useEffect } from 'react'
import { nanoid } from 'nanoid'
import '../../assets/style.css';
export default function InputBar(props){
const timeElapsed = Date.now();
const today = new Date(timeElapsed);
function processInput(s) {
let m = s.match(/^(#.+?:)?(.+)/)
if (m) {
return {
tags: m[1] ? m[1].slice(1, -1).split('#') : ['default'],
text: m[2],
created: today.toDateString(),
id:nanoid()
}
}
}
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
e.target.value=""
}
}
return(
<div className="main-input-div">
<input type="text" onKeyDown={(e) => handleKeyDown(e)}/>
</div>
)
}
Tab
import { useState } from 'react'
import "./tab-item.css"
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrash } from "#fortawesome/free-solid-svg-icons";
export default function TabItem(props) {
return (
<div className="tab-item">
<div className="tab-item-text">{props.item.text}</div>
<div className="tab-item-actions">
<FontAwesomeIcon icon={faTrash} onClick={()=>props.delete(props.item.id)}/>
</div>
<div className="tab-item-details">
<div className="tab-item-details-tags">
{
props.item.tags.map(el=><div className="tab-item-details-tags-tag">{el}</div>)
}
</div>
</div>
<div className="tab-item-date">{props.item.created}</div>
</div>
)
}
The above answer is almoost correct. I am adding more info to the same concepts.
useEffect running twice:
This is most common ask in recent times. It's because the effect runs twice only in development mode & this behavior is introduced in React 18.0 & above.
The objective is to let the developer see & warn of any bugs that may appear due to a lack of cleanup code when a component unmounts. React is basically trying to show you the complete component mounting-unmounting cycle. Note that this behavior is not applicable in the production environment.
Please check https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed for a detailed explanation.
App crashes on second time: It's probably because you are trying to update the input value from event.target.value if you want to have control over the input value, your input should be a controlled component meaning, your react code should handle the onChange of input and store it in a state and pass that state as value to the input element & in your onKeyDown handler, reset the value state. That should fix the crash.
export default function InputBar(props){
const [inputVal, setInputVal] = useState("");
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
setInputVal("")
}
}
return(
<div className="main-input-div">
<input
type="text"
value={inputVal}
onChange={(e) => {setInputVal(e.target.value)}}
onKeyDown={(e) => handleKeyDown(e)}
/>
</div>
)
}
Hope this helps. Cheers!
Your app is using strict mode, which in a development mode renders components twice to help detect bugs (https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects).
root.render(
<StrictMode>
<App />
</StrictMode>
);
As for the crash, I think it's happening due to props.setNewList being an asynchronous call and the resetting of e.target.value - something like this seemed to fix it for me:
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
const inputVal = e.target.value;
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(inputVal), ...oldData.items]
}
}
)
e.target.value=""
}
}
I will add, that using document.querySelector to get values isn't typical usage of react, and you might want to look into linking the input's value to a react useState hook.
https://reactjs.org/docs/forms.html#controlled-components

How to setState with select element in react - WITHOUT LAG

There seems to be a lag of one render cycle when I change a select element and when it's state actually changes. I know that there are several similar questions on this but none of them see to work for me. useEffect is still showing the old state from one render cycle before.
Anyone know how to address this?
Parent component code:
import React, {useCallback, useEffect, useState} from 'react'
import Dropdown from '../components/Dropdown.js'
const ValueCalculation = () => {
const industryData = require('../../../backend/standard_data/industry_benchmarks.json')
var industryList = []
industryData.map((record)=>{
var x = record.Industry;
industryList.push(x)
})
const [industry, setIndustry] = useState(industryList[8]);
const updateIndustry = (updatedValue)=>{
console.log(updatedValue); //<--This is updated with the right value!
setIndustry(updatedValue) //<--This is NOT updating the current value
console.log(industry); //<-- This does NOT show the updated value
}
useEffect(()=>{
console.log(industry); //<--Still showing value from previous render cycle
},[])
return (
<div>
<Dropdown
label="Industry"
value={industry}
onChange={(e)=>updateIndustry(e.target.value)}
list={industryList}
/>
</div>
)
}
export default ValueCalculation
Code for Child Dropdown component..
import React from 'react'
const Dropdown = (props) => {
return (
<div className="form-group mb-3">
<label>{props.label}</label>
<select
className="form-control"
value={props.value}
onChange={props.onChange}
>
{
props.list.map(item=>(
<option key={props.list.indexOf(item)} value={item}>{item}</option>
))
}
</select>
</div>
)
}
export default Dropdown
SetState is async so your console.log is going to run before the state has been set. The code you have works correctly as you can see in the sandbox link provided.
const updateIndustry = (updatedValue) => {
//This is updated with the right value!
console.log(updatedValue);
//This is updating correctly and will show on re render
setIndustry(updatedValue);
//This will not work since setState is async
//Console.log() is firing before the state has been set
console.log(industry);
};
As for the useEffect. You will need to add industry as a dependency so that the console.log is called as soon as the state changes :
useEffect(() => {
console.log(industry);
}, [industry]);
Sandbox : https://codesandbox.io/s/hidden-voice-8jvt2f?file=/src/App.js
So, it's a little bit complicated but your state is changing on every rerender cycle, so ur state it's updated after the updateIndustry it's finished (popped out from js callstack). I tested your code, and it is working perfectly and i refactored it a little bit
import React, { useEffect, useState } from "react";
import Dropdown from "./Dropdown.js";
const App = () => {
var industryList = ["a", "b", "c", "d"];
const [industry, setIndustry] = useState(industryList[0]);
useEffect(() => {
console.log(industry);
}, [industry]);
return (
<div>
<Dropdown
label="Industry"
value={industry}
onChange={(e) => setIndustry(e.target.value)}
list={industryList}
/>
</div>
);
};
export default App;
Also, useEffect hook is reexecuted when its dependency changes value, in your case your dependency array is empty so I added [industry] to it.

useEffect triggered after onClick event - with different results

I wanted to create a dropdown menu, which shows itself and hides on hovering, and disappears after clicking its item. I thought I found a way to do it - but it works only sometimes. (Or maybe it doesn't work - but sometimes it does.) Details below:
I gotta DropdownMenu2 component, which display is being toggled by onMouseEnter/Leave events. This component (my dropdown menu) holds inside <NavLink> menu items.
I wanted the dropdown menu to disappear after clicking on menu item, so inside <Navlink> I created onClick event which triggers handleClick. This functions sets a click variable - to a CSS className with display:none. click is then passed to <div> that contains the Dropdown menu.
To toggle the dropdown menu display again on mouse hover, I had to get rid of the click class from the div. For that I created useEffect hook, with click dependency - so it fires every time click state changes. And function inside this hook - changes click value, so it no longer represents the CSS display:none class. So after (2.) - div containing dropdown menu has display:none, disapears, and useEffect erases that - making it hover ready.
problem:
this works only sometimes - sometimes useEffect is triggered so fast after onClick, that the dropdown menu doesn't even drop. ( click changes so fast that div container gets the "erasing" class immediately after display:none class )
NaviMainButtonDrop2
import DropdownMenu2 from "./DropdownMenu2";
import useHoverButton from "./sub-components/useHoverButton";
const NaviMainButtonDrop2 = () => {
const { disp, hoverOn, hoverOff } = useHoverButton();
return (
<li
className={`nav-main__button dropdown-us`}
>
<a
className="hover-pointer"
onMouseEnter={hoverOn}
onMouseLeave={hoverOff}
>
title
</a>
{ disp && <DropdownMenu2 /> }
</li>
)
}
export default NaviMainButtonDrop2
useHoverButton (custom hook for NaviMainButtonDrop2)
import { useState } from "react";
const useHoverButton = () => {
const [disp, setDisp] = useState(false);
const hoverOn = () => setDisp(true)
const hoverOff = () => setDisp(false)
return { disp, hoverOn, hoverOff }
}
export default useHoverButton
DropdownMenu2
import "./DropdownMenu.css"
import { NavLink } from "react-router-dom";
import { MenuItemContentSchool } from "./sub-components/MenuItemContentSchool"
import { useEffect } from "react";
import useAddClass from "./sub-components/useAddClass";
const DropdownMenu2 = () => {
const { click, setClick, handleClick } = useAddClass("hide-menu");
useEffect(() => {
console.log("[usEffect]")
setClick("");
}, [click]);
return (
<div className={`dropdown-holder-us ${click}`}>
{/* here menu unfolds */}
{MenuItemContentSchool.map((item) => {
return (
<NavLink
to={item.link}
className={(navData) => (navData.isActive ? "d-content-us active-style" : 'd-content-us')}
onClick={handleClick}
key={item.id}
>
{item.title}
</NavLink>
)
})}
</div>
)
}
export default DropdownMenu2
useAddClass (custom hook for DropdownMenu2)
import { useState } from "react"
const useAddClass = (className) => {
const [click, setClick] = useState("");
const handleClick = () => setClick(className);
return { click , handleClick }
}
export default useAddClass
I think the issue here is that you are not able to get the latest state whenever you update the next state that is why it works sometimes and sometimes it doesn't.
According to me there could be 2 solutions to this, either use a setTimeout or get the latest state when setting the state.
setTimeout solution-
useEffect(() => {
setTimeout(() => {
setClick("")
},2000)
Try and always get the latest state when you update the next state.
useEffect(() => {
console.log("[usEffect]")
setClick((clickLatest) => "");
}, [click]);
and
const handleClick = () => setClick((clickLatest) => className);
This callback will help the useState wait for the latest state and then update the state further.
I think I just found a simple solution to this. I don't understand why useEffect seems to work in a random timing, but using setTimeOut inside it, and delaying the execution of setClick - seems to do the job.
useEffect(() => {
setTimeout(() => {
setClick("")
},2000)

React Hooks: State Is Not Updating in Function

I have spent several hours trying to fix this issue with no luck. I have a reusable component that starts with a state of any empty object, the object is given a series of properties based off of props, and then based on user input on each set of radio buttons will show a child component.
Unfortunately, the function that is supposed to update the state that will then trigger whether the user sees the child component is not working.
I believe the issue is in my editDisplay function. Currently when I click one of the radio buttons the new object that should become the new state is logged to the console correctly, but when I use React Devtools to inspect the page, I see that state is not updating. That being said, I could see a case where I am misunderstanding useEffect, and perhaps useEffect is running each time editDisplay is running. Any help would be appreciated.
import React, {useState, useEffect} from 'react'
import {Form} from 'react-bootstrap'
import RatingInput from './RatingInput'
export default function MembershipForm({inputs, name, record, setRecord}) {
const [display, setDisplay] = useState({})
useEffect(()=>{
inputs.forEach(input => {
var newDisplay = display
newDisplay[name+input]=false
setDisplay(newDisplay)})
},[])
const editDisplay = (input, visible) => {
var newDisplay =display
newDisplay[name+input]=visible
console.log(newDisplay)
setDisplay(newDisplay)
}
const questions=inputs.map(input => {
return (
<div key={name+input}>
<Form.Group>
<Form.Label className='mx-2'>Do you have a {name} {input} Rating?</Form.Label>
<Form.Check inline label='Yes' value={true} name={name+input} type='radio' className = 'mx-2' onClick ={() =>{editDisplay(input, true)}} />
<Form.Check inline label='No' value ={false} name={name+input} type='radio' className = 'mx-2' defaultChecked onClick = {() =>{editDisplay(input, false)}} />
</Form.Group>
{display[name+input] && <RatingInput input={input}/>}
</div >
)
})
return (
<div className='ml-3'>
{questions}
</div>
)
}
const editDisplay = (input, visible) => {
var newDisplay = display
newDisplay[name+input]=visible
console.log(newDisplay)
setDisplay(newDisplay)
}
You should perform react state update in an immutable way.
What you are doing above is you are mutating existing state variable (that assignment on first line doesn't add much: newDisplay refers to same object as display). In such case react might not detect change. Do this instead:
var newDisplay = { ... display };

Why useState in React Hook not update state

When i try example from React Hook, i get a problem about useState.
In code below, when click button, i add event for document and check value of count.
My expect is get count in console.log and view as the same. But actual, i got old value (init value) in console & new value in view . I can not understand why count in view changed and count in callback of event not change.
One more thing, when i use setCount(10); (fix a number). And click button many time (>2), then click outside, i got only 2 log from checkCount. Is it React watch count not change then don't addEventListener in next time.
import React, { useState } from "react";
function Example() {
const [count, setCount] = useState(0);
const add = () => {
setCount(count + 1);
console.log("value set is ", count);
document.addEventListener("click", checkCount);
};
const checkCount = () => {
console.log(count);
};
return (
<div>
<p>You clicked {count} times</p>
<p>Click button first then click outside button and see console</p>
<button onClick={() => add()}>Click me</button>
</div>
);
}
export default Example;
If you want to capture events outside of your component using document.addEventListener, you will want to use the useEffect hook to add the event, you can then use the useState to determine if your capturing or not.
Notice in the useEffect I'm passing [capture], this will make it so the useEffect will get called when this changes, a simple check for this capture boolean determines if we add the event or not.
By using useEffect, we also avoid any memory leaks, this also copes with when your unmount the component, it knows to remove the event too.
const {useState, useEffect} = React;
function Test() {
const [capture, setCapture] = useState(false);
const [clickInfo, setClickInfo] = useState("Not yet");
function outsideClick() {
setClickInfo(Date.now().toString());
}
useEffect(() => {
if (capture) {
document.addEventListener("click", outsideClick);
return () => {
document.removeEventListener("click", outsideClick);
}
}
}, [capture]);
return <div>
<p>
Click start capture, then click anywhere, and then click stop capture, and click anywhere.</p>
<p>{capture ? "Capturing" : "Not capturing"}</p>
<p>Clicked outside: {clickInfo}</p>
<button onClick={() => setCapture(true)}>
Start Capture
</button>
<button onClick={() => setCapture(false)}>
Stop Capture
</button>
</div>
}
ReactDOM.render(<React.Fragment>
<Test/>
</React.Fragment>, document.querySelector('#mount'));
p { user-select: none }
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="mount"></div>
#Keith i understand your example but when i apply get some confuse. In origin, i always call function is handleClick and still call it after run handleClickOutside but now i don't know how to apply that with hook.
This is my code that i want insted of Hook
class ClickOutSide extends Component {
constructor(props) {
super(props)
this.wrapperRef = React.createRef();
this.state = {
active: false
}
}
handleClick = () => {
if(!this.state.active) {
document.addEventListener("click", this.handleClickOut);
document.addEventListener("contextmenu", this.handleClickOut);
this.props.clickInside();
} else {
document.removeEventListener("click", this.handleClickOut);
document.removeEventListener("contextmenu", this.handleClickOut);
}
this.setState(prevState => ({
active: !prevState.active,
}));
};
handleClickOut = event => {
const { target } = event;
if (!this.wrapperRef.current.contains(target)) {
this.props.clickOutside();
}
this.handleClick()
}
render(){
return (
<div
onDoubleClick={this.props.onDoubleClick}
onContextMenu={this.handleClick}
onClick={this.handleClick}
ref={this.wrapperRef}
>
{this.props.children}
</div>
)
}
}
export default ClickOutSide

Resources