Reputation: 13588
I'm trying to create a component that is being split up in to parts.
E.g. in the example code below, I aimed to have the Form and the Actions separated while maintaining state. This way, I can create a basic Form + action button component, and then style it differently on different pages.
import { useState } from 'react'
const FormBasic = ({ children }) => {
const [ text, setText ] = useState('')
const onSubmit = () => console.log("Submitting");
const onChange = (e) => {
console.log("Typing", e.target.value);
setText(e.target.value)
}
const Form = () => <textarea rows={4} onChange={onChange} value={text} />;
const Actions = () => <button onClick={onSubmit}>Some Buttons</button>;
return children({ Form, Actions });
};
export default FormBasic;
I will then be using it this way (below). The thing is - it all renders just fine for static components, but you will start seeing problems the moment state changes.
E.g. When I type in the <textarea />
and onChanged
is triggered, the function rerenders and you loses focus to the text area. So end up you cannot really type in the text area.
export default function App() {
return (
<FormBasic>
{({ Form, Actions }) => {
return (<React.Fragment>
<div style={{ border: '1px solid red'}}>
<Form />
</div>
<div style={{ border: '1px solid blue'}}>
<Actions />
</div>
</React.Fragment>)
}}
</FormBasic>
);
}
I'm including a code sandbox - https://codesandbox.io/s/solitary-brook-nrrbf?file=/src/App.js
There may be questions about the practical use of this kind of component. Practically aside, how may I modify my codes so that I can continue to use the state inside the exported textarea
.
Or are there other forms of exporting a component so that I can split them and maintaining the state?
Or is the above style possible at all?
PS: I've used context and redux, and I am trying not to use them in this example. I am trying to keep it simple.
Upvotes: 5
Views: 1378
Reputation: 1873
One solution to your problem is: instead of passing text
and setText
as props, to use the React Context API.
Specifically:
FormBasic
would become the state + context provider.Form
and Actions
would become the context consumers.Here's what I came up with (working example on CodeSandbox):
(I used more generic names for the state variables to help other readers of this post).
import React from "react";
import { useState, useContext, createContext } from "react";
// create the context with some generic defaults
const StateContext = createContext({
state: "",
setState: v => v
});
// create the context hook
const useStateContext = () => useContext(StateContext);
// the textarea component
const Form = () => {
const { state, setState } = useStateContext();
const onChange = (e) => {
console.log("Typing", e.target.value);
setState(e.target.value);
};
return <textarea rows={4} onChange={onChange} value={state} />;
};
// the buttons component
const Actions = () => {
const { state } = useStateContext();
const onSubmit = () => {
alert(state);
};
return <button onClick={onSubmit}>Some Buttons</button>;
};
// the form itself holding state and the context provider
const FormBasic = ({ children }) => {
const [state, setState] = useState("");
return (
<StateContext.Provider value={{ state, setState }}>
{children}
</StateContext.Provider>
);
};
const App = () => {
return (
<FormBasic>
<div style={{ border: "1px solid red" }}>
<Form />
</div>
<div style={{ border: "1px solid blue" }}>
<Actions />
</div>
</FormBasic>
);
};
Notice that FormBasic
no longer needs to have children
as a function, or the content wrapped in React.Fragment
, making the code cleaner. You can use the context consumers directly.
Upvotes: 3