0

I currently have a page with headers: h1, h2, h3. I want their text to be editable by a user onClick, so basically the header will turn into input type='text' then revert back to the header when user is done entering his values in the input. I have this functionality working quite well using the useReducer hook where i dispatch the corresponding values related to each one. However i'm struggling to figure out how to make the textIsEditing prop/boolean reusable across all 3 components. Let me explain a bit more with code:

JSON Data:

{
  "name":"Click to rename One",
  "campaigntype": "Click to rename Two",
  "description": "Click to rename Three",
}

Component that holds the 3 headers:

export default function MetaData() {
  const [nameIsEditing, setNameEditing] = useState(false);
  const [campaignIsEditing, setCampaignIsEditing] = useState(false);
  const [descriptionIsEditing, setDescriptionIsEditing] = useState(false);

  function toggleNameEdit(){
    setNameEditing(!nameIsEditing);
  }
  function toggleCampaignEdit(){
    setCampaignIsEditing(!campaignIsEditing);
  }
  function toggleDescriptionEdit(){
    setDescriptionIsEditing(!descriptionIsEditing);
  }

  const initialState = jsonData;

  const [metaData, dispatch] = useReducer(metaDataReducer, initialState);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const defaultMetaValue = metaData[e.target.name as keyof MetaState];

    dispatch({ type: e.target.name, value: e.target.value === '' ? defaultMetaValue : e.target.value });
  };

  return (
    <Grid className='menuItem' container direction='column'>

      {nameIsEditing ?
        <RenameInput name='name' textStyle='h3' onClickAway={toggleNameEdit} onSubmit={toggleNameEdit} adName={metaData.name} onChange={handleChange} />
        :
        <H1 onClick={toggleNameEdit}>{metaData.name}</H1>}

      {campaignIsEditing ?
        <RenameInput name='campaigntype' textStyle='subtitle2' onClickAway={toggleCampaignEdit} onSubmit={toggleCampaignEdit} campaignType={metaData.campaigntype} onChange={handleChange} />
        :
        <H2 onClick={toggleCampaignEdit}>{metaData.campaigntype}</H2>}

      {descriptionIsEditing ?
        <RenameInput paragraph textStyle='body2' name='description' onClickAway={toggleDescriptionEdit} onSubmit={toggleDescriptionEdit} adDescription={metaData.description} onChange={handleChange} />
        :
        <H3 onClick={toggleDescriptionEdit} className='meta-description'>{metaData.description}</H3>}

    </Grid>);
}

The reducer:

const metaDataReducer = (state: MetaState, action: ActionType) => {

  if (action.type in state) {
    return { ...state, [action.type]: action.value };
  } else {
    return state;
  }

};

The RenameInput:

const RenameInput = (props) => {
  return (
    <ClickAwayListener onClickAway={props.onClickAway}>
      <form onSubmit={props.onSubmit}>
        <TextInput
          defaultValue={props.adName || props.adDescription || props.campaignType}
          autoFocus
          onChange={props.onChange}
          multiline={props.paragraph}
          textStyle={props.textStyle}
          name={props.name}
        />
      </form>
    </ClickAwayListener>
  );
};

On my MetaData component, i really don't like this part of the code

 const [nameIsEditing, setNameEditing] = useState(false);
  const [campaignIsEditing, setCampaignIsEditing] = useState(false);
  const [descriptionIsEditing, setDescriptionIsEditing] = useState(false);

  function toggleNameEdit(){
    setNameEditing(!nameIsEditing);
  }
  function toggleCampaignEdit(){
    setCampaignIsEditing(!campaignIsEditing);
  }
  function toggleDescriptionEdit(){
    setDescriptionIsEditing(!descriptionIsEditing);
  }

I want to make this re-usable across all the 3 components as it's practically doing the same thing. I need one function that handles all 3 of these but i'm really unsure where or how to do this? Do i do this in the reducer, or how could i turn this into factory function? There must be a better way than the way i've done it with {nameIsEditing ? renameComponent : h1 {campaignIsEditing ? renameComponent : h2 {descriptionIsEditing ? renameComponent : h3

Jim41Mavs
  • 482
  • 4
  • 21

1 Answers1

1

You should simply create a new component EditableTitle. It'll handle isEditing state and it'll do the logic for rendering either the read-only title or the RenameInput component.
In order to handle the small differences between your titles, you can send props to EditableTitle. For example, you can send a component value to render an h1, an h2 or whatever other component you want.

KorHosik
  • 1,225
  • 4
  • 14
  • 24
  • 1
    hehe thanks, i defo need to get more sleep! Now that i've thought about that's such a simple solution. – Jim41Mavs Jun 09 '20 at 19:30