'How do I add input data to JSON array in React.

I've been working on understanding React concepts and did my Todo project. I have the dummy data displaying, but can't add a new value to my dummy data, which is stored in an array of objects in a separate file, todos.js.

Here is the file hierarchy


Here is the error I am getting -

index.js:2177 Warning: Each child in an array or iterator should have a unique "key" prop.


TodoList.js

import React from 'react';
import Todo from './Todo';
import todos from '../todos'

class TodoList extends React.Component {
    constructor() {
        super();
        this.state = {
            todoItems: todos,
            newItem: {}
        }
    }

    addItem = (event) => {
        event.preventDefault();
        const todoList = this.state.todoItems;
        todoList.push(this.state.newItem);
        this.setState({ 
            todoList: todos,
            newItem: {}
        });

    };

    handleInput = (event) => {
        this.setState({ newItem: event.target.value });
    }

    render() {
        const itenary = this.state.todoItems;
        return (
            <div>
                {itenary.map(todo =>
                    <div key={todo.id}>
                        <Todo handleClick={this.props.handleClick} thing={todo} />
                    </div>

                )}
                <br />
                <form onSubmit={this.addItem}>
                    <input type="text" onChange={this.handleInput} placeholder="Add a new task" />
                    <button>Submit</button>
                </form>
            </div>
        );
    }
}

export default TodoList;

Todo.js

import React from 'react';

class Todo extends React.Component {
    constructor() {
        super();
        this.state = {
            clicked: false
        }
    }


    handleClick = () => {
        this.setState({ clicked: !this.state.clicked });
    }

    render() {
        const styles = this.state.clicked ? { textDecoration: 'line-through' } : { textDecoration: 'none' };
        return (
            {/* This is where the todo item is*/}
            <div style={styles} onClick={this.handleClick} key={this.props.thing.id}>{this.props.thing.text}</div>
        );
    }
}

export default Todo;

todos.js

const todos = [
    { id: 1, text: 'Go to the gym', 'completed': false },
    { id: 2, text:  'Do laundry', 'completed': false },
    { id: 3, text: 'Study for exams', 'completed': false },
    { id: 4, text: 'Read a book', 'completed': false },
    { id: 5, text: 'Clean the bedroom', 'completed': false },
    { id: 6, text: 'Go to the park', 'completed': false },
];

export default todos; 

Any help and/or feedback is appreciated.



Solution 1:[1]

You must give the new todo you add to todoItems a unique id that React can use to distinguish it from the others when you render them.

You should also not mutate the current state by using push. You should instead set state with an entirely new array that contains everything the previous one did.

Example

class TodoList extends React.Component {
  constructor() {
    super();
    this.state = {
      todoItems: todos,
      newItem: ""
    };
  }

  addItem = event => {
    event.preventDefault();
    this.setState(prevState => {
      return {
        todoItems: [
          ...prevState.todoItems,
          { id: Math.random(), text: prevState.newItem, completed: false }
        ],
        newItem: ""
      };
    });
  };

  // ...
}

const todos = [
  { id: 1, text: "Go to the gym", completed: false },
  { id: 2, text: "Do laundry", completed: false },
  { id: 3, text: "Study for exams", completed: false },
  { id: 4, text: "Read a book", completed: false },
  { id: 5, text: "Clean the bedroom", completed: false },
  { id: 6, text: "Go to the park", completed: false }
];

class TodoList extends React.Component {
  constructor() {
    super();
    this.state = {
      todoItems: todos,
      newItem: ""
    };
  }

  addItem = event => {
    event.preventDefault();
    this.setState(prevState => {
      return {
        todoItems: [
          ...prevState.todoItems,
          { id: Math.random(), text: prevState.newItem, completed: false }
        ],
        newItem: ""
      };
    });
  };

  handleInput = event => {
    this.setState({ newItem: event.target.value });
  };

  render() {
    const itenary = this.state.todoItems;
    return (
      <div>
        {itenary.map(todo => (
          <div key={todo.id}>
            <Todo handleClick={this.props.handleClick} thing={todo} />
          </div>
        ))}
        <br />
        <form onSubmit={this.addItem}>
          <input
            type="text"
            onChange={this.handleInput}
            value={this.state.newItem}
            placeholder="Add a new task"
          />
          <button>Submit</button>
        </form>
      </div>
    );
  }
}

class Todo extends React.Component {
  constructor() {
    super();
    this.state = {
      clicked: false
    };
  }

  handleClick = () => {
    this.setState({ clicked: !this.state.clicked });
  };

  render() {
    const styles = this.state.clicked
      ? { textDecoration: "line-through" }
      : { textDecoration: "none" };
    return (
      <div style={styles} onClick={this.handleClick} key={this.props.thing.id}>
        {this.props.thing.text}
      </div>
    );
  }
}

ReactDOM.render(<TodoList />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

<div id="root"></div>

Solution 2:[2]

You can always define the Object/s array within a function accepting an array: In this example:

const [programs, setPrograms] = useState([]);

In order to create a single JSON program array:

setPrograms([{id: program?.id, title: program?.title}]);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tholle
Solution 2 Enrique Cuchetti