Without useTransition If we bunch in one function the small counter action and big 20k render all render happens at ones after all processing is finished.
import { jsxToStr, H } from '/components/post/reExport'
import { useState, useTransition } from 'react'
import randomNumFromTo from '/functions/randomNumFromTo'
const containerStyles = { border: '1px solid grey', margin: '10px', padding: '10px' }
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: '8px' }}>{items.map((item, i) => <span key={i}>{item}{'; '}</span>)}</div>
</div>
)
}
useTransition With useTransition we can tell which state updates are urgent and which are not urgent There will be two renders state setter functions are wrapped into useTransition We will see the click counter update first, and 20k numbers after that The hook gives us the isPending state to let us show some spinner while data is being rendered
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 ? 'Loading...' : null}</div>
<div css={{ fontSize: '8px' }}>{items.map((item, i) => <span key={i}>{item}{'; '}</span>)}</div>
</div>
)
}
useDeferredValue useDeferredValue does the same thing as useTransition it is useful when we have a state value, but don't have control over the corresponding setState function, for ex the value comes from the library useTransition gives a complete control as we can decide which code is treated as “low priority” with useDeferredValue we wrap either the state value or a value computed based on the state value such derived value has low update priority for example we have a resource consuming job, for example filtering an array on every key stoke it may make our app sluggish and unresponsive useDeferredValue hook tells the application not to do any processing for this value until app is busy it is kind of debouncing with some uncontrolled logic 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. But unfortunately that is not 100% true and there is some sluggishness still
import { useState, useDeferredValue } from 'react'
import syncWait from '/functions/syncWait'
import { useUpdateEffect } from 'react-use'
function UseDeferredValue2() {
const [inputValue, setInputValue] = useState('')
const deferredInputValue = useDeferredValue(inputValue)
useUpdateEffect(() => {
console.log(`input value: ${inputValue}`)
}, [inputValue])
useUpdateEffect(() => {
console.log(`deferred: ${deferredInputValue}`)
}, [deferredInputValue])
useUpdateEffect(() => {
console.log('...waiting for 0.5 sec')
syncWait(500)
}, [deferredInputValue])
return (
<div>
<input
type="text"
placeholder="type text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
</div>
)
}
Practical usage text input value is rendered normally with onChange event in useEffect hook depending on deferredInput value we do sorting heavy computation using startTransition function in console log we see that not every key stoke triggers filtering, which is good
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: ', deferredInput)
const filtered = bigArray.filter(item => item.toString().includes(deferredInput))
setItems(filtered)
})
}, [deferredInput])
return (
<div css={containerStyles}>
<input type="text" value={inputValue} onChange={handleInput} />
<div style={ { opacity: isPending ? 0.4 : 1 }}>
<p>Searching for: {deferredInput || 'All'}</p>
{isPending ? <p>Loading...</p> : null}
<div css={{ fontSize: '8px' }}>{items.map((item, i) => <span key={i}>{item}{'; '}</span>)}</div>
</div>
</div>
)
}
Note useTransition wraps the state updating code useDeferredValue wraps a value affected by the state change do not utilize both at the same time because they accomplish the same thing in practical example, which I took from Dave Gray's video useTransition & useDeferredValue are used together probably just to show pending status, which comes from useTransition hook. Same logic can be done by useDeferredValue hook only.