AziCode
AziCode

Reputation: 2692

How to handle the state of multiple inputs and change their state from the parent?

I have a custom input component that has an edit mode, I use multiple inputs and my goal is to extract all the state from the input and put it on the parent.

I managed to do it for the input value since I create a useState for every input, but how can I achieve the same thing for the toggle behavior. I want to have a button next to each input that toggles the state of the input.(Show/Hide the input).

Right now when I press edit all the edits Booleans get triggered so all the inputs become visible.

How can I toggle one input at a time from the parent, Basically I want my custom components to be purely for presentation and the state should live on the parent

Example:

const Parent = () => {
  const [value1, setValue1] = useState('')
  const [value2, setValue2] = useState('')
  const [value3, setValue3] = useState('') 


  const [editMode, setEditMode] = useState(false)

  return (
    <div>
      <ChildCustomInput value={value1} updateValue={setValue1} editMode={editMode} updateEditMode={setEditMode} />
      <ChildCustomInput value={value2} updateValue={setValue2} editMode={editMode} updateEditMode={setEditMode}/>
      <ChildCustomInput value={value3} updateValue={setValue3} editMode={editMode} updateEditMode={setEditMode}/>
    </div>
  )
}


const ChildCustomInput = (props)=> {
  // ref to update the input's value in the parent
  const textInputEl = useRef(null)

  // method to update the parent's values
  const updateComponentValue = () => {
    props.updateValue(textInputEl.current.value)
    props.updateEditMode(!props.editMode)
  }

  // Toggle Edit mode
  const changeEditMode = () => {
    props.updateEditMode(!props.editMode)
  }

  const renderEditView = () => {
    return (
      <div>
        <input ref={textInputEl}></input>
        <button onClick={updateComponentValue}>
          OK
        </button>
      </div>  
    )
  }


  const renderDefaultView = () => {
    return (
      <div>
        <div>
          {props.value}
        </div>

        <button onClick={changeEditMode}>
          EDIT
        </button>
      </div>
    )
  }


  return (
    <div>
    {
      props.editMode 
        ? renderEditView() 
        : renderDefaultView()
     }
    </div>
 )
} 



export default Parent;

Upvotes: 2

Views: 174

Answers (2)

MaCadiz
MaCadiz

Reputation: 1817

For this case you should have a separated state that manages the toggle property for each input. Like:

const [value1EditMode, setValue1EditMode] = useState(false);
const [value2EditMode, setValue2EditMode] = useState(false);

And so on for each input.

But I suggest you to use the useReducer hook, so you just manage the whole child components state with just one reducer. You could do something like this:

const initialState = {
  input1: {
    value: "",
    editMode: false
  },
  input2: {
    value: "",
    editMode: false
  },
  input3: {
    value: "",
    editMode: false
  }
};

function reducer(state, action) {
  switch (action.type) {
    case "CHANGE_VALUE":
      let newState = {
        ...state
      };
      newState[action.key].value = action.value;
      return newState;
    case "SET_EDIT_MODE":
      newState = {
        ...state
      };
      newState[action.key].editMode = action.editMode;
      return newState;
    default:
      throw new Error();
  }
}

const Parent = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  function updateValue(key, value) {
    dispatch({ type: "CHANGE_VALUE", value, key });
  }

  function updateEditMode(key, editMode) {
    dispatch({ type: "SET_EDIT_MODE", editMode, key });
  }

  return (
    <div>
      <ChildCustomInput
        value={state.input1.value}
        updateValue={value => {
          updateValue("input1", value);
        }}
        editMode={state.input1.editMode}
        updateEditMode={editMode => {
          updateEditMode("input1", editMode);
        }}
      />
      <ChildCustomInput
        value={state.input2.value}
        updateValue={value => {
          updateValue("input2", value);
        }}
        editMode={state.input2.editMode}
        updateEditMode={editMode => {
          updateEditMode("input2", editMode);
        }}
      />
      <ChildCustomInput
        value={state.input3.value}
        updateValue={value => {
          updateValue("input3", value);
        }}
        editMode={state.input3.editMode}
        updateEditMode={editMode => {
          updateEditMode("input3", editMode);
        }}
      />
    </div>
  );
};

Here is a working example: https://codesandbox.io/s/eloquent-hellman-8on6s?file=/src/Parent.js:52-1818

Upvotes: 0

Florian Motteau
Florian Motteau

Reputation: 3724

It seems that you need a editMode boolean in the parent state for each of your input (ie editMode1, editMode2, editMode3), and each ChildCutomInput should use its own editModeX and setEditModeX instead of sharing the same boolean.

const Parent = () => {
  const [value1, setValue1] = useState('');
  const [value2, setValue2] = useState('');
  const [value3, setValue3] = useState('');


  const [editMode1, setEditMode1] = useState(false);
  const [editMode2, setEditMode2] = useState(false);
  const [editMode3, setEditMode3] = useState(false);

  return (
    <div>
      <ChildCustomInput value={value1} updateValue={setValue1} editMode={editMode1} updateEditMode={setEditMode1} />
      <ChildCustomInput value={value2} updateValue={setValue2} editMode={editMode2} updateEditMode={setEditMode2}/>
      <ChildCustomInput value={value3} updateValue={setValue3} editMode={editMode3} updateEditMode={setEditMode3}/>
    </div>
  )
}

Upvotes: 1

Related Questions