React: How to add a button click handler to reveal text? - reactjs

I want create a button click handler to reveal the answer text "Animal". So far I have <button onClick={this.revealAnswer}>Reveal answer</button> and the handler
revealAnswer = () => { };
What information should I put in the handler?

Use the useState hook.
const [reveal, setReveal] = useState(false);
const revealAnswer = () => {
setReveal(reveal => !reveal)
}
...
return (
...
{reveal && text}
...
)

You can add a state variable (boolean type) to the component, something like const [revealAnswer, setRevealAnswer] = useState(false). Here, 'false' being the default value.
In the handler, you can then update the state every time the button gets clicked.
const revealAnswer = () => {
setRevealAnswer(!revealAnswer);
};
And in your JSX you should have condition based on this variable revealAnswer. Eg:
...
{ revealAnswer && textValue }
Hope that helps!

Related

Value isn't be updated async in React useState (React)

I want to change State with child elements in React. However, when I click once, it is not immediately updated. Click twice, it shows the correct answer.
How to update async?
export default function Example() {
const onClick = async () => {
console.log('a', test)
// should be 'b', but console log 'a'
}
const [test, setTest] = useState('a')
return (
<ClickExample setTest={setTest} onClick={onClick} />
)
}
export default function ClickExample() {
const next = useCallback(
(alphabet: string) => {
setTest(alphabet)
onClick()
},
[onClick, setTest],
)
return <SelectButton onClick={() => next('b')} />
}
You can receive the value to be updated as an argument from the onClick callback. It'll be something like this:
export default function Example() {
const [test, setTest] = useState('a')
const handleClick = (newValue) => {
setTest(newValue);
}
return (
<ClickExample onClick={handleClick} />
)
}
export default function ClickExample({ onClick }) {
return <SelectButton onClick={() => onClick('b')} />
}
NOTE: You should avoid using useCallback() when it is not necessary. Read more over the web but this article from Kent C. Dodds is a good start. As a rule of thumb: Never use useCallback()/useMemo() unless you REALLY want to improve performance after needing that improvement.
In the first render, the value of test is equal to'a'. So when the console.log is executed, it has already captured 'a' as the value of test state. (See closures and stale closures).
One way to fix this would be to create a handleClick function in the parent component which receives the new value of test as its input and set the state and log the new value(which will be updated in the next render) using its argument.
// ClickExample
const handleClick = (alphabet) => {
setTest(alphabet);
console.log('a', alphabet);
};
codesandbox

React state not updating in click event with two functions, but updating with one function

This one has turned out to be a head scratcher for a while now...
I have a react component that updates state on a click event. The state is a simple boolean so I'm using a ternary operator to toggle state.
This works however as soon as I add a second function to the click event state no longer updates. Any ideas why this is happening and what I'm doing wrong?
Working code...
export default function Activity(props) {
const [selected, setSelected] = useState(false);
const selectActivity = () => {
selected ? setSelected(false) : setSelected(true);
return null;
};
const clickHandler = (e) => {
e.preventDefault();
selectActivity();
};
return (
<div
onClick={(e) => clickHandler(e)}
className={`visit card unassigned ${selected ? 'selected' : null}`}
>
//... some content here
</div>
);
}
State not updating...
export default function Activity(props) {
const [selected, setSelected] = useState(false);
const selectActivity = () => {
selected ? setSelected(false) : setSelected(true);
return null;
};
const clickHandler = (e) => {
e.preventDefault();
selectActivity();
props.collectVisitsForShift(
props.day,
props.startTime,
props.endTime,
props.customer
);
};
return (
<div
onClick={(e) => clickHandler(e)}
className={`visit card unassigned ${selected ? 'selected' : null}`}
>
//... some content here
</div>
);
}
I went for a walk and figured this one out. I'm changing state in the parent component from the same onClick event, which means the child component re-renders and gets its default state of 'false'.
I removed the state change from the parent and it works.
Thanks to Andrei for pointing me towards useCallback!
I loaded your code in a CodeSandbox environment and experienced no problems with the state getting updated. But I don't have access to your collectVisitsForShift function, so I couldn't fully reproduce your code.
However, the way you're toggling the state variable doesn't respect the official guidelines, specifically:
If the next state depends on the current state, we recommend using the updater function form
Here's what I ended up with in the function body (before returning JSX):
const [selected, setSelected] = useState(false);
// - we make use of useCallback so toggleSelected
// doesn't get re-defined on every re-render.
// - setSelected receives a function that negates the previous value
const toggleSelected = useCallback(() => setSelected(prev => !prev), []);
const clickHandler = (e) => {
e.preventDefault();
toggleSelected();
props.collectVisitsForShift(
props.day,
props.startTime,
props.endTime,
props.customer
);
};
The documentation for useCallback.

React setState doesn't update boolean variable

I created a state variable using
const [drawing, setDrawing] = useState(false)
Then I've this button which should update the value of drawing
<button onClick={() => toggleDrawing}>Toggle Drawing</button>
The toggleDrawing function is:
const toggleDrawing = () => {
setDrawing(true)
}
but when I press the button and console.log the value of drawing id doesn't update, it's always a step behind. it start with a value of false, after one click it remains false and after the second click it switches to true.
const toggleDrawing = () => {
setDrawing((oldValue) => !oldValue)
}
if you want to toggle state in React you always want to refer to the previous state
const [drawing, setDrawing] = useState(false)
Case:1 If you want a toggle true=>false and false=>true
const toggleDrawing = () => {
setDrawing(!drawing);
}
<button onClick={toggleDrawing}>Toggle Drawing</button>
Case:2 If you just want to set it to true
const toggleDrawing = () => {
setDrawing(true);
}
<button onClick={toggleDrawing}>Toggle Drawing</button>
Sandbox https://codesandbox.io/s/vigilant-meadow-thdni?file=/src/App.js
Note => console.log(drawing) in toggleDrawing function will return false at first then true reason is state is not updated
The Problem
You're just returning the toggleDrawing function without executing it
Solution
Execute the function when the click event occurs like this
<button onClick={() => toggleDrawing()}>Toggle Drawing</button>
Or pass the toggleDrawing to onClick as the actual function like
<button onClick={toggleDrawing}>Toggle Drawing</button>

input onClick and onFocus collision

I have an <input> element, and I want to show content (div) below the input if it has focus.
Also, on every click on the input, I want to hide/show the div.
The problem is that if the input is not focused, than clicking the input will trigger both onClick and onFocus events, so onFocusHandler will run first so the menu will appear but immidately after that onClickHandler will run and hide it.
This is the code (that doesn't work):
import React from 'react';
const MyComp = props => {
/*
...
state: focused, showContent (booleans)
...
*/
const onFocusHandler = () => {
showContent(true);
setFocused(true);
}
const onClickHandler = () => {
if (focused) {
showContent(false);
} else {
showContent(true);
}
}
return (
<>
<input
onFocus={onFocusHandler}
onClick={onClickHandler}
/>
{
showContent &&
<div>MyContent</div>
}
</>
);
};
export default MyComp;
How can I solve this issue?
This is a very odd desired behavior as an active toggle is rather opposed to an element being focused or not. At first I couldn't see any clear way to achieve the behavior you desire. But I thought it could be achieved.
Use the onMouseDown handler instead of the onClick handler.
Use onFocus handler to toggle on the extra content.
Use onBlur handler to toggle off the extra content.
Code:
const MyComp = (props) => {
const [showContent, setShowContent] = useState(false);
const onFocusHandler = () => {
setShowContent(true);
};
const onBlurHandler = () => {
setShowContent(false);
};
const onClickHandler = () => {
setShowContent((show) => !show);
};
return (
<>
<input
onBlur={onBlurHandler}
onFocus={onFocusHandler}
onMouseDown={onClickHandler}
/>
{showContent && <div>MyContent</div>}
</>
);
};
Note: It should be noted that onMouseDown isn't a true replacement to onClick. With a click event the mouse down and up have to occur in the same element, so if. user accidentally clicks into the input but then drags out and releases, the onClick event should not fire. This may be ok though since the focus event fires before the click event so the input may get focus and toggle the content on anyway. Maybe this quirk is acceptable for your use-case.
You should you onBlur event to setContent to false. There is no need to use the onClick handler for that.

How do I use my updated variable in an already binded onClick event of another component?

I have an array of objects in which each object has an onclick event. I map my array of objects to a component. I have a button in which I change a variable's value.
If I click my button, it changes the variable's value, but when I fire the onclick event on one of my components it does not use the variable's changed value. (I'm guessing that this is normal behaviour because the event was already binded to the component when it was created.)
Although, how can I use my updated variable value in my component's onClick event?
First I declare my variables
const [myVariable, setMyVariable] = useState("first value");
const [mymap, setmymap] = useState([
{
key: 1,
name: "one",
onClick: event => handleClick(event)
},
{
key: 2,
name: "two",
onClick: event => handleClick(event)
}
]);
setVariable is my onClick event of my first button that changes my variable's value
const setVariable = () => {
setMyVariable("second value");
};
handleClick is the onClick event of my components
const handleClick = () => {
console.log(myVariable);
};
Here I return my UI with my map of MyComponent
return (
<div>
<button onClick={setVariable}>Set variable</button>
<h1>{myVariable}</h1>
{mymap.map(element => (
<MyComponent key={element.key} onclickevent={element.onClick} />
))}
</div>
);
In this very example storing the event in every mymap item looks redundant. If there is no reason for it then simpler version will work as expected:
import React, { useState, useCallback } from "react";
const MyComponent = ({onclickevent}) => (<div>
<button onClick={onclickevent}>
Use variable from MyComponent
</button>
</div>);
const MyPage = props => {
const [myVariable, setMyVariable] = useState("first value");
// handler changes only when `myVariable` changes
const clickHandler = useCallback(() => {
console.log(myVariable);
}, [myVariable]);
// removing handlers from state
const [mymap, setmymap] = useState([
{
key: 1,
name: "one"
},
{
key: 2,
name: "two"
}
]);
return (
<div>
<button onClick={() => setMyVariable("second value")}>Set another value</button>
<h1>{myVariable}</h1>
{mymap.map(element => (
<MyComponent key={element.key} onclickevent={clickHandler} />
))}
</div>
);
};
export default MyPage;
Above instead storing handler in a state and then obtaining it when iterating over state elements mymap.map(el => el.onClick) it is directly passed to children (example shows those are all the same anyway). But we also need to make sure that handler is not stale and changes depending on the value it expects:
const clickHandler = useCallback(() => {
console.log(myVariable);
}, [myVariable]);
Updated Codesandbox example provided in a question:
PouncingPoodle, I stuck with answering by myself. But I've just found
the answer which fits perfectly for your trouble
Thanks so much #Travnikov.dev for the perfect point.
Here is my solution:
useEffect(() => {
setmymap(prev => {
let update = [...prev];
for (let i = 0; i < update.length; i ++) {
update[i].onClick = (event => handleClick(event))
update[i] = {...update[i], onClick: update[i].onClick}
}
return update;
})
}, [myVariable]);
So what's happening is I have an useEffect with the dependency of myVariable. Once my value has changed, I just update my components' array of objects's onClick to exactly what is was, except this time the onClick will have the updated value.

Resources