I have the following component:
import React, { useState } from "react"; import { FormControl, TextField } from "@material-ui/core"; interface IProps { text?: string; id: number; onValueChange: (text: string, id: number) => void; placeholder: string; } export const QuestionTextRow: React.FC<IProps> = (props) => { const [item, onItemChange] = useState(props.text); const onChange = (e: React.FormEvent<HTMLInputElement>) => { const newValue = e.currentTarget.value; onItemChange(newValue); props.onValueChange(newValue, props.id); }; return ( <> <FormControl fullWidth> <div>{props.text}</div> <TextField aria-label="question-text-row" onDragStart={(e) => { e.preventDefault(); e.stopPropagation(); }} value={item} onChange={(ev: React.ChangeEvent<HTMLInputElement>): void => { onChange(ev); }} /> </FormControl> </> ); };
It is rendered via the following component:
const renderQuestionOptions = (id: number): JSX.Element => { const item = props.bases.find((x) => x.sortableId === id); if (!item) return <> </>; return ( <div className={classes.questionPremiseRow}> <div className={classes.rowOutline}> <QuestionOptionsSortableRow item={item} isDisabled={false} onClickRow={onClickBasisRow}> <QuestionTextRow text={item.text ? item.text.text : ""} id={item.sortableId} onValueChange={basisValueChanged} placeholder={intl.formatMessage({ id: "question.create.basis.row.placeholder" })} ></QuestionTextRow> </QuestionOptionsSortableRow> </div> </div> ); };
It renders the following list:
As you can see props.text
and useState
item
from props.text
are rendered equally. If props.text
is updated it does not reflect on useState
though.
https://stackoverflow.com/a/53846698/3850405
I can solve it by useEffect
to make it work:
useEffect(() => { onItemChange(props.text); }, [props.text]);
https://reactjs.org/docs/hooks-effect.html
https://stackoverflow.com/a/54866051/3850405
However If I add key={`${item.text?.text}-${item.sortableId}`}
to QuestionTextRow
it will work without using useEffect
. How come?
I know a static unique key should be used but would it not be the same result if key={item.uniqueId}
was used?
https://www.npmjs.com/package/react-key-index
The argument passed to useState is the initial state much like setting state in constructor for a class component and isn’t used to update the state on re-render
https://stackoverflow.com/a/43892905/3850405
Advertisement
Answer
However If I add key={
${item.text?.text}-${item.sortableId}
} to QuestionTextRow it will work without using useEffect. How come?
That is because of reconciliation. In react, when on one render you have say:
<SomeComponent key={1}/>
If on next render you render same component (at the same place) with different key, say:
<SomeComponent key={2}/>
React will destroy instance related to previous component and create a new instance for this one, hence the useState
inside that component will be initialized with the provided text
property once again (like when you created the component first time).
If the key is same for some component on previous and next renders and you just change some other props, in this case the component is re-rendered (no instance destroyed), that’s why you didn’t see the text
property reflected in state.
Sometimes it can be tricky to copy props to state like you have in your useEffect
solution, I recommend you read this post, it is about classes but same ideas apply.