React hook useState's setter inside function doesn't work - reactjs

I'm trying to do a refactor of a countdown component that a project I'm working on has.
When I finished the migration of the logic the value of the counter didn't work. I decided to start from zero in codesandbox, so I tought of the simplest implementation and came out with this:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
useEffect(() => {
const interval = setInterval(() => setCounter(counter - 1), 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);
What it happens here is that the value of counter stays on 59 after the first run of the interval.
Codesandbox: https://codesandbox.io/embed/flamboyant-moon-ogyqr
Second iteration on issue
Thank you for the response Ross, but the real issue happens when I link the countdown to a handler:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
const [countdownInterval, setCountdownInterval] = useState(null);
const startCountdown = () => {
setCountdownInterval(setInterval(() => setCounter(counter - 1), 1000));
};
useEffect(() => {
return () => clearInterval(countdownInterval);
});
return (
<div className="App" onClick={startCountdown}>
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);

Add the counter variable within the second parameter (the array) of the useEffect function. When you pass in an empty array, it will only update the state once on the initial render. An empty array is often used when you're making an HTTP request or something along those lines instead. (Edited for second iteration)
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(5);
const [counterId, setCounterId] = useState(null);
useEffect(() => {
return () => clearInterval(counterId);
}, []);
const handleClick = () => {
/*
* I'd take startCountdown and make
* it's own component/hook out of it,
* so it can be easily reused and expanded.
*/
const startCountdown = setInterval(() => {
return setCounter((tick) => {
if (tick === 0) {
clearInterval(counterId);
setCounter(0);
return setCounterId(null);
};
return tick - 1;
});
}, 1000)
setCounterId(startCountdown);
};
return (
<div className="App" onClick={handleClick}>
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);
For more information on this implementation, read about React Hooks and skipping effects at https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects.

You can use the Functional Updates version of the function returned by useState to compute the new state based on the previous state.
Your updated code would look like this:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
useEffect(() => {
const interval = setInterval(() => {setCounter(counter => counter - 1);}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);
UPDATE EDIT
Here is a version that starts the countdown with a click handler:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [counter, setCounter] = useState(60);
const [countdownInterval, setCountdownInterval] = useState(null);
const startCountdown = () => {
setCountdownInterval(setInterval(() => setCounter(counter => counter - 1), 1000));
};
useEffect(() => {
return () => clearInterval(countdownInterval);
}, [countdownInterval]);
return (
<div className="App" onClick={startCountdown}>
<h1>Hello CodeSandbox {counter}</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App timerSeconds={360} />, rootElement);

Related

Limit the number of items in menu in react quizzes

I am using react quizzes library:
https://codesandbox.io/s/react-quizzesexample-forked-2w1gj?file=/src/index.js:408-499
According to the documentation we can choose just these kind of builder items from menu that we want using toolBox.
I want to leave just Checkboxes in the menu but i didn't find a solution for this. Who can help?
function App() {
const [formdata, setFormData] = useState([]);
return (
<div className="App">
<QuizzBuilder onChange={setFormData} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
This package react-quizzes, looks not really great. Its final build not include all modules, and require importing things directly. But if you want, here's the solution:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { QuizzBuilder } from "react-quizzes";
import ToolBox from "react-quizzes/lib/ToolBox";
import "react-quizzes/lib/assets/antd.css";
const filteredToolBox = ToolBox().filter(el => el.field_name === "checkboxes_")
function App() {
const [formdata, setFormData] = useState([]);
return (
<div className="App">
<QuizzBuilder toolBox={filteredToolBox} onChange={setFormData} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Update #1
Added logic to override ToolBox label
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { QuizzBuilder } from "react-quizzes";
import QuizExample from "./QuizExample";
import "react-quizzes/lib/assets/antd.css";
import ToolBox from "react-quizzes/lib/ToolBox";
import { defaultMessages } from "react-quizzes/lib/translations/TranslatedText";
const filteredToolBox = ToolBox().filter(
(el) => el.field_name === "checkboxes_"
);
const messages = {
en: {
...defaultMessages.en,
"toolbox.checkboxes.name": "Here are Checkboxes"
}
};
function App() {
const [formdata, setFormData] = useState([]);
return (
<div className="App">
<QuizzBuilder
messages={messages}
toolBox={filteredToolBox}
onChange={setFormData}
/>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

check state of another functional component

i am new to react, i want to call the state of an outside function, for example :
export default function Child() {
const [succeeded, setSucceeded] = useState(false);
}
export default function Parent() {
if(Child.succeeded){
// do the following
}
}
i know that props are used for const objects only, and i don't want to merge both functions in a signle one to keep things organised, i would like to check for child's state to do the next step, or to callback the parent function with the new state to notify it. is there any way to do it ? Thanks a lot for your time.
Another approach is that you can use the useRef, which is very handy in some cases.
import React, {useState} from "react";
export default function Child({nameRef}) {
const [name, setName] = useState('');
React.useEffect(() => {
nameRef.current = name;
}, [name]);
return (
<>
<input nameRef={nameRef} type="text" onChange={event => setName(event.target.value)} />
</>
);
}
import React, { useState, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Child from './Child';
function App() {
let [name, setName] = useState("Nate");
let nameRef = useRef();
const submitButton = () => {
console.log(nameRef.current);
};
return (
<div className="App">
<p>{name}</p>
<div>
<Child nameRef={nameRef} />
<button type="button" onClick={submitButton}>
Submit
</button>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Debouncing and Setting text data not happening at the same time

I am trying to use the debounce feature as well as the ability to retain the value of textbox that I typed but it's not happening for some reason. If I comment out setMyval(e.target.value); on line #20 then the debounce works without any issue but the value I type does not show up. Whereas if I uncomment it, then the value shows in the textbox but debounce feature does not work (meaning there are multiple console logs). Please if someone can tell me why is this happening and how can I make it work, it would help me.
Below is my reactjs code:
// App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import debounce from "./debounce";
function App() {
const [myval, setMyval] = useState("");
const handleChange = debounce(() => {
console.log("This log msg should be debounced");
}, 2000);
return (
<div className="App">
<input
type="text"
value={myval}
onChange={e => {
setMyval(e.target.value);
handleChange(e.target.value);
}}
/>
<button onClick={() => setMyval("my new value")}>Change Value</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
// debounce.js
export default function debounce(fn, wait) {
let timer;
return function() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
Code at https://codesandbox.io/s/affectionate-wind-0ef3y
What I expect:
If I type a word in the textbox, the value should remain in the textbox.
Debounce should work i.e. few console logs should appear.
If I click on the "Change Value" button, it should update the value "my new value" in the textbox.
https://codesandbox.io/s/sharp-turing-w4hu9
Your debounce doesnt work, because your debounce function gets redeclared on every component update. Use useCallback to keep the reference the same and avoid reinitialization
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
import debounce from "./debounce";
import "./styles.css";
function App() {
const [myval, setMyval] = useState("");
const handleChange = useCallback(debounce(() => {
console.log("This log msg should be debounced");
}, 2000), []);
return (
<div className="App">
<input
type="text"
value={myval}
onChange={e => {
setMyval(e.target.value);
handleChange(e.target.value);
}}
/>
<button onClick={() => setMyval("my new value")}>Change Value</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

userRef or createRef returning undefined in functional component

I have read lot of answers here but all of them are tide to class components.
If I have simple functional component where I use useRef or createRef, ref.current is undefined
I'm assigning it on top of div or input but I can't get any of their properties
Console.log() gives me data only when I use standelone console.log(ref)
Every other property is undefined e.g. console.log(ref.current)
import React, { useRef } from "react";
import ReactDOM from "react-dom";
function App() {
const ref = useRef()
console.log(ref.current) // undefined
console.log(ref) // { current }
return (
<div className="App">
<h1 ref={ref}>Hello CodeSandbox</h1>
{/* <input ref={ref} name="test" value="bla" /> */}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
Take a look on this demo and look at console:
https://codesandbox.io/s/fervent-kirch-soe8n
But even in class component I can't access for example ref.current.innerHTML:
https://codesandbox.io/s/relaxed-beaver-ic1em
Ok I have found where is problem.
If I use useEffect() or if I use some button handler, I have access to element:
useEffect(()=>{
console.log(ref.current.innerHTML)
})
same in class component:
class App extends React.Component {
myRef = React.createRef();
componentDidMount(){
console.log(this.myRef.current); // correct
}
render() {
console.log(this.myRef.current); // null
return <h2 ref={this.myRef}>Start editing to see some magic happen!</h2>
}
}
or
<button onClick={()=> console.log(ref.current.innerHTML)}>Click</button>
You give the ref to the element in the dom,
so you can't access it before it actually gets render,
so in react that occur in the component lifecycle,
which is either useEffect or componentDidMount.
import React, { useRef, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const ref = useRef();
useEffect(() => {
console.log(ref.current)
},[])
return (
<div className="App">
<h1 ref={ref}>Hello CodeSandbox</h1>
{/* <input ref={ref} name="test" value="bla" /> */}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
const Expander = forwardRef((_, ref) => {
return <div ref={ref}>test</div>;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Way without ref and with ref and useState
import React, { useRef, useEffect, useState } from "react";
import ReactDOM from "react-dom";
function app() {
const ref = useRef('lalala');
const [h1Text, setH1Text] = useState('Hello CodeSandbox');
useEffect(() => {
console.log(ref.current)
});
const changeHeader = () =>{
setH1Text('mamamama');
}
const changeHeader2 = (ev) =>{
ev.target.innerHTML = "Hello CodeSandbox222222222222";
}
return (
<div className="App">
<h1 ref={ref} onClick={changeHeader}>{h1Text}</h1>
<h2 onClick={changeHeader2}>Hello CodeSandbox2</h2>
</div>
);
}
And code that input chagne the h1 without refs
function Example2() {
const [h1Text, setH1Text] = useState('Hello CodeSandbox');
const changeHeader = (ev) =>{
setH1Text(ev.target.value);
}
return (
<div className="App">
<h1>{h1Text}</h1>
<input onChange={changeHeader} name="test" />
</div>
);
}
If you referring component directly without mounting (componentDidMount/useEffect) then you will be get an undefined value. Alternatively you can use arrow function to access ref element.
example:
export function App(props) {
const ref = useRef()
console.log(ref.current) // undefined
const seeRef = () => {
console.log(ref.current) // <h1 ></h1>
}
return (
<div className='App'>
<h1 ref={ref}>Hello React.</h1>
<button onClick={()=> seeRef()}>Click</button>
</div>
);
}
Ref: https://stackoverflow.com/a/72548440/4652706

Use case for useLayoutEffect + useState vs useMemo

I've seen this answer: useMemo vs. useEffect + useState , and it sums it up well for useEffect, but in my case I want to perform an expensive operation that will change the DOM as early as possible. Would useMemo() still be recommended instead of useLayoutEffect() with a state update? Does the double render of effect -> state-update negate any performance boost?
EDIT
useLayoutEffect() scenario:
useLayoutEffect(() => {
const tokens = expensiveOperationGeneratingClasses(param1)
setTokens(tokens)
},
[param1])
render (
<>
{
tokens.map(token => <span className={token.id}/>)
}
</>
)
useMemo scenario:
const tokens = useMemo(() => {
return expensiveOperationGeneratingClasses(param1)
},
[param1]
render (
<>
{
tokens.map(token => <span className={token.id}/>)
}
</>
)
Actually I realised that I'm not doing DOM operations but rather just generating the class names before the rendering of the <span> tags to avoid flickering, so I think i'm better off using useMemo, am I right?
I will try to explain where you can use LayoutEffect and Memo. Let's start with the using of LayoutEffect.
The using of LayoutEffect has some drawbacks says Dan Abramov Link 1, Link 2.It's a good explanation of where you can use these gives Kent C. Dodds.If you need an example, you can see it here Chris. Don't forget about reading for understand the difference.
Now about of the using Memo. It's also has a drawback. For what we use Memo ,and where it is used you can found here.
And now in practice.
option 1 use LayoutEffect
import React, { useState, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Control = () => {
const [add, setAdd] = useState(1);
return (
<div>
<div>
<PostOffice add={add} />
</div>
<div onClick={() => setAdd(add + 1)}>{"Click"}</div>
</div>
);
};
function PostOffice({ add }) {
const [letter, setLetter] = useState(add);
useLayoutEffect(() => {
console.log("useLayoutEffect");
setLetter(add);
}, [add]);
console.log(letter);
return <div className="App">{console.log(letter, "DOM")}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Control />, rootElement);
I'm not sure about this option 1, because there is an anti-pattern effect here.
option 2 use LayoutEffect
import React, { useState, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Control = () => {
const [add, setAdd] = useState(1);
return (
<div>
<div>
<PostOffice add={add} />
</div>
<div onClick={() => setAdd(add + 1)}>{"Click"}</div>
</div>
);
};
function PostOffice({ add }) {
const [letter, setLetter] = useState(0);
useLayoutEffect(() => {
console.log("useLayoutEffect");
setLetter(add);
}, [add]);
console.log(letter);
return <div className="App">{console.log(letter, "DOM")}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Control />, rootElement);
there will be a meaningless rendering
option useMemo
import React, { useState, useMemo } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const Control = () => {
const [add, setAdd] = useState(1);
return (
<div>
<div>
<PostOffice add={add} />
</div>
<div onClick={() => setAdd(add + 1)}>{"Click"}</div>
</div>
);
};
function PostOffice({ add }) {
const Letter = useMemo(() => {
console.log("useMemo");
return add + 1;
}, [add]);
console.log(Letter);
return <div className="App">{console.log(Letter, "DOM")}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Control />, rootElement);
And here everything works perfectly
Total
Minus useMemo 1,
Minus useLayoutEffect, 1,anti-pattern effect or meaningless rendering,adding useState,
This is why you should use useMemo.
but if there is a way not to use these hooks, it will be perfect.

Resources