0

In ES6, ComputedPropertyName allows us to do things like use a variable as a key, which in turn means we can set state dynamically. However, if you look around at examples of setting state dynamically, they tend to all have one thing in common -- the state key's name is hardcoded. As an example:

class Input extends React.Component {
    state = { state1: "" };

    handleChange = event => {
        const {
            target: { name, value }
        } = event;

        this.setState({
            [name]: value
        });
    };

    render() {
        return (
            <div>
                <label>
                    <input
                        type="text"
                        name="state1"
                        value="new value"
                        onChange={this.handleChange}
                    />
                </label>
            </div>
        );
    }
}

This works because we have a state key called "state1", as seen in the line state = { state1: "" };, and we are hardcoding name in the input field to be that state key, as seen in the line name="state1".

I do not like this solution, because it means I now have to keep track of state.state1" in more than one location. If I were to refactorstate.state1to instead bestate.state2, I would have to go findname="state1"1 and update that to read name="state2". Instead of worry about that, I am wondering if there is a way to set state dynamically without hardcoding this state key. That is, I'm looking to change

<input
      type="text"
      name="state1"
      value="new value"
      onChange={this.handleChange}
      />

Into something like:

<input
      type="text"
      name={this.state.state1.keyname}
      value="new value"
      onChange={this.handleChange}
      />

Obviously the above doesn't work because keyname is undefined, but the intention here is that name can take on the value of "state1" without me having to hardcode it. How can this be achieved?

buratino
  • 1,408
  • 2
  • 17
  • 40

2 Answers2

1

You can have an array with objects with keys type and name which you can use to set the initial state and render the inputs dynamically. This way you'll only have to change the value once in the array. You can do something like this.

Here is a codesandbox

import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
  constructor() {
    super();
    this.arr = [
      { type: "text", name: "state1" },
      { type: "password", name: "state2" }
    ];

    // set the state keys dynamically from this.arr
    this.state = this.arr.reduce((agg, item) => {
      agg[item.name] = "";
      return agg;
    }, {});
  }

  handleChange = event => {
    const {
      target: { name, value }
    } = event;

    this.setState(
      {
        [name]: value
      }
    );
  };

  renderInputs = () => {
    return this.arr.map((item, i) => (
      <div key={i}>
        <label>
          <input
            type={item.type}
            name={item.name}
            value={this.state[item.name]}
            onChange={this.handleChange}
          />
        </label>
      </div>
    ));
  };

  render() {
    const inputs = this.renderInputs();
    return <div>{inputs}</div>;
  }
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Hope this helps !

Hemant Parashar
  • 3,684
  • 2
  • 16
  • 23
0

There is the new useReducer() that comes with hooks and context. Check this out i think that is the best pattern to solve your issue. https://reactjs.org/docs/hooks-reference.html.

Ricardo Gonzalez
  • 1,827
  • 1
  • 14
  • 25