I’m simply not understanding why is this happening or even how to explain it, I have this custom hook:
const useFeatures = (existingFeatures: Ft[]) => {
console.log('fts provided to state:', existingFeatures)
const [features, setFeatures] = useState(existingFeatures)
console.log('fts given by state:', features)
...
and the output is
LOG fts provided to state: [{"done": true, "ftName": "task", "name": "T"}]
LOG fts given by state: [{"ftName": "number", "label": "N", "name": "", "value": 22}]
This is happening between renders of different components that uses the useFeatures custom hook and provide different "features" (initial states), its like useState is caching the previous state even if is re initialized.
I also should clarify that I’m developing a React Native App.
I know isn’t much information to post a question, but I’ve been not able to reproduce the bug outside my project.
Just wanna know if someone have experienced something similar.
Ive tried run the second console.log after a timeout or even inside a useEffect hook that takes as parameter [features]
and still got the same log
EDIT
I have some "records" that come from a useQuery realm hook
const records = useQuery(
RecordSchema,
recs => recs.filtered('date == $0', strDateFmt(dayOn)),
[dayOn],
).map(r => ({
...r,
features: r.features.map(f => JSON.parse(f)),
}))
useFeatures is used by EditableRecord
{records.map((record, i) => (
<EditableRecord key={i} existingRecordId={record._id} />
))}
EditableRecord.tsx
const existingRecord = useObject<RecordSchema>('Record', existingRecordId)
const { features, dispatchFeatures } = useFeatures(
existingRecord?.features.map(f => JSON.parse(f)) || [],
)
if I update dayOn (that comes from a useState hook) multiple times, the records are rendered with the features of the first record generating this unexpected log
LOG fts provided to state: [{"color": "#844", "ftName": "tag", "name": "Tg"}]
LOG fts given by state: [{"content": "Ssss", "ftName": "text", "name": "", "prompt": "P"}]
LOG fts provided to state: [{"ftName": "number", "label": "N", "name": "", "value": 22}]
LOG fts given by state: [{"content": "Ssss", "ftName": "text", "name": "", "prompt": "P"}]
LOG fts provided to state: [{"done": true, "ftName": "task", "name": "T"}]
LOG fts given by state: [{"content": "Ssss", "ftName": "text", "name": "", "prompt": "P"}]
3
Answers
The default value for the
useState
hook is only the initial value of state. Once you update state by callingsetFeatures
it is totally expected that the two console logs will read different values until the hook is un-mounted. (unless of coursesetFeatures
is called again with the initial value).Edit: Now that you have added more context, I think setting the
key
of theEditableRecord
torecord._id
is likely to fix your issue. Keys are how React keeps track of the components that are returned from a map/array. Using the array index instead of a truly unique key can lead to bugs where react doesn’t know a component should be updated.This is why you don’t use a loop index as a
key
. If all you had to do was pass in a loop index as thekey
prop, then React could easily do that for you. It wants you to pass akey
that means something to your data.What’s happening here is that when react renders and sees an element with a
key
that matches an element at the same spot from the last render, it assumes it’s the exact same component.Let’s look at this code.
So say it renders with a single record in
records
. It will render the equivalent of:Now
dayOn
changes, andrecords
now contains a single totally different record. Thekey
will still be0
, and so that same component will be used with new props. The new record id is passed in as prop, and sent to the initial value of a state that was already setup, and so it is ignored.You need to tell React that if the record changes, then it needs to destroy and recreate that component from scratch. And that’s done by using a unique key.
Try this:
The best practice is to always use a key that uniquely identifies the data that is being rendered by each iteration of the loop. For database records, that’s an
id
. If it’s a list of names that you know will be unique, then that name could work.And if it’s a simple list that renders no internal hooks or state and probably won’t change after its initial render, then maybe you can use the loop index as a key. But this is a last resort, or else bugs like this one make you feel like you are going crazy happen.
Please show where are you calling to
useFeatures
. If you are using 2 version of the same hook, make sure you are calling the correct function from the parent.