Eric McWinNEr
Eric McWinNEr

Reputation: 533

Child Component not updating after state changes

I am learning react, and I am making a simple ToDoApp. I set some todo data from a JSON file in the state of my App Component and use the values to populate a Child component. I wrote a method to be called each time the onChange event is fired on a checkbox element and flip the checkbox by updating the state. Thing is this code worked perfectly fine before, but it's not anymore. The state gets updated accordingly when I change the checkbox, but it doesn't update in the child element, I'd like to know why. Here's my code

App.js

import React from "react";
import TodoItem from "./TodoItem";
import toDoData from "./toDosData";

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            toDoData: toDoData
        };
        this.handleOnChange = this.handleOnChange.bind(this);
    }

    handleOnChange(key)
    {
        this.setState(prevState => {
            let newState = prevState.toDoData.map(currentData => {
                if(currentData.id === key)
                    currentData.completed = !currentData.completed;
                return currentData;
            });
            return {toDoData: newState};
        });
    }

    render() {
        let toDoComponents = this.state.toDoData.map(toDoDatum =>
            <TodoItem key={toDoDatum.id} details={{
                key: toDoDatum.id,
                text: toDoDatum.text,
                completed: toDoDatum.completed,
                onChange: this.handleOnChange
            }} />);
        return (
            <div>
                {toDoComponents}
            </div>
        );
    }
}

export default App;

TodoItem.js

import React from "react";

class TodoItem extends React.Component {

    properties = this.props.details;

    render() {
        return (
            <div>
                <input type="checkbox" checked={this.properties.completed}
                    onChange={() => this.properties.onChange(this.properties.key)}
                />
                <span>{this.properties.text}</span>
            </div>
        )
    }
}

export default TodoItem;

Thanks in advance.

Upvotes: 0

Views: 57

Answers (1)

devserkan
devserkan

Reputation: 17638

Why do you need to assign your details prop to properties in your class? If you do that properties does not reflect the prop changes and your child component can't see the updates. Just use the props as it is:

render() {
  const { details } = this.props;
  return (
    <div>
      <input
        type="checkbox"
        checked={details.completed}
        onChange={() => details.onChange(details.key)}
      />
      <span>{details.text}</span>
    </div>
  );
}
}

Also, since you don't use any state or lifecycle method in TodoItem component, it can be a functional component as well.

const TodoItem = ({ details }) => (
  <div>
    <input
      type="checkbox"
      checked={details.completed}
      onChange={() => details.onChange(details.key)}
    />
    <span>{details.text}</span>
  </div>
);

One more thing, why don't you pass the todo itself to TodoItem directly?

<TodoItem
  key={toDoDatum.id}
  todo={toDoDatum}
  onChange={this.handleOnChange}
/>

and

const TodoItem = ({ todo, onChange }) => (
  <div>
    <input
      type="checkbox"
      checked={todo.completed}
      onChange={() => onChange(todo.id)}
    />
    <span>{todo.text}</span>
  </div>
);

Isn't this more readable?

Update after comment

const toDoData = [
  { id: 1, text: "foo", completed: false },
  { id: 2, text: "bar", completed: false },
  { id: 3, text: "baz", completed: false }
];

const TodoItem = ({ todo, onChange }) => (
  <div>
    <input
      type="checkbox"
      checked={todo.completed}
      onChange={() => onChange(todo.id)}
    />
    <span>{todo.text}</span>
  </div>
);

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      toDoData: toDoData
    };
    this.handleOnChange = this.handleOnChange.bind(this);
  }

  handleOnChange(key) {
    this.setState(prevState => {
      let newState = prevState.toDoData.map(currentData => {
        if (currentData.id === key)
          currentData.completed = !currentData.completed;
        return currentData;
      });
      return { toDoData: newState };
    });
  }

  render() {
    let toDoComponents = this.state.toDoData.map(toDoDatum => (
      <TodoItem
        key={toDoDatum.id}
        todo={toDoDatum}
        onChange={this.handleOnChange}
      />
    ));
    return <div>{toDoComponents}</div>;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

Upvotes: 2

Related Questions