Search code examples
reactjsinputparent

Override input value in Child component in React


I have a child input component and parent where I set value.

Parent component

const \[dummyText, setDummyText\] = useState('')
return (

<Input
  {...props}
  id="first-name"
  label="First name"
  type="text"
  placeholder="Please enter your first name"
  value={dummyText}
  onChange={(value) => setDummyText(value.target.value)}
/>
)

Child input component

    import React, { InputHTMLAttributes, forwardRef } from 'react'
    export type Props = {} & InputHTMLAttributes<HTMLInputElement>

    const FormInput: React.FC<Props> = forwardRef<HTMLInputElement, Partial<Props>>(
      ({ ...props }, ref) => <input ref={ref} {...props} />
    )

    FormInput.displayName = 'FormInput'

    export default FormInput

I want to add button in child component where on click I need to clear the value.

The thing is that I want to have clear functionality in child component and I do not want to write extra code for every <Input in parent, and whatever I do in child component, I just can't override props.value.

Does anybody have any experience how to solve this?


Solution

  • If you want to clear the input value you can pass the clearing function as a prop to the child component like this:

    const Input = ({ onClearInput, ...otherProps }) => {
      return (
        <div style={{ display: 'flex', gap: '.5rem' }}>
          <input {...otherProps} />
          <button onClick={onClearInput}>Clear</button>
        </div>
      );
    };
    
    export default function App() {
      const [firstName, setFirstName] = useState('');
      const [secondName, setSecondName] = useState('');
    
      const onClearInputHandler = (handler) => handler('');
    
      return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem' }}>
          <Input
            id="first-name"
            label="First name"
            type="text"
            placeholder="Please enter your first name"
            value={firstName}
            onChange={(event) => setFirstName(event.target.value)}
            onClearInput={() => onClearInputHandler(setFirstName)}
          />
    
          <Input
            id="second-name"
            label="Second name"
            type="text"
            placeholder="Please enter your second name"
            value={secondName}
            onChange={(event) => setSecondName(event.target.value)}
            onClearInput={() => onClearInputHandler(setSecondName)}
          />
        </div>
      );
    }
    

    Because you'll have a different state for each input you can pass the set function from useState as a parameter and run it inside the onClearInputHandler to clear the value.

    Edit: Then I think the easiest solutions is to handle the clear function inside the input itself like this:

      const Input = (props) => {
      const [value, setValue] = useState(props.value || '');
      return (
        <div style={{ display: 'flex', gap: '.5rem' }}>
          <input
            {...props}
            value={value}
            onChange={(event) => setValue(event.target.value)}
          />
          <button onClick={() => setValue('')}>Clear</button>
        </div>
      );
    };
    
    export default function App() {
      const onSubmitHandler = (event) => {
        event.preventDefault();
    
        const formData = new FormData(event.target);
        const formProps = Object.fromEntries(formData);
        console.log(formProps);
      };
    
      return (
        <form action="submit" onSubmit={onSubmitHandler}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '.5rem' }}>
            <Input
              id="first-name"
              label="First name"
              type="text"
              placeholder="Please enter your first name"
              name="first-name"
            />
    
            <Input
              id="second-name"
              label="Second name"
              type="text"
              placeholder="Please enter your second name"
              name="second-name"
            />
          </div>
          <button type="submit" style={{ margin: '.5rem 0' }}>
            Submit
          </button>
        </form>
      );
    

    All inputs are inside the form and you can read the values when the form is submitted. Otherwise you'll have to create a new state or ref for every single Input you have in your parent component. This way the input value and the handler to clear it are always inside the Input component.

    Here's a demo on stackblitz: https://stackblitz.com/edit/react-gnyvav?file=src%2FApp.js