Reputation: 1121
Currently, I have created some different form elements. I'm trying to create them in a diverse way so I can piece them together to create different formats for a form.
Here are a few of my form elements:
// Field Component
interface IField extends ILabel {}
export const Field: React.FunctionComponent<IField> = props => {
return (
<div>
<Label {...props} />
{props.children}
</div>
);
};
// Label Component
interface ILabel {
htmlFor: string;
label: string;
required?: boolean;
}
export const Label: React.FunctionComponent<ILabel> = props => {
return (
<label htmlFor={props.htmlFor}>
{props.label}
// Some required icon would go where I've added the <span />.
{props.required && <span />}
</label>
);
};
// Input Wrapper Component
export const InputWrapper: React.FunctionComponent = props => {
return <div>{props.children}</div>;
};
// Input Component
interface IInput {
type: string;
id: string;
name: string;
value?: string;
placeholder?: string;
required?: boolean;
}
export const Input: React.FunctionComponent<IInput> = props => {
return (
<input
type={props.type}
id={props.id}
name={props.name}
value={props.value}
placeholder={props.placeholder}
required={props.required}
/>
);
};
And here's how I implement the component:
<Field htmlFor="name" label="Name:" required>
<InputWrapper>
<Input
id="name"
type="text"
name="name"
placeholder="Enter your name..."
/>
</InputWrapper>
</Field>
I would like to be able to set the required
prop on the Field
component and it will also get passed down to my Input
component, no matter how deeply nested it becomes. How is this possible?
I'm also providing a CodeSandBox demo.
Thanks for any help in advance!
Upvotes: 3
Views: 1834
Reputation: 6089
When using props.children
and that it is impossible to know the exact hierarchy of the nested components, or that it may vary for different use-cases, it can be hard to try to pass the props
from parent to children.
Context provide a way to share data and react on changes, for a tree of components.
Your codesandbox adapted to the explained solution
https://codesandbox.io/s/react-stackoverflow-60241936-fvkdo
In your case, you would need to create a Context
for your Field
component to "share" the required
field and define a default value.
interface IFieldContext {
required?: boolean;
}
export const FieldContext = React.createContext<IFieldContext>({
required: false // default value when the prop is not provided
});
Then using FieldContext.Provider
in the Field
component you can assign the "shared" values for the nested components.
export const Field: React.FunctionComponent<IField> = props => {
return (
<FieldContext.Provider value={{ required: props.required }}>
<Label {...props} />
{props.children}
</FieldContext.Provider>
);
};
Finally, in the Input
component, use FieldContext.Consumer
to access the "shared" values and retrieve the required
prop assigned on the Field
component. You will then be able to remove the required
field from the IInput
interface since it now comes from the Context
and not from props
anymore.
interface IInput {
type: string;
id: string;
name: string;
value?: string;
placeholder?: string;
}
export const Input: React.FunctionComponent<IInput> = props => {
return (
<FieldContext.Consumer>
{context => (
<input
type={props.type}
id={props.id}
name={props.name}
value={props.value}
placeholder={props.placeholder}
required={context.required} // access context prop
/>
)}
</FieldContext.Consumer>
);
};
And "voila", you can use the required
prop on the Field
component and it will be applied on your nested Input
component, no matter how deep the Input
is nested... fancy stuff 😄
export const App: React.FunctionComponent = () => {
return (
<Field htmlFor="name" label="Name:" required>
<InputWrapper>
<Input
id="name"
type="text"
name="name"
placeholder="Enter your name..."
/>
</InputWrapper>
</Field>
);
};
Upvotes: 1