Reputation: 357
I have a simple react component. The component has a handler that runs when the button is clicked. There is some blocking code in the handler. My task is: when I click the button the loader appears. After that, the blocking code do some heavy operations and after that, the loader disappears:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [wait, setWait] = useState(true);
const onClickHandler = () => {
setWait(true);
// Blocking code
setWait(false);
};
return (
<div className="App">
<button onClick={onClickHandler}>Click</button>
{wait && <div>Loading...</div>}
</div>
);
}
This code is artificial and I know that it is better not to do this - to block the thread. I know that batching works here. But still, I would like to know how I can show the loader in this situation. And in general, is such a situation possible in production, or is it a bad practice?
Upvotes: 0
Views: 634
Reputation: 506
Blocking the code means that any other operation in your page will have to wait until the first one finished. If you don't care of this happening (maybe this loader covers the entire screen or you don't want the user to do anything else until the operation is done) then that's okay.
Now, maybe you want to perform this opeation while the user interacts with the page, in that case you should make use of Promise
s' asynchronous behavior, by using the then
/catch
/finally
syntax. You will also need useRef
so you can have access to the component's current state in the callbacks.
import { useState, useRef } from "react";
import "./styles.css";
export default function App() {
// your big operation should return a Promise
const bigOperation = () => {
return new Promise((resolve, reject) => {
resolve("Heeeey");
})
}
// added "_" to the set function so it won't be confused
// with our function to update the reference
const [wait, _setWait] = useState(true);
// create reference for the wait state
const waitRef = useRef(wait);
// function to update reference and state (mimics real one _setWait)
const setWait = (newValue) => {
waitRef.current = newValue;
_setWait(newValue);
}
const onClickHandler = () => {
setWait(true);
// this executes asyncronously
bigOperation().then(() => {
setWait(false);
});
};
return (
<div className="App">
<button onClick={onClickHandler}>Click</button>
{wait && <div>Loading...</div>}
</div>
);
}
Upvotes: 1
Reputation: 4725
There are two ways to do this
new Promise
or setTimeout
will do.
export default function App() {
const [wait, setWait] = useState(true);
const onClickHandler = () => {
setWait(true);
new Promise((resolve) => {
// Blocking code
setWait(false);
resolve()
});
};
return (
<div className="App">
<button onClick={onClickHandler}>Click</button>
{wait && <div>Loading...</div>}
</div>
);
}
You can use custom hooks like useWorker
to achieve this conveniently
useWorker
: https://github.com/alewin/useWorker
Upvotes: 1
Reputation: 63550
I'd move the condition outside of the main return statement.
if (wait) return <div>Loading...</div>;
return (
<div className="App">
<button onClick={onClickHandler}>Click</button>
</div>
);
Upvotes: 0