4

Im writing a "Todo" app to learn React, and this is my first app (it's using Rails as a backend). Currently I have two components that handle state, which is where im running into an issue.

ListContainer holds the state for "Lists", and a "List" holds the state for individual ListItems. Right now Lists work just fine, however I want to add a simple input box for my "List" component that can quickly add a new ListItem to the List. Currently im able to add a "test" value just fine.

One way would be to make a ListItemForm component, but that seems so unnecessary to just send one value. Im more of a backend person so im sure there is an easy way that's just not obvious. My javascript is also pretty rusty, so im sure it's riddled with errors.

Anyways here is the List component:

import React, { Component } from 'react';
import axios from 'axios';
import ListItem from './ListItem';


class List extends Component { 
    constructor(props){
        super(props)
        this.state = {
            listItems: []
        }
    }

    componentDidMount() {
        axios.get(`/api/v1/lists/${this.props.list.id}/list_items.json`)
        .then(response => {
            console.log("Getting List 'listitems'")
            console.log(response.data)
            console.log(this.state.listItems)
            this.setState({
                listItems: response.data
            })
            console.log(this.state)
        })
        .catch(error => console.log(error))
    }

    addNewListItem = (list_id, content) => {
        axios.post(`/api/v1/lists/${list_id}/list_items`, {
            content: content
          })
        .then(response => {
            console.log(response)
            const listItems = [ ...this.state.listItems, response.data ]
            this.setState({listItems})
            console.log("state of list")
            console.log(this.state)

        })
        .catch(error => {
            console.log(error)
        })
    }

    removeListItem = (listItem_id) => {
        console.log("listItem_id :" + listItem_id);
        axios.delete(`/api/v1/list_items/${listItem_id}`)
        .then(response => {
            console.log(response);
            const listItems = this.state.listItems.filter(
                listItem => listItem.id !== listItem_id
            )
            this.setState({listItems})

        })
        .catch(error => {
            console.log(error)
        })
    }

    render() {
        return (
            <div className="single-list" key={this.props.list.id}>
                <h4>{this.props.list.title}</h4>
                <p>{this.props.list.description}</p>
                {console.log(this.props.list)}
                {this.state.listItems.map( listItem => {
                    return <ListItem key={listItem.id} listItem={listItem} removeListItem={this.removeListItem}/>
                })}
                <button onClick={() => this.props.onRemoveList(this.props.list.id)}>Erase</button>
                <button onClick={() => this.props.onUpdateList(this.props.list.id)}>Update</button>
                <button onClick={() => this.addNewListItem(this.props.list.id,"test")}>Add</button>
            </div>
        )
    }
}

export default List;

I would like the field to be at the bottom and just be a text field with a button. Or is it as simple as having an input and attaching a submit button and onClick handler to send e.target.value to the addNewListItem function? Or am I overthinking this?

Hopefully it makes sense to have the input form within the "List" component and not "ListItem"

msmith1114
  • 2,717
  • 3
  • 33
  • 84
  • Yup, it's just as simple as adding an ``, attaching a `onInput` to it. – Jonas Wilms Nov 08 '19 at 20:45
  • @JonasWilms right but I don't want the function to fire until the user completes typing in whatever they were typing and hitting a button. From what im reading the `onInput` will fire everytime the text changes? – msmith1114 Nov 08 '19 at 20:50
  • 1
    Unless you're submitting form data to a server, you don't need a `
    `. If you want things to happen "on a button press", add your event listener to that button. But it's also important to [remember how React works](/a/36683831/740553) when it comes to capturing user input: you're still going to need to have `onChange` handling on that input to actually get the value the user's typing, and storing that in your state. Then your button's onClick can do whatever it needs to do with that state value; it should _not_ try to access the input element's DOM node or something.
    – Mike 'Pomax' Kamermans Nov 08 '19 at 20:50
  • 1
    Yup. Whenever the input changes, store it in the state. Then when the button gets clicked, create a new item out of that state, and reset the input – Jonas Wilms Nov 08 '19 at 20:51
  • @Mike'Pomax'Kamermans The problem is the state in the List Component is storing the array of ListItems. So it'd require me to make a new state just for that....which I guess isn't the end of the world, just was something I was triyng to avoid. I realize now though that I had forgotten that react won't update without a state change...so I guess it makes sense to make a new state. – msmith1114 Nov 08 '19 at 20:57

1 Answers1

2

you must place the value of the input within the state of the component, and when you call the add button, pass the state of the input.

import React, { Component } from 'react';
import axios from 'axios';
import ListItem from './ListItem';


class List extends Component { 
    constructor(props){
        super(props)
        this.state = {
            listItems: [],
            newItem: ''
        }

        this.handleChange = this.handleChange.bind(this);
    }

    componentDidMount() {
        axios.get(`/api/v1/lists/${this.props.list.id}/list_items.json`)
        .then(response => {
            console.log("Getting List 'listitems'")
            console.log(response.data)
            console.log(this.state.listItems)
            this.setState({
                listItems: response.data
            })
            console.log(this.state)
        })
        .catch(error => console.log(error))
    }

    addNewListItem = (list_id, content) => {
        axios.post(`/api/v1/lists/${list_id}/list_items`, {
            content: content
          })
        .then(response => {
            console.log(response)
            const listItems = [ ...this.state.listItems, response.data ]
            this.setState({listItems})
            console.log("state of list")
            console.log(this.state)

            this.setState({
              newItem: ''
            });
        })
        .catch(error => {
            console.log(error)
        })
    }

    removeListItem = (listItem_id) => {
        console.log("listItem_id :" + listItem_id);
        axios.delete(`/api/v1/list_items/${listItem_id}`)
        .then(response => {
            console.log(response);
            const listItems = this.state.listItems.filter(
                listItem => listItem.id !== listItem_id
            )
            this.setState({listItems})

        })
        .catch(error => {
            console.log(error)
        })
    }

    handleChange (event) {
      this.setState({ newItem: event.target.value });
    }

    render() {
        return (
            <div className="single-list" key={this.props.list.id}>
                <h4>{this.props.list.title}</h4>
                <p>{this.props.list.description}</p>
                {console.log(this.props.list)}
                {this.state.listItems.map( listItem => {
                    return <ListItem key={listItem.id} listItem={listItem} removeListItem={this.removeListItem}/>
                })}
                <button onClick={() => this.props.onRemoveList(this.props.list.id)}>Erase</button>
                <button onClick={() => this.props.onUpdateList(this.props.list.id)}>Update</button>

                <input value={this.state.newItem} onChange={this.handleChange}/>
                <button onClick={() => this.addNewListItem(this.props.list.id, this.state.newItem)}>Add</button>
            </div>
        )
    }
}

export default List;
  • Side question: Why do event handlers I notice for react NEVER seem to use es6 arrows? Wouldn't that avoid the need to bind them? Also is there a good way to clear out the value for the input field after submitting? – msmith1114 Nov 08 '19 at 21:03
  • 1
    @msmith1114 As the value of the input is placed in the state of the component, it is necessary that when your API responds that with a 200 (OK) the state is modified in a vacuum. The arrow functions are used to save a function that needs to be executed, to avoid placing `this.handleChange = this.handleChange.bind(this);` you can try calling the method `onChange={this.handleChange.bind(this)}` of the input. this would eliminate the need to place the method definition within the component's constructor – Jorge Martinez Jimenez Nov 08 '19 at 21:16