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>
);
}
CodePudding user response:
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>
);
};
CodePudding user response:
You have to uplift the state to parent component, or use a state manager like redux. In your case you are already passing down an onChange function, which should do what you need. But on parent component you need to manage the state, and store changes in the state, and pass it down to components as props.
An alternative to redux is mobx, which is a reactive library, in your case, sounds like you are familiar with reactive components, might fit you better. Other alternatives are using the Context that comes with react, and react-query is also a solid alternative, as it also handles async api calls.
