2

I'm creating a dynamic list component that basically renders a new empty text field every time something is added to the list. The component is as follows (it uses a custom TextFieldGroup but I don't think that code is necessary as nothing out of the ordinary is going on.):

const DynamicInputList = ({ inputs, onChangeArray, label, name, errors }) => 
{
  const nonEmptyInputs = inputs.filter(input => {
    return input !== "";
  });
  const lastIndex = nonEmptyInputs.length;
  return (
    <div>
      {nonEmptyInputs.map((input, index) => {
        return (
          <Grid container spacing={16} key={index}>
            <Grid item xs={12}>
              <TextFieldGroup
                label={label}
                id={`${name}${index}`}
                name={name}
                value={input}
                onChange={e => onChangeArray(e, index)}
                errors={errors[name]}
              />
            </Grid>
          </Grid>
        );
      })}
      <Grid container spacing={16} key={lastIndex}>
        <Grid item xs={12}>
          <TextFieldGroup
            label={label}
            id={`${name}${lastIndex}`} 
            name={name}
            value={inputs[lastIndex]}
            onChange={e => onChangeArray(e, lastIndex)}
            errors={errors[name]}
          />
        </Grid>
      </Grid>
    </div>
  );
};

The onChangeArray function is as follows:

  onChangeArray = (e, index) => {
    const state = this.state[e.target.name];
    if (e.target.value === "") {
      state.splice(index, 1);
      this.setState({ [e.target.name]: state });
    } else {
      state.splice(index, 1, e.target.value);
      this.setState({ [e.target.name]: state });
    }
  };

Everything works fine except when a blank field is changed (begin to type) it immediately removes focus from any of the fields. I'm looking for a way to keep the focus on this field while still adding the new one below.

Thanks in advance.

1
  • please elaborate it.. Everything works fine except when a blank field is changed (begin to type) it immediately removes focus from any of the fields. Commented Jul 24, 2018 at 20:54

1 Answer 1

2

The problem is that you're encountering the following sequence of events (assuming inputs length of 5):

  1. React renders 1 field (key = 5)
  2. User starts typing 'a'
  3. Your state change is triggered
  4. React renders 2 entirely new fields (key = 1 & value = 'a', key = 5) and trashes the old one (key = 5)

There are at least two solutions

Don't delete / trigger re-render of the original field

This is the cleaner solution.

First, you are directly mutating state in onChangeArray with your use of slice, because that does an in-place modification. Perhaps this isn't causing problems now, but it's a big React anti-pattern and could create unpredictable behavior.

Here's a quick fix:

  onChangeArray = (e, index) => {
    const state = [...this.state[e.target.name]];
    if (e.target.value === "") {
      state.splice(index, 1);
      this.setState({ [e.target.name]: state });
    } else {
      state.splice(index, 1, e.target.value);
      this.setState({ [e.target.name]: state });
    }
  };

And other options

I'll leave the reworking of the implementation to you, but the gist would be:

  1. Use consistent key references, which means iterate over all your inputs and just skip the empty ones instead of finding the empty ones first and setting that arbitrary key to the total length. Of course, you should still render the first empty one.
  2. You could consider rendering everything, and use CSS display:none attribute as needed to accomplish your desired UX

Leverage input's autofocus attribute

I assume your TextFieldGroup uses an input element. You could pass the autofocus attribute to the field you want to have focus. This answers your original question.

But this isn't recommended, as you're still needlessly running through those re-render cycles and then putting a patch on top.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot! I actually should have known to use the spread operator (...) but I'm still new to all this, thanks for catching that. To solve my problem I basically delete the empty fields in the onChangeArray function now instead of inside the component by filtering the copied state (i.e. newState = state.filter(item => item !== "")). I also add the last field by updating state with this.setState({[e.target.name]: [...newState, ""]}). That way my component uses consistent key references as you mentioned. Hope this is closer to a React-like pattern.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.