David Jarrin
David Jarrin

Reputation: 1749

React Passing state to sibling component and up to parent class

Very very new to React and I seem to be stuck. This is a simple Todo app, I basically have 3 components, the base component, an input component and a task component. I have figured out how to edit the state within each component but I am having trouble passing state from component to component.

class App extends Component {
    render() {
        return (
            <div id="appContainer">
                <HeaderTitle />
                <TaskInput />
                <Task taskState={true} text="task one" />
                <Task taskState={true} text="task two" />
                <Task taskState={true} text="task three" />
            </div>
        );
    }

}

class TaskInput extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
    }
    update(e) {
        this.setState({inputValue: e.target.value});
        console.log(this.state);
    }
    taskCreate(e) {
        this.setState({text: this.state.inputValue, completeState: false});
        console.log('button clicked');
        console.log(this.state);
    }
    render () {
        return (
            <div className="taskInputContainer">
                <TaskInputField update={this.update.bind(this)} taskCreate={this.taskCreate.bind(this)} />
            </div>
        )
    }
}


class Task extends Component {
    constructor(props) {
        super();
        this.state = {
            completeState: false
        }
    }
    toggleTask (e) {
        this.setState({
            completeState: !this.state.completeState
        });
    }
    delete (item) {

    }
    render() {
        return (
            <div className="taskContainer" onClick={this.toggleTask.bind(this)}>
                <div className={"taskState " + this.state.completeState}></div>
                <div className={"taskText " + this.state.completeState }>{this.props.text}</div>
                <div className="taskDelete"><i className="fa fa-times-circle-o" aria-hidden="true"></i></div>
            </div>
        );
    }
}

const TaskInputField = (props) =>
    <div className="taskInputContainer">
        <input type="text" className="taskInputField" onChange={props.update}/>
        <i className="fa fa-plus-circle" aria-hidden="true" onClick={props.taskCreate}></i>
    </div>;

Task.propTypes = {
    text: PropTypes.string.isRequired,
    completeState: PropTypes.bool
};
Task.defaultProps = {
    text: 'Task',
    completeState: false
};

const HeaderTitle = () => (
    <h1>Davids Todo List</h1>
);

export default App;

So in the TaskInput has its own state that I can update but how do I pass that up to the parent component to update and add a Task component? Also how do I add a Task component without re-rendering the whole thing?

Upvotes: 1

Views: 4706

Answers (1)

Mμ.
Mμ.

Reputation: 8542

This issue is documented in detail in the article 'lifting the state up' in React's documentation.

TLDR, you create a handler that updates the state of the current component and pass it to children as props. In the example below (a modified version of your code), I passed down the methods that changes the state of component App, into its children components (TaskInput and Tasks).

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      tasks: [],
    }
  }
    
  addTask = (e, text) => {
    e.preventDefault();
    const newTask = {
      id: new Date().getTime(),
      done: false,
      text
    };
    const newTasks = this.state.tasks.concat([newTask]);
    this.setState({
      tasks: newTasks
    })
  }
    
  toggleTask = (id) => {
    const updatedTask = this.state.tasks.filter(task => task.id === id);
    updatedTask[0].done = !updatedTask[0].done;
    const newTasks = this.state.tasks.map(task => {
      if (task.id === id) {
        return updatedTask[0];
      }
      return task;
    });
    this.setState({
      tasks: newTasks
    });
  }
    
  render() {
    return (
      <div id="appContainer">
        <HeaderTitle />
        <TaskInput addTask={this.addTask} />
        {
          this.state.tasks.length > 0 ? <Tasks tasks={this.state.tasks} toggleTask={this.toggleTask}/> : <div>no tasks yet</div>
        }
      </div>
    );
  }

}

class TaskInput extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      currentInput: ''
    }
  }
  
  handleChangeText = (e) => {
    this.setState({
      currentInput: e.target.value,
    })
  }
  
  render() {
    return (<form>
<input type="text" value={this.state.currenInput} onChange={this.handleChangeText}/><input type="submit" onClick={(e) => this.props.addTask(e, this.state.currentInput)} value="Add Task"/></form>)
  }
} 


const Tasks = (props) => (
  <div>
    {
      props.tasks.map(task => (
        <div 
          style={ task.done ? { textDecoration: 'line-through'} : {} }
          onClick={() => props.toggleTask(task.id)}
          >{task.text}</div>
      ))
    }
  </div>
);

const HeaderTitle = () => (
  <h1>Davids Todo List</h1>
);

ReactDOM.render(<App />, document.getElementById('app'))
<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="app"></div>

Upvotes: 4

Related Questions