0

So I have a working "Todo" list implementation currently that's using Rails for a backend API and React for the frontend (Along with axios to make requests). Im familiar with Rails but React is still new to me.

Currently I have 4 Components, but the 2 I'm concerned with are List and ListItem.

List.js

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: ''
        }
    }

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

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

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

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

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

    handleChange = (e) => {
        this.setState({ newItem: e.target.value });
      }

    render() {
        return (
            <div  key={this.props.list.id}>
            <div className="card">
                <div className="card-body">
                    <h4 className="card-title">{this.props.list.title}</h4>
                    <p className="card-text">{this.props.list.description}</p>
                </div>
                <ul className="list-group list-group-flush">
                    {this.state.listItems.map( listItem => {
                        return <li className="list-group-item" key={listItem.id}><ListItem listItem={listItem} removeListItem={this.removeListItem}/></li>
                    })}
                </ul>
            </div>
                <form>
                    <div className="form-group">
                        <input className="form-control" placeholder="...." value={this.state.newItem} onChange={this.handleChange}/>
                    </div>
                        <button className="btn btn-primary btn-sm mx-1" onClick={() => this.addNewListItem(this.props.list.id, this.state.newItem)}>Add Item</button>
                        <button className="btn btn-info btn-sm mx-1" onClick={() => this.props.onUpdateList(this.props.list.id)}>Update</button>
                        <button className="btn btn-danger btn-sm mx-1" onClick={() => this.props.onRemoveList(this.props.list.id)}>Erase</button>
                </form>
            </div>

        )
    }
}

export default List;

ListItem.js

import React, { Component } from 'react';

const ListItem = (props) =>
    <div className="list-item" >
        {props.listItem.content}
        <button className="float-right btn btn-outline-danger btn-sm" onClick={() => props.removeListItem(props.listItem.id)}>Delete</button>
    </div>

export default ListItem;

ListContainer wraps around List however that's not the issue im facing. It seems that when I add an item to my List, it causes a refresh of the entire page (I can see the styling go away for a second). I tried to take a sample video but unfortunately the video doesn't show it since it happens so quickly.

I can tell the screen is refreshing because when I watch the console and "preserve log" I see this:

VM412:1 XHR finished loading: POST "http://localhost:3000/api/v1/lists/8/list_items".

then

Navigated to http://localhost:3000/?

So it seems like after I POST to my API (and update the state) what I feel like is happening is that it's re-rendering on the addNewListItem AND on the componentDidMount. Maybe im mis-using componentDidMount but it made the most sense to pull in the "most updated" items when the component mounts.

It's odd that it's getting re-called though? Shouldn't componentDidMount only get called once? (I can tell in my console it's getting called again after the AddNewListItem runs based on console logs. Or maybe it's working fine and this is just some weird browser glitch from running locally?

msmith1114
  • 2,717
  • 3
  • 33
  • 84

1 Answers1

3

When you place buttons in forms and do not specify a type they will default to submit, causing your page you reload as they try to call the form action. Make a habit of always specifying the button type - <button type="button">.

For the button which actually submits the form, set the type to submit and handle the event with <form onSubmit={handleSubmit}> and remember to call event.preventDefault() in there to stop the page reloading. You should not handle submit buttons with onClick this is bad practice as it isn't accessible (Return key won't submit the form). But onClick is fine for non submitting actions.

You should also wrap your ListItem in memo as currently every item is re-reconciling even though only one item changed - export default React.memo(ListItem) which is the functional equivalent of React.PureComponent.

Dominic
  • 62,658
  • 20
  • 139
  • 163
  • 1
    Ah! that makes sense. I fixed it by doing also `onClick={(e) => this.addNewListItem(e,this.props.list.id, this.state.newItem)}` and then calling `e.preventDefault()` but your way seems much better!. Also I am not familiar with memo's so I will have to read up on that! – msmith1114 Nov 12 '19 at 03:23