Coming from Vue and diving into React I seem to struggle with the concept of the hooks / component lifecycle and data flow. Where in Vue I could solve my issues using v-model in React I struggle to do so. Basically:
What I intend to do is : have a parent component which is a form. This component will have several child components where each are fragments of the form data. Each child component manages its own state. The parent component has a submit button that should be able to retrieve the values from all child components.
In a nutshell, my approach is: have a functional component to manage part of said form using state hooks. This "form fragment"-component can broadcast a change event broadcastChange() containing the updated values of its inputs. In the parent I have a submit button which invokes this broadcastChange() event from the child using a ref.
The problem I am running into is that I am always getting the default values of the child state. In the below example, I’d always be getting foo for property inputValue. If I were to add a submit button inside the child component to invoke broadcastChange() directly, I do get the latest values.
What am I overlooking here ? Also, if this is not the React way to manage this two-way communication, I’d gladly hear about alternatives.
Parent code:
function App() {
const getChildChangesFn = useRef( null );
const submitForm = e => {
e.nativeEvent.preventDefault();
getChildChangesFn.current(); // request changes from child component
};
const handleChange = data => {
console.log( data ); // will always list { inputValue: "foo" }
};
return (
<form>
<child getChangeFn={ getChildChangesFn } onChange={ handleChange } />
<button type="submit" onClick={ () => submitForm() }>Save</button>
</form>
);
}
Child code:
export default function Child( props ) {
const [ inputValue, setInputValue ] = useState( "foo" );
useEffect(() => {
// invoke inner broadcastChange function when getChangeFn is triggered by parent
props.getChangeFn.current = broadcastChange;
}, []);
const broadcastChange = () => {
props.onChange({ inputValue });
};
render (
<fieldset>
<input
type="text"
value={ inputValue }
onChange={ e => setInputValue( e.target.value ) }
/>
</fieldset>
);
}
>Solution :
You need to leave Vue behind and make your thinking more React-y. Rather than trying to manage your state changes imperitavely by ‘broadcasting’ them up, you need to ‘lift your state’ (as they say) in to your parent and pass it down to your children along with change handlers.
A simple example:
export default function App() {
const [childState, setChildState] = useState(false);
const onChildClick = () => setChildState((s) => !s);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<ChildComponent childState={childState} onClickHandler={onChildClick} />
</div>
);
}
const ChildComponent = ({ childState, onClickHandler }) => {
return (
<button onClick={onClickHandler}>
State is {childState ? "true" : "false"}
</button>
);
};