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