Separate Container Components From Presentational Components in React

react

#1

Hi,

I just finished the React course and I’m trying a little exercise on my own. I’m making a todo app, which renders a box with the todo text and an edit and delete button:

Udklip1

When the “edit” button is pressed the component renders a textarea, where you can change the content of the todo:

Udklip2

I have tried to apply the principle we learn in the course, that there should be a separate component for managing state and one for managing JSX. But I’m having trouble making it work without having some state in the component that renders the JSX. To know whether the todo should render text only or render the form I made a property in the parent component called “editing” which is initially set to “false”. I then pass down a function to the child component that can change “editing” to “true”. But this makes all of the child components go into editing mode, which makes sense because all of the children will now have “editing” set to true. So now I handle this state in the child component, as that gives me control over that particular state for each component. Is there a way to manage it in the parent, or is the correct solution to make a new container that manages the state for the child: Parent(manage state) --> TodoContainer(manages state) --> Todo(manages JSX)

Here’s my code:

Parent component:

import React, { Component } from 'react';
import Todo from "./components/Todo";
import NewButton from "./components/NewButton";
import './App.css';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: "",
      todos: ["walk dog", "Code todo app"],
    }

    this.save = this.save.bind(this);
    this.delete = this.delete.bind(this);
    this.addNew = this.addNew.bind(this);
  }

  save(newText, i) {
    const arr = this.state.todos;
    arr[i] = newText;
    this.setState({todos: arr});
  }

  delete(i) {
    const arr = this.state.todos;
    arr.splice(i, 1);
    this.setState({todos: arr});
  }

  addNew() {
    const arr = [...this.state.todos, "New Todo"]
    this.setState({todos: arr});
  }

  render() {
    const todos = this.state.todos.map((text, i) => (
      <Todo key={i} 
            id={i} 
            text={text} 
            inputValue={this.state.inputValue}
            edit={this.edit} 
            save={this.save} 
            editing={this.state.isEditing} 
            delete={this.delete}/>
    ));
    return (
      <div className="App ">
        <div className="col-sm-12">
          <NewButton add={this.addNew}/>
          {todos}
        </div>
      </div>
    );
  }
}

export default App;

Child component

import React, { Component } from "react";
import "./Todo.css"

class Todo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      inputValue: "",
      editing: false
    }

    this.handleEdit = this.handleEdit.bind(this);
    this.handleSave = this.handleSave.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleDelete = this.handleDelete.bind(this);
  }

  handleEdit() {
    this.setState({editing: true});
  }

  handleSave(e) {
    this.props.save(this.state.inputValue, this.props.id);
    this.setState({editing: false});
  }

  handleChange(e) {
    this.setState({inputValue: e.target.value})
  }

  handleDelete() {
    this.props.delete(this.props.id);
  }

  render() {
    let todo;
    let {editing} = this.state;
    if (!editing) {
      todo = (
        <div className="row">
          <div className="todo-container col-sm-4">
            <p className="todo-text">{this.props.text}</p>
            <button type="button" className="btn btn-primary" onClick={this.handleEdit}>Edit</button>
            <button type="button" className="btn btn-danger" onClick={this.handleDelete}>Delete</button>
          </div>
        </div>
      );
    } else if (editing) {
      todo = (
        <div className="row">
          <div className="todo-container col-sm-4">
            <textarea className="todo-textarea" name="update_todo" rows="3" defaultValue={this.props.text} onChange={this.handleChange}></textarea>
            <br/>
            <button type="button" className="btn btn-success" onClick={this.handleSave}>Save</button>
          </div>
        </div>
      )
    }
    return (
      <div>
        {todo}
      </div>
    )
  }
}

export default Todo;

Thanks! :slight_smile:


#2

I think the parent should only handle displaying the list and input.
The child should handle if it should be editable or not. There should be two components then: App -> ToDo

To manage it in the parent, you might need to add the buttons after the Todo item, like
div-container
----Todo
----Edit Button that has a reference to the To-Do item’s key
----Delete Button that has a reference to the To-Do item’s key