I am creating a form Component that will have a Form component and an Input component. Something like this:
<Form>
<Input name="name" />
<Input name="email" />
</Form>
In my setup, labels are automatically get generated from the name prop. What I would like to do, though, is provide an option to not show labels. Now, I could do it on each <Input> component like this:
<Form>
<Input noLabel name="name" />
<Input noLabel name="email" />
</Form>
But what I would really like to do is add it to the <Form> component and have it automatically applied to each <Input> component. Something like this:
<Form noLabel>
<Input name="name" />
<Input name="email" />
</Form>
The way I envision it, when defining my <Input> component I could do a check if the noLabel prop is set on the <Form> component. Something like this:
export const Input = props => {
...
{!props.noLabel && <label>...}
<input.../>
...
}
But I can't figure out how to get access to the noLabel prop from the <Form> component so that I can check to see whether or not it is set.
Any ideas on how to do this?
I would choose the context approach, to overcome the problems I mentioned in my comment to Mohamed's solution, this would enable indirect nesting as well:
const FormContext = React.createContext();
const Form = ...;
Form.Context = FormContext; // or simply Form.Context = React.createContext();
export default ({noLabel, ...props}) => <FormContext.Provider value={{noLabel}}/>;
and then your input component will consume it either like this:
const Input = props => {
const {noLabel} = useContext(Form.Context);
return (... < your no label logic here > ...);
}
or like this:
const Input = ....;
export default props => () => {
const {noLabel} = useContext(Form.Context);
return <Input noLabel={noLabel}/>
}
In your Form component, you can use React.Children and React.cloneElement to pass the noLabel prop to the input components, something like this :
const children = React.Children.map(this.props.children, child =>
React.cloneElement(child, { noLabel: this.props.noLabel })
);
return (<form>{children}</form>);
One way to do it is manipulating form's children. Mapping over each one and inject noLabel prop. You still will need to check inside Input for a noLabel prop but it's definitely less work
const Form = ({children, noLabel}) =>{
return React.children.forEach(_, child =>{
return React.cloneElement(child, { noLabel })
})
}
Related
I wrote a custom input for passwords. He looks like this:
const InputPassword = ({placeholder}) => {
const inputRef = useRef()
const [isPasswordVisible, setPasswordVisible] = useState(false)
function setInputStatus() {
if (isPasswordVisible) {
inputRef.current.setAttribute('type', 'password')
} else {
inputRef.current.setAttribute('type', 'text')
}
setPasswordVisible(!isPasswordVisible)
}
return (
<div className={cl.main}>
<input
ref={inputRef}
type={"password"}
placeholder={placeholder}
className={cl.input}
/>
<div className={cl.eyeContainer} onClick={setInputStatus}>
<Eye
isPasswordVisible={isPasswordVisible}
/>
</div>
</div>
);
};
export default InputPassword;
Now I want to get the value from my input in the parent component. The best way to do this is to use a forwardRef, but my component already has an internal Ref that is needed to change the type of the input. Surely you can somehow use one Ref to solve these two problems, but I did not find how.
I tried to pass a Boolean type state from the parent, so that when this state changes, call a method in the child component, this method would change another state in the parent, which stores the value from the child. And when this parent state changes, the necessary logic would work out for me. But the code turned out to be just as cumbersome and confusing as what is written above. Naturally, such code does not work well, it shows unpredictable behavior. I'm sure the solution is much simpler.
You can also pass a state variable to the child component, I am passing a ref variable, assuming that the parent component does not need to re-render based on the changes in the value of variable that is being passed as a prop.
Your parent component should have a inputRef, and pass it as a prop to child component, something like this:
const Parent = () => {
const inputRef = useRef()
return <InputPassword inputRef={inputRef} />
}
export default Parent;
const InputPassword = ({placeholder, inputRef}) => {
const [isPasswordVisible, setPasswordVisible] = useState(false)
function setInputStatus() {
if (isPasswordVisible) {
inputRef.current.setAttribute('type', 'password')
} else {
inputRef.current.setAttribute('type', 'text')
}
setPasswordVisible(!isPasswordVisible)
}
return (
<div className={cl.main}>
<input
ref={inputRef}
type={"password"}
placeholder={placeholder}
className={cl.input}
/>
<div className={cl.eyeContainer} onClick={setInputStatus}>
<Eye
isPasswordVisible={isPasswordVisible}
/>
</div>
</div>
);
};
export default InputPassword
I’m wondering how can I change a children input value from main?
Basically I have a component that I’m sending a children, and in this component I want to be able to change the input value.
Here is my main:
<FooterOptions>
<input type="text"
onChange={event=>setState(event.target.value)}
value={state}
hidden/>
</FooterOptions>
And here is my component:
export function FooterOptions(props:{children: ReactNode}){
return(
<div>
{props.children}
</div>
)
}
The children prop is something that you can only render onto the page, so there's nothing you can do with it to change the value of the input.
Instead how I would think about this is that you want to provide a mechanism for FooterOptions to change the value of another component. Here it happens to also be rendered as its children, but it would work the same even if it was rendered someplace else.
const Parent = () => {
const updateInput = (val) => setState(val)
return (
<FooterOptions handleUpdate={updateInput}>
<input type="text"
onChange={event=>setState(event.target.value)}
value={state}
hidden/>
</FooterOptions>
)
}
export function FooterOptions(props:{children: ReactNode, handleUpdate}){
// Example handler
const handleClick = () => handleUpdate('updated inside FooterOptions')
return(
<div onClick={handleClick}>
{props.children}
</div>
)
}
If you'd like to add more details of how you are hoping to update, then I can add a better example 😀
I have a form in which all input, select tags are separate components and each component but the submit button is in the form it self like-
<form>
<InputComp1 />
<InputComp2 />
<Select1 />
<Select2 />
<button type='submit' value='Register'/>
</form>
So how do I collect all state from various components and when user clicks on the submit the values get submitted.?
Is this approach while dealing with forms right? or should I manage state of all tags in the same component?
Manage the state of all inputs/selects in this component. You can pass values and handler functions to the inputs using props.
There is no "right" approach, the answer depends on the context.
You can have form as a controlled component, where you manage the state of all tags (while passing callbacks down the tree) as you suggested and as mentioned in docs.
Or, you can have it as uncontrolled component, for example:
const Input = ({ name }) => {
return <input name={name} />;
};
const Component = () => {
return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
const data = new FormData(e.target);
const entries = data.entries();
for (let entry of entries) {
console.log(entry);
}
}}
>
<Input name="username" />
<Input name="email" />
<input type="submit" value="Submit" />
</form>
</>
);
};
See controlled vs uncontrolled components.
Yes you should manage the state in the parent component itself and pass the onchange handler and value of that field as props inside the child components to update the value of the form fields.
Solution #1 would be "manage state react way". With this you should store state you need to share between components somewhere in their common ancestor. In your case it would be component that holds Form
Solution #2 applicable only if you use real form and form controls. Handle 'submit' event from the form and get all you need to submit from form data.
Solution #3 applicable only if you use some sort of "state manager". Follow instructions and best practices of the library you use.
In some cases you can mix and match that solutions. For example I still recommend to handle 'submit' event on form regardless of solution.
there is a concept of state lifting in react:
create a controlled form here and for every child, component pass a function to get the data from child components to parent one. by doing this you can submit all the values once.
here is the example
import React, {useState} from 'react';
const ChildInput = ({onChange, id}) => {
return(
<input
key={id}
type="text"
placeholder="enter name"
onChange={onChange}
/>
)
}
const Parent = () => {
const [name, setName] = useState('');
const onSubmit =(e)=>{
e.preventDefault();
// append your all data here just like child component
data = {name}
}
return(
<form onSubmit={onSubbmit}>
<ChildInput onChange={()=>setName(e.target.value)} id="name" />
<button type="submit" value="submit"/>
</form>
)}
for more information check this one: https://reactjs.org/docs/glossary.html#controlled-vs-uncontrolled-components
Does anyone know how to make this code work in a React Functional Component?
onfocus="(this.type='date')" onblur="(this.type='text')"
I am trying to get placeholder text to appear prior to the user clicking on the input element. Then when clicked, the input will change to MM/DD/YYYY.
Trying to emulate something like this in my React project: https://stackoverflow.com/a/34565565/14677057
Would appreciate any help! Thank you!
Have a state variable for the type, then use it in what you render:
const Example = () => {
const [type, setType] = useState('text');
return (
<input
type={type}
onFocus={() => setType('date')}
onBlur={() => setType('text')}
/>
)
}
you can useRef for focusing. onBlur will work in camel case.
eg:
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
Handling events with react elements is syntactically different from DOM elements.
events are named using camelCase, rather than lowercase.
We need to pass a JSX function, rather than a string.
`
function HandleInputField(){
const onChange=()=>{//your code}
const onFocus=()=>{//your code}
const onBlur=()=>{//your code}
return <input onChange={} onFocus={} onBlur={}/>
}
`
In my case I try to create a simple Form Component - mostly for "testing" reactjs and work with it.
To do this I work with 2 Components. The first Component is the Parent, the "Form" Component. The second Component is the field of the form - for example a simple textfield. This is the markup it would look like:
<Form
schema={MyFormSchema}
>
<Input name="name" />
<Input name="age" />
</Form>
In MyFormSchema I have all information which I need for every Child of the type "Input". For this case I have done this in my "Form" Component:
Form.jsx
Form = React.createClass({
renderChildren() {
return React.Children.map(this.props.children, (child)=>{
if (child.type && child.type.prototype && child.type.prototype.constructor.displayName === 'Input') {
let additionalProps = {
fieldSchema: this.props.schema.pick(child.props.name),
onChange: this.onChange
};
return React.cloneElement(child, additionalProps);
}
return child;
});
},
render() {
return(
<form>
{this.renderChildren()}
</form>
);
}
});
What I am doing here is to "clone" every "input" child and add some new props depending on the schema.
So the first question is:
Is this really the correct war in reactJs ? When I am not "cloning" every element and adding new properties I have to add the property directly in my View, right ? Something like but I am trying to prevent this because all information I need I already have as a prop in my Form Schema.
After playing around with this I found out, that this.props.children only have the first level of children. But when I have nested my Children in my Form Component it will not work anymore that my Component is replacing the Input Component with the manipulated component.
Example:
<Form
schema={MyFormSchema}
>
<AnotherComponent>
<Input name="name" />
</AnotherComponent>
<Input name="age" />
</Form>
When I am doing it like I now doing it this code will not work anymore because in this.props.children I only have [AnotherComponent, Input[name=age]] and the Input[name=name] is missing. So I think the way I am doing it is the wrong way. But i am not sure.
So the main question is:
Like in my example: What is the correct way in ReactJs to inherit props (or what ever) to all children (also the nested one) - or is this not possible in the "react" way and I really have to pass all necessary props to all children ?
Edit:
When I am talking about "pass all necessary props to all children" I mean something like this:
<Form
schema={MyFormSchema}
>
<AnotherComponent>
<Input name="name" fieldSchema={this.getFieldSchema('name')} onChange={this.onChange} />
</AnotherComponent>
<Input name="age" fieldSchema={this.getFieldSchema('age')} onChange={this.onChange} />
</Form>
In this example I would pass all necessary props I want to add dynamically by the parent. In my example above the next problem would be: "this" would not work for the name input because of its parent AnotherComponent. So I would have to reference to the parent - of course: its possible, but I think it would be a ugly way.
There are three correct ways to deeply pass props:
1) Just actually pass them down the tree from each component to the next (this is the most readable (in terms of code logic), but can get unwieldy once you have too many props to pass and lots of levels in your tree.
Example:
import React from 'react';
var GrandParent = React.createClass({
render () {
return (
<Parent familyName={'componentFamily'} />
);
}
});
var Parent = React.createClass({
render () {
return (
<Child familyName={props.familyName} />
);
}
});
var Child = React.createClass({
render () {
return (
<p>{'Our family name is ' + props.familyName}</p>
);
}
});
2) Use a Flux-style store (I prefer Reflux, though Redux is all the rage right now) to keep a common state. All components can then access that store. For me at least, this is the current preferred method. It's clear and it keeps business logic out of the components.
Example (using Reflux):
import React from 'react';
import Reflux from 'reflux';
var MyStore = Reflux.createStore({
// This will be called in every component that connects to the store
getInitialState () {
return {
// Contents of MyFormSchema here
};
}
});
var Input = React.createClass({
propTypes: {
name: React.PropTypes.string.isRequired
},
mixins: [Reflux.connect(MyStore)],
render () {
// I don't know what MyFormSchema so I'm generalizing here, but lets pretend its a map that uses the name of each field a key and then has properties of that field within the map stored at the key/value
return (
<input type={this.state[name].type} name={this.props.name} value={this.state[name].type} />
);
}
});
3) Use React's context feature. As you'll see immediately from looking at the docs, this feature is still in development and is subject to possible change and even removal in future versions of React. So, while it is likely the easiest way to pass props down a tree of components, personally I'm staying away from it until it becomes more of a finalized feature.
I'm not going to write an example for this one since the docs make it very clear. However, make sure to scroll down on the doc page and take a look at Parent-child coupling, which is kind of what you're doing right now.
Another solution for you is that instead of having a single component that renders Form and its Inputs, why not just pass the prop to Form as you do currently, and then simply render the individual Input using Form's render().
You could use react-addons-clone-with-props package this way:
import React, { Component } from 'react';
import cloneWithProps from 'react-addons-clone-with-props';
// ...
class Form extends Component {
recursivelyMapChildren(children) {
return React.Children.map(children, child => {
if (!React.isValidElement(child)) {
return child;
}
return React.cloneElement(child, {
...child.props,
children: this.recursiveCloneChildren(child.props.children)
});
})
}
render() {
return (
<form>{this.recursivelyMapChildren(this.props.children)}</form>
);
}
}
What the code does:
Gets all the children components via predefined children prop (see docs).
Recursively maps the collection of children with React.Children.map method, applying a lambda function to each element.
Saves the mapped (i.e. updated, but not mutated!) children elements into mappedChildren constant.
Puts them within form DOM element.
It looks simple and it should be so.
But you have to keep in mind that React is great when your code is kept clean and transparent. When you explicitly pass props like
<Form
schema={MyFormSchema}
>
<Input
name="name"
schema={MyFormSchema} />
<Input
name="age"
schema={MyFormSchema} />
</Form>
there's way less things to get broken when you accidentally change the underlying logic.
Thankyou. Credits #Rishat Muhametshin
I have used the above to create a re-usable method.
This works beautifully:
utils/recursivelyMapChildren.jsx
const recursivelyMapChildren = (children, addedProperties) => {
return React.Children.map(children, child => {
if (!React.isValidElement(child)) {
return child;
}
return React.cloneElement(child, {
...child.props,
...addedProperties,
children: this.recursivelyMapChildren(child.props.children, addedProperties)
});
})
};
export default recursivelyMapChildren;
usecase:
Form.jsx
import recursivelyMapChildren from 'utils/recursivelyMapChildren';
class Form extends Component {
handleValidation(evt, name, strValidationType){
/* pass this method down to any nested level input field */
}
render(){
return (
<form>
{recursivelyMapChildren(this.props.children, {
handleValidation: this.handleValidation.bind(this)
})}
<input type="submit" value="submit" className="validation__submit"/>
</form>
)
}
}
export default Form
SomeExample.jsx
const SomeExample = () => {
return (
<Form>
<input type="hidden" name="_method" value="PUT"/>
<fieldset>
<legend>Personal details</legend>
<div className="formRow">
<InputText/> {/* This component will receive the method - handleValidation, so it is possible to update the state on the nested grand parent - form */}
</div>
<div className="formRow">
<InputText/>{/* This component will receive the method - handleValidation, so it is possible to update the state on the nested grand parent - form */}
</div>
</fieldset>
</Form>
)
}
export default SomeExample;
I have an alternate solution to pass props to nested children. The function createFormComponents takes the schema and produces an object of components that will receive props as usual but with the schema already provided. You could link the FormContainer in my example up to a store or use setState to handle changes to the schema over time and the children will update correctly.
The example's output is to the console to demonstrate that the props are received as expected.
function Form_(props) {
console.log('Form props', props)
return <div>{props.children}</div>
}
function Input_(props) {
console.log('Input props', props)
return <div />
}
function createFormComponents(schema) {
return {
Form: props => {
return Form_({ ...props, schema })
},
Input: props => {
return Input_({ ...props, schema })
},
}
}
const FormContainer = React.createClass({
render: function() {
const myFormSchema = { x: 0, y: 1, z: 2 }
const {
Form,
Input,
} = createFormComponents(myFormSchema)
return (
<Form>
<Input name="name" />
<Input name="age" />
</Form>
)
}
})
ReactDOM.render(
<FormContainer />,
document.getElementById('container')
)
Fiddle: Props Example