T
TanStack3y ago
rival-black

Avoiding useEffect when data changes

How would you handle data from the server changing, based upon which local state should change too? The code below "works" but I would like to avoid useEffect, since I feel like it isn't really the right tool for what I want. So what is the general pattern for how to make changes in server side data affect client side state?
const useMyCustomHook = (txId: string) => {
const { data: transaction } = useQuery({
queryKey: ['transactionDetails', txId],
queryFn: getTransactionDetails,
})
const currentFeeRate = transaction ? getTransactionFeeRate(transaction) : 1
const [newFeeRate, setNewFeeRate] = useState(String(currentFeeRate))

useEffect(() => {
setNewFeeRate(String(currentFeeRate + 1))
}, [currentFeeRate])

return setNewFeeRate
}
const useMyCustomHook = (txId: string) => {
const { data: transaction } = useQuery({
queryKey: ['transactionDetails', txId],
queryFn: getTransactionDetails,
})
const currentFeeRate = transaction ? getTransactionFeeRate(transaction) : 1
const [newFeeRate, setNewFeeRate] = useState(String(currentFeeRate))

useEffect(() => {
setNewFeeRate(String(currentFeeRate + 1))
}, [currentFeeRate])

return setNewFeeRate
}
In other words: I have a derived value from the cache that should be able to change on the client side and optionally the user has a post button to update the server side data.
10 Replies
sensitive-blue
sensitive-blue3y ago
I'd do this:
const { data: transaction } = useQuery(...)

const currentFeeRate = transaction ? getTransactionFeeRate(transaction) : 1
const [newFeeRate, setNewFeeRate] = useState(undefined)

const feeRate = newFeeRate ?? String(currentFeeRate)

return [feeRate, setNewFeeRate]
const { data: transaction } = useQuery(...)

const currentFeeRate = transaction ? getTransactionFeeRate(transaction) : 1
const [newFeeRate, setNewFeeRate] = useState(undefined)

const feeRate = newFeeRate ?? String(currentFeeRate)

return [feeRate, setNewFeeRate]
rival-black
rival-blackOP3y ago
That is actually stunningly simple and I wonder why I didn't think of this myself, thanks! One thing that is not relevant to my current use case but that I am still wondering about for future cases: What if we want changes in the server data to also update the feeRate here? Let's say the user updates the fee rate from a different device and we want this update to also be reflected here.
sensitive-blue
sensitive-blue3y ago
Split it up into multiple components and pass a key={deviceId}. That will instruct react to reset local state
rival-black
rival-blackOP3y ago
Not sure I understand it properly. I presume splitting it up referred to the data from the query and the local state, so something like:
const MyParent = () => {
const { data: transaction, isSuccess } = useQuery(...)

return !isSuccess ? <Placeholder /> : <MyChild transaction={transaction} />
}

const MyChild = ({ transaction }) => {
const currentFeeRate = getTransactionFeeRate(transaction)
const [newFeeRate, setNewFeeRate] = useState(currentFeeRate + 1)

return <MyGrandChild newFeeRate={newFeeRate} setNewFeeRate={setNewFeeRate} />
}
const MyParent = () => {
const { data: transaction, isSuccess } = useQuery(...)

return !isSuccess ? <Placeholder /> : <MyChild transaction={transaction} />
}

const MyChild = ({ transaction }) => {
const currentFeeRate = getTransactionFeeRate(transaction)
const [newFeeRate, setNewFeeRate] = useState(currentFeeRate + 1)

return <MyGrandChild newFeeRate={newFeeRate} setNewFeeRate={setNewFeeRate} />
}
but where exactly should I pass the key here (presuming my assumption of how to split it up was correct).
sensitive-blue
sensitive-blue3y ago
what / where is the device that changes? I guess it would be:
<MyChild transaction={transaction} key={device.id} />
<MyChild transaction={transaction} key={device.id} />
rival-black
rival-blackOP3y ago
I think I formulated it a bit imprecisely, I'll try to break it down more simply. The scenario is as follows: I have a form for updating the fee of a transaction In that form I have an input field whose value should get initialized to currentFeeRate + 1 The user should be able to update the value of that inputField (setNewFeeRate) If the value of currentFeeRate changes, newFeeRate should get updated to that new value How would I best achieve this without using useEffect?
genetic-orange
genetic-orange3y ago
In your example above, if you do:
<MyChild transaction={transaction} key={getCurrentFeeRate(transaction)} />
<MyChild transaction={transaction} key={getCurrentFeeRate(transaction)} />
that should do it. Whenever currentFeeRate changes, MyChild would be remounted and the newFeeRate state reset to currentFeeRate + 1. That would erase any change made by the user though.
rival-black
rival-blackOP3y ago
I don’t think so, because useState would keep the previous value even if props change, but essentially that same idea could work like this I think:
const MyParent = () => {
const { data: transaction, isSuccess } = useMyQuery()

return !isSuccess ? <Placeholder /> : <MyChild realFeeRate={getTransactionFeeRate(transaction)} />
}

const MyChild = ({ realFeeRate }) => {
const [feeRate, setFeeRate] = useState(realFeeRate)
const [newFeeRate, setNewFeeRate] = useState(realFeeRate + 1)

if(realFeeRate !== feeRate) {
setFeeRate(realFeeRate)
setNewFeeRate(realFeeRate + 1)
}

return <MyForm value={newFeeRate} onChange={setNewFeeRate} />
}
const MyParent = () => {
const { data: transaction, isSuccess } = useMyQuery()

return !isSuccess ? <Placeholder /> : <MyChild realFeeRate={getTransactionFeeRate(transaction)} />
}

const MyChild = ({ realFeeRate }) => {
const [feeRate, setFeeRate] = useState(realFeeRate)
const [newFeeRate, setNewFeeRate] = useState(realFeeRate + 1)

if(realFeeRate !== feeRate) {
setFeeRate(realFeeRate)
setNewFeeRate(realFeeRate + 1)
}

return <MyForm value={newFeeRate} onChange={setNewFeeRate} />
}
I don’t think I can come up with a better way of doing this though, is there perhaps something more simple that I’m missing? Otherwise I would mark this question as solved
genetic-orange
genetic-orange3y ago
I don’t think so, because useState would keep the previous value even if props change
The state would be reset whenever the key value changes: https://react.dev/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key
Preserving and Resetting State – React
The library for web and native user interfaces
rival-black
rival-blackOP3y ago
Aaah I see, okay that’s cool In my previous snippet there was no need for useMemo => I edited it to be the simplest one I can think of using the previous approach -> using the different key is even simpler though, thanks so much for your help!!

Did you find this page helpful?