React.useTransition vs React.useDeferredValue

2022.10.24

useTransition vs useDeferredValue

#react#state
use client /components/post/reExport import randomNumFromTo from import { useUpdateEffect } from import syncWait from const containerStyles = { border: , margin: , padding: } const bigArray = [...Array(20000).keys()] const Basic = () => { const [count, setCount] = useState(0) const [items, setItems] = useState([]) const handleClick = () => { setCount(count + 1) setItems([]) setItems(bigArray.map(() => randomNumFromTo())) } return ( <div css={containerStyles}> <button onClick={handleClick}>Show 20k random numbers</button> <div>Click counter: {count}</div> <div css={{ fontSize: }}> {items.map((item, i) => ( <span key={i}> {item} { } </span> ))} </div> </div> ) } const UseTransition = () => { const [count, setCount] = useState(0) const [items, setItems] = useState([]) const [isPending, startTransition] = useTransition() const handleClick = () => { // urgent setCount(count + 1) setItems([]) // not urgent startTransition(() => { setItems(bigArray.map(() => randomNumFromTo())) }) } return ( <div css={containerStyles}> <button onClick={handleClick}>Show 20k random numbers</button> <div>Click counter: {count}</div> <div>{isPending ? : null}</div> <div css={{ fontSize: }}> {items.map((item, i) => ( <span key={i}> {item} { } </span> ))} </div> </div> ) } function UseDeferredValue() { const [inputValue, setInputValue] = useState( ) const deferredInputValue = useDeferredValue(inputValue) useUpdateEffect(() => { console.log( ) }, [inputValue]) useUpdateEffect(() => { console.log( ) }, [deferredInputValue]) useUpdateEffect(() => { console.log( ) syncWait(500) }, [deferredInputValue]) return ( <div> <input type= placeholder= value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> </div> ) } const PracticalExample = () => { const [inputValue, setInputValue] = useState( ) const [items, setItems] = useState(bigArray) const [isPending, startTransition] = useTransition() const deferredInput = useDeferredValue(inputValue) const handleInput = (e) => setInputValue(e.target.value) useEffect(() => { startTransition(() => { console.log( , deferredInput) setItems(bigArray.filter((item) => item.toString().includes(deferredInput))) }) }, [deferredInput]) return ( <div css={containerStyles}> <input type= value={inputValue} onChange={handleInput} placeholder= /> <div style={{ opacity: isPending ? 0.4 : 1 }}> <p>Searching for: {deferredInput || }</p> {isPending ? <p>Loading...</p> : null} <div css={{ fontSize: }}> {items.map((item, i) => ( <span key={i}> {item} { } </span> ))} </div> </div> </div> ) } const postObj = { title: , date: , tags: [ ], imgUrl: , desc: , body: ( <> <H>Without useTransition</H> <p> If we bunch in one function the small counter action and big 20k render all render happens at ones after all processing is finished. </p> <Code block jsx>{ /components/post/reExport import randomNumFromTo from const containerStyles = { border: , margin: , padding: } const Basic = () => { const [count, setCount] = useState(0) const [items, setItems] = useState([]) const handleClick = () => { setCount(count + 1) setItems([]) setItems(bigArray.map(() => randomNumFromTo())) } return ( <div css={containerStyles}> <button onClick={handleClick}>Show 20k random numbers</button> <div>Click counter: {count}</div> <div css={{ fontSize: }}>{items.map((item, i) => <span key={i}>{item}{ }</span>)}</div> </div> ) } const UseTransition = () => { const [count, setCount] = useState(0) const [items, setItems] = useState([]) const [isPending, startTransition] = useTransition() const handleClick = () => { // urgent setCount(count + 1) setItems([]) // not urgent startTransition(() => { setItems(bigArray.map(() => randomNumFromTo())) }) } return ( <div css={containerStyles}> <button onClick={handleClick}>Show 20k random numbers</button> <div>Click counter: {count}</div> <div>{isPending ? : null}</div> <div css={{ fontSize: }}>{items.map((item, i) => <span key={i}>{item}{ }</span>)}</div> </div> ) } t have control over the corresponding{ } <code>setState</code> function, for ex the value comes from the library </li> <li> <code>useTransition</code> gives a complete control as we can decide which code is treated as “low priority” </li> <li> with <code>useDeferredValue</code> we wrap either the state value or a value computed based on the state value </li> <li>such derived value has low update priority</li> <li> for example we have a resource consuming job, for example filtering an array on every key stoke </li> <li>it may make our app sluggish and unresponsive</li> <li> <i>useDeferredValue</i> hook tells the application not to do any processing for this value until app is busy </li> <li>it is kind of debouncing with some uncontrolled logic</li> <li> Type text in input and check in console that heavy function which depends on deferred value is updated only after actions associated with a non-deferred value.{ } </li> <li>But unfortunately that is not 100% true and there is some sluggishness still</li> </ul> <Code block jsx>{ import syncWait from import { useUpdateEffect } from function UseDeferredValue2() { const [inputValue, setInputValue] = useState( ) const deferredInputValue = useDeferredValue(inputValue) useUpdateEffect(() => { console.log(\ ) }, [inputValue]) useUpdateEffect(() => { console.log(\ ) }, [deferredInputValue]) useUpdateEffect(() => { console.log( ) syncWait(500) }, [deferredInputValue]) return ( <div> <input type= placeholder= value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> </div> ) } /imgs/deferred_value.png /> <UseDeferredValue /> <H>Practical usage</H> <ul> <li> text input value is rendered normally with <code>onChange</code> event </li> <li> in useEffect hook depending on <code>deferredInput</code> value we do sorting heavy computation using <code>startTransition</code> function </li> <li>in console log we see that not every key stoke triggers filtering, which is good</li> </ul> <LazyImg path= /> <Code block jsx>{ ) const [items, setItems] = useState(bigArray) const [isPending, startTransition] = useTransition() const deferredInput = useDeferredValue(inputValue) const handleInput = (e) => setInputValue(e.target.value) useEffect(() => { startTransition(() => { console.log( , deferredInput) const filtered = bigArray.filter(item => item.toString().includes(deferredInput)) setItems(filtered) }) }, [deferredInput]) return ( <div css={containerStyles}> <input type= value={inputValue} onChange={handleInput} /> <div style={ { opacity: isPending ? 0.4 : 1 }}> <p>Searching for: {deferredInput || }</p> {isPending ? <p>Loading...</p> : null} <div css={{ fontSize: }}>{items.map((item, i) => <span key={i}>{item}{ }</span>)}</div> </div> </div> ) } } <Lnk path= >Dave Gray