1

OK! I listed a more concise example of this question here: Need Help Creating a Simple List Maker App in React JS

I am studying React and building a simple list-maker app for my portfolio. But Im stuck! I am trying to create the functionality for an input form and button that, when clicked, will add info into the list in the body of the page. When I was studying dom manipulation I did a getElementByTagName and then li.appendChild etc. But now I have no idea how to do that in React, because from what I've read, you're not supposed to do dom manipulation in react. Can anyone help me work through creating the proper function to allow the button to add things to the list? And delete items too? Here is my code so far:

import React, { Component } from 'react';
import './App.css';
import Navigation from './components/Navigation';
import ListInput from './components/ListInput';
import ListName from './components/ListName';

class App extends Component {
  constructor() {
    super();
      this.state = {
        input: '',
        items: []
      };    
  }

  addItem = () => {
    this.setState(state => {
      let inputValue = this.input.current.value;
      if (inputValue !== '') {
          this.setState({
            items: [this.state.items, inputValue]
          })
      }
    })
  }


  onButtonEnter = () => {
    this.addItem();
  }

  render() {
    return (
      <div className="App">
       <Navigation />
       <ListName />
       <ListInput addItem={this.addItem}
        onButtonEnter={this.onButtonEnter} />
      </div>
    );
  }
}

export default App;

And here is the code for my ListInput component where I am building the input form and the button to submit the list information into the body of the page:

import React from "react";
import "./ListInput.css";

const ListInput = ({ addItem, onButtonEnter }) => {
  return (
    <div>
      <p className="center f2">{"Enter List Item"}</p>
      <div className="center">
        <div className="center f3 br-6 shadow-5 pa3 ">
          <input
            type="text"
            className="f4 pa2 w-70 center"
            placeholder="Enter Here"
          />
          <button
            className="w-30 grow f4 link ph3 pv2 dib white bg-black"
            onClick={onButtonEnter}
            onSubmit={addItem}
          >
            {"Enter"}
          </button>
        </div>
      </div>
    </div>
  );
};

export default ListInput;

I've tried using .push in one of my functions but it didn't work and I really didn't understand it anyway. Any help would be appreciated thank you!

Ashley E.
  • 473
  • 2
  • 6
  • 12
  • What's the code for ListInput? More to the point, how is ListInput using the props addToList & onButtonEnter? – Chimera.Zen Sep 26 '18 at 17:48
  • @Chimera.Zen the ListInput is my input component, it's in a different file. I just connected it to my App.js file. – Ashley E. Sep 26 '18 at 18:03
  • You are passing `addToList` prop to `ListInput` but expect to get `addItem`. is this a typo? – Sagiv b.g Sep 27 '18 at 21:05
  • oh no! sorry I changed the addToList into addItem – Ashley E. Sep 27 '18 at 21:17
  • i put in the right edits – Ashley E. Sep 27 '18 at 21:24
  • Hey @Sagivb.g. So I'm working through the Item and ItemList components, but my app is returning the error item is not defined. Here is my code for Item: import React from 'react'; const Item = ({text}) =>{ return (
      {text}
    )} export default Item;
    – Ashley E. Sep 30 '18 at 19:08
  • and this is the code for my ItemList Component: import React from 'react'; import Item from './Item'; const ItemList = ({ items }) => { return (
    {item.map(items => )}
    ) } export default ItemList;
    – Ashley E. Sep 30 '18 at 19:12
  • and this is the code for my ItemList Component: import React from 'react'; import Item from './Item'; const ItemList = ({ items }) => { return (
    {item.map(items => )}
    ) } export default ItemList;
    – Ashley E. Sep 30 '18 at 19:13
  • i think you should create a new question with this code. its realy hard to read it in comments and it may be not related directly to this question so other people will benefit from a new question imo. you can link to it here so i will watch it – Sagiv b.g Sep 30 '18 at 19:14
  • Ok. Thanks Sagiv I will do that – Ashley E. Sep 30 '18 at 19:19
  • 1
    Hey @Sagivb.g I reposted and linked the new question. I hope that clears things up. Thank you – Ashley E. Sep 30 '18 at 19:31

2 Answers2

1

You can manipulate the dom elements through React Refs:

https://reactjs.org/docs/refs-and-the-dom.html

I don't know exactly where is your List component, but I'm supposing that this ListName component receives the items as props, and render them into the screen, so I'm just doing this:

import React, { Component } from 'react';
import './App.css';
import Navigation from './components/Navigation';
import ListInput from './components/ListInput';
import ListName from './components/ListName';

class App extends Component {
  constructor() {
    super();
    this.button = React.createRef();
    this.state = {
      input: '',
      items: []
    }
  }

  addToList = () => {
    let inputValue = this.input.current.value;

    if(inputValue !== '') {
      this.setState({
        items: [ ...this.state.items, inputValue ]
      })
    }

  }


  onButtonEnter = () => {
    this.addToList()
  }

  render() {

    const { items } = this.state;

    return (
      <div className="App">
       <Navigation />
       <ListName 
          items={ items }
       />
       <ListInput addToList={this.addToList}
        onButtonEnter={this.onButtonEnter} ref={ this.button } />
      </div>
    );
  }
}

export default App;
Tio Zed
  • 25
  • 4
1

In react we use a declarative way to write our code.

  • We got a state that holds our data and act as our source of truth
  • We got Components and functions that returns the visualization according to the current state

So if you got a list of Items and you want to display them, you will loop on that list and return a visual representation of Item for each item.

When you want to add or remove an item just add or remove an item from that list in the state.

Here is a small running example of such usage:

class Item extends React.Component {
  remove = () => {
    const { id, onRemove } = this.props;
    onRemove(id);
  };

  render() {
    const { text } = this.props;
    return (
      <div style={{ display: "flex" }}>
        <button onClick={this.remove}>Remove</button>
        <div>{text}</div>
      </div>
    );
  }
}

class App extends React.Component {
  state = {
    items: [
      { id: 1, text: "item 1" },
      { id: 2, text: "item 2" },
      { id: 3, text: "item 3" }
    ]
  };

  addItem = () => {
    this.setState(state => {
      const { items } = state;
      const newId = uuid();
      const newItem = { id: newId, text: `item ${newId}` };
      return {
        items: [...items, newItem]
      };
    });
  };

  onItemRemove = itemId => {
    this.setState(state => {
      const { items } = state;
      const filteredItems = items.filter(item => item.id !== itemId);
      return {
        items: filteredItems
      };
    });
  };

  render() {
    const { items } = this.state;
    return (
      <div>
        <div>
          <button onClick={this.addItem}>Add Item</button>
          <hr />
        </div>
        {items.map(item => (
          <Item
            key={item.id}
            id={item.id}
            text={item.text}
            onRemove={this.onItemRemove}
          />
        ))}
      </div>
    );
  }
}

const root = document.getElementById("root");
ReactDOM.render(<App />, root);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.js"></script>
<div id="root"/>

Edit
I'm sorry to hear that you are still confused, but that's OK! you are learning a new technology, and beginnings are always the hardest :)

I advice you to read the DOCS starting with "Thinking in React" carefully and go step by step.

In your original question you asked how to "append" or remove children in react, i think my answer covers this.
As for your new question

My biggest problem is I don't understand how you and the others are figuring out what to do...

You will get this with time, as long as you keep writing and experiencing.
I'm not sure i fully understand what is it that confuse you in my example or in any of the other examples you saw.


I'll try to break down your goal as i understand it:

  • You want to render a list of items.
  • You want a Form that will let the user type in text and add a new item with that text.
  • You want the user to be able to remove an item.

Now lets talk about separation of concerns to architect this App.

Starting from the smallest part, an Item.

Item
If you want to display a list of items, you probably want an <Item /> component.
What's its job? lets say for now to render some text. So the item need a text prop, it would look something like this:

const Item = ({text}) => <div>{text}</div>

OK, moving up.
ItemList
If we want to display multiple items, we would need to get an array of items and loop over them in order to render them.
This is a common task in react and we mostly do it with Array.prototype.map. So our component would look something like this:

const ItemList = ({ items }) => (
  <div>
    {
      items.map(item => <Item key={item.id} text={item.text} />)
    }
  </div>
)

Note the key prop.

OK now, but which of these components should be responsible of adding and removing keys? None! these components responsible for how things should look.
We need another component that manages the data and logic and maybe even doesn't "care" on how things look.

ItemListContainer
This component needs to handle the logic for adding and removing items, and maybe even hold the items in its state, so it can provide them to the ItemList.
If we need a state, it should be a React.Component class:

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  render() {
    const { items } = this.state;
    return <ItemList items={items} />;
  }
}

At this point we have a working app that displays our items:

const Item = ({ text }) => <div>{text}</div>;

const ItemList = ({ items }) => (
  <div>{items.map(item => <Item key={item.id} text={item.text} />)}</div>
);

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  render() {
    const { items } = this.state;
    return <ItemList items={items} />;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<ItemListContainer />, rootElement);
<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="root" />

Now let's think about what do we need to add an item?

  • An addItem function.
  • An input for the item's text.
  • A submit button to trigger the addItem function.

OK so we need another component to wrap the input and button lets call it ItemForm.

But lets ask our self, does ItemForm needs to handle the logic of adding the item? (hint NO!).

The component that holds the data, and responsible for our logic is ItemListContainer. this is where we will write our addItem function.
But we will also need to get the input text from ItemForm and only when the submit button was clicked.

I think we need a state for ItemForm as well. it will manage the input change text, and will trigger a callback passed to it on submission.
It would look something like this:

class ItemForm extends React.Component {
  state = { value: "" };

  onValueChange = ({ target }) => this.setState({ value: target.value });

  onSubmit = () => {
    const { onSubmit } = this.props;
    const { value } = this.state;

    // just validating empty strings
    if (!value) return;

    // clearing the input field
    this.setState({ value: "" });

    // passing the value to the callback from props
    onSubmit(value);
  };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input
          placeholder="enter item text"
          type="text"
          value={value}
          onChange={this.onValueChange}
        />
        <button onClick={this.onSubmit}>Submit</button>
      </div>
    );
  }
}

Great! now all we need to do is render ItemForm in ItemListContainer.
We will create a dummy addItem function just to log the value to see it works:

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  addItem = value => {
    console.log(value);
  }

  render() {
    const { items } = this.state;
    return (
      <div>
        <ItemForm onSubmit={this.addItem} />
        <ItemList items={items} />
      </div>
    );
  }
}

And this is the entire app running:

const Item = ({ text }) => <div>{text}</div>;

const ItemList = ({ items }) => (
  <div>{items.map(item => <Item key={item.id} text={item.text} />)}</div>
);

class ItemForm extends React.Component {
  state = { value: "" };

  onValueChange = ({ target }) => this.setState({ value: target.value });

  onSubmit = () => {
    const { onSubmit } = this.props;
    const { value } = this.state;

    // just validating empty strings
    if (!value) return;

    // clearing the input field
    this.setState({ value: "" });

    // passing the value to the callback from props
    onSubmit(value);
  };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input
          placeholder="enter item text"
          type="text"
          value={value}
          onChange={this.onValueChange}
        />
        <button onClick={this.onSubmit}>Submit</button>
      </div>
    );
  }
}

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  addItem = value => {
    console.log(value);
  }

  render() {
    const { items } = this.state;
    return (
      <div>
        <ItemForm onSubmit={this.addItem} />
        <ItemList items={items} />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<ItemListContainer />, rootElement);
<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="root" />

OK, now for the addItem logic. As we already know, the items is an array in our state, if we want to add a new item all we need to do is add it to our array.
We will not use this.state.push(newItem) as this will mutate the array and that's a big no-no in our react world.
So our addItem function could look something like this:

addItem = value => {
  this.setState(state => {
    const { items } = state;
    // watch it we are missing an id property here!!!
    const newItem = { text: value };
    return {
      items: [...items, newItem]
    };
  });
};

As you can see this is simple as creating a new item with the value as the text property. But notice that we didn't provide an id property, we will need it for our key prop of <Item /> and we will need it later on when we want to remove an item (we will remove it based on the id).
Here you have couple of choices, i'll go with uuid

See the running example with the new logic (added uuid as well):

const Item = ({ text }) => <div>{text}</div>;

const ItemList = ({ items }) => (
  <div>{items.map(item => <Item key={item.id} text={item.text} />)}</div>
);

class ItemForm extends React.Component {
  state = { value: "" };

  onValueChange = ({ target }) => this.setState({ value: target.value });

  onSubmit = () => {
    const { onSubmit } = this.props;
    const { value } = this.state;

    // just validating empty strings
    if (!value) return;

    // clearing the input field
    this.setState({ value: "" });

    // passing the value to the callback from props
    onSubmit(value);
  };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input
          placeholder="enter item text"
          type="text"
          value={value}
          onChange={this.onValueChange}
        />
        <button onClick={this.onSubmit}>Submit</button>
      </div>
    );
  }
}

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  addItem = value => {
    this.setState(state => {
      const { items } = state;
      const newId = uuid();
      const newItem = { text: value, id: newId };
      return {
        items: [...items, newItem]
      };
    });
  };

  render() {
    const { items } = this.state;
    return (
      <div>
        <ItemForm onSubmit={this.addItem} />
        <ItemList items={items} />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<ItemListContainer />, rootElement);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.js"></script>

<div id="root" />

Removing an item
This is not harder than adding an item, we just need 2 things:

  • A remove button next to each Item.
  • A removeItem function that will accept the relevant id of the specific item.

To do that we will need to change our Item component a bit.

const Item = ({ text, onRemove }) => (
  <div>
    <button onClick={onRemove}>X</button>
    {text}
  </div>
);

As you can see, each Item now receives another prop onRemove.
We will need to pass it down from our ItemList which is the component that renders each Item:

const ItemList = ({ items, onRemove }) => (
  <div>
    {items.map(item => (
      <Item key={item.id} text={item.text} onRemove={() => onRemove(item.id)} />
    ))}
  </div>
);

Note how ItemList also recieves an onRemove prop, but its not just passing it down. Its passing it down wrapped with an anonymous function, and passing the id as an argument. Remember? we said that we will need this id in order to know which item to remove.

So lets see how our ItemListContainer looks like now with the new onRemove function:

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  addItem = value => {
    this.setState(state => {
      const { items } = state;
      const newId = uuid();
      const newItem = { text: value, id: newId };
      return {
        items: [...items, newItem]
      };
    });
  };

  onRemove = id => console.log(id);

  render() {
    const { items } = this.state;
    return (
      <div>
        <ItemForm onSubmit={this.addItem} />
        <ItemList items={items} onRemove={this.onRemove} />
      </div>
    );
  }
}

We are only logging the id just to see its working.
And here is the running code:

const Item = ({ text, onRemove }) => (
  <div>
    <button onClick={onRemove}>X</button>
    {text}
  </div>
);

const ItemList = ({ items, onRemove }) => (
  <div>
    {items.map(item => (
      <Item key={item.id} text={item.text} onRemove={() => onRemove(item.id)} />
    ))}
  </div>
);

class ItemForm extends React.Component {
  state = { value: "" };

  onValueChange = ({ target }) => this.setState({ value: target.value });

  onSubmit = () => {
    const { onSubmit } = this.props;
    const { value } = this.state;

    // just validating empty strings
    if (!value) return;

    // clearing the input field
    this.setState({ value: "" });

    // passing the value to the callback from props
    onSubmit(value);
  };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input
          placeholder="enter item text"
          type="text"
          value={value}
          onChange={this.onValueChange}
        />
        <button onClick={this.onSubmit}>Submit</button>
      </div>
    );
  }
}

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  addItem = value => {
    this.setState(state => {
      const { items } = state;
      const newId = uuid();
      const newItem = { text: value, id: newId };
      return {
        items: [...items, newItem]
      };
    });
  };

  onRemove = id => console.log(id);

  render() {
    const { items } = this.state;
    return (
      <div>
        <ItemForm onSubmit={this.addItem} />
        <ItemList items={items} onRemove={this.onRemove} />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<ItemListContainer />, rootElement);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.js"></script>

<div id="root" />

Now that we have all things attached, we can write our logic. this is simple as just using Array.prototype.filter() :

onRemove = id => {
  this.setState(state => {
    const { items } = state;
    const filteredItems = items.filter(item => item.id !== id);
    return { items: filteredItems };
  });
};

Now we have everything works as expected, you can see it here (yay!):

const Item = ({ text, onRemove }) => (
  <div>
    <button onClick={onRemove}>X</button>
    {text}
  </div>
);

const ItemList = ({ items, onRemove }) => (
  <div>
    {items.map(item => (
      <Item key={item.id} text={item.text} onRemove={() => onRemove(item.id)} />
    ))}
  </div>
);

class ItemForm extends React.Component {
  state = { value: "" };

  onValueChange = ({ target }) => this.setState({ value: target.value });

  onSubmit = () => {
    const { onSubmit } = this.props;
    const { value } = this.state;

    // just validating empty strings
    if (!value) return;

    // clearing the input field
    this.setState({ value: "" });

    // passing the value to the callback from props
    onSubmit(value);
  };

  render() {
    const { value } = this.state;
    return (
      <div>
        <input
          placeholder="enter item text"
          type="text"
          value={value}
          onChange={this.onValueChange}
        />
        <button onClick={this.onSubmit}>Submit</button>
      </div>
    );
  }
}

class ItemListContainer extends React.Component {
  state = {
    // starting with 2 items
    items: [{ id: 1, text: "item 1" }, { id: 2, text: "item 2" }]
  };

  addItem = value => {
    this.setState(state => {
      const { items } = state;
      const newId = uuid();
      const newItem = { text: value, id: newId };
      return {
        items: [...items, newItem]
      };
    });
  };

  onRemove = id => {
    this.setState(state => {
      const { items } = state;
      const filteredItems = items.filter(item => item.id !== id);
      return { items: filteredItems };
    });
  };

  render() {
    const { items } = this.state;
    return (
      <div>
        <ItemForm onSubmit={this.addItem} />
        <ItemList items={items} onRemove={this.onRemove} />
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<ItemListContainer />, rootElement);
<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>
<script src="https://cdnjs.cloudflare.com/ajax/libs/node-uuid/1.4.8/uuid.js"></script>

<div id="root"/>

Note that there are some none best practices in my examples like the inline anonymous functions. But don't worry about it now, focus on learning the basics

I hope this clears some confusion you have, and will give you a good starting point to learn this great technology. :)

Sagiv b.g
  • 30,379
  • 9
  • 68
  • 99
  • why are you assigning const newId to the items? – Ashley E. Sep 27 '18 at 20:15
  • to have a unique id for each (and just displaying them as an example) – Sagiv b.g Sep 27 '18 at 20:28
  • im still having trouble. the unique id will be automatically generated when an item is entered? Also I have an app.js component which is rendering everything, but then I have a ListInput component which is where Im building the input and submit button to enter that info. – Ashley E. Sep 27 '18 at 20:33
  • Aw i see, don't let this function -> `uuid()` confuse you. it is a [library that can generate unique strings](https://www.npmjs.com/package/uuid). i was just using it for this example. You can use a different method, as long as [each child in the array will get a different id (and key)](https://reactjs.org/docs/lists-and-keys.html) – Sagiv b.g Sep 27 '18 at 20:37
  • I'm sorry Im still confused. I updated the question so that people can see both my components. My biggest problem is I don't understand how you and the others are figuring out what to do . Thank you – Ashley E. Sep 27 '18 at 20:46
  • wow! Beautiful code! Thank you so much! I will work through your examples and let you know how I do. Have you been a programmer for long? I recently finished a front end course but I feel like coding my own projects is the way to really learn. – Ashley E. Sep 27 '18 at 22:54
  • just one question, with ItemListContainer why would I create that as a class? Since I already have the app.js file with the class in it? and why won't I declare it as this.state etc? Sorry if I seem rigid i'm going off of what I learned in my course – Ashley E. Sep 27 '18 at 22:58
  • `ItemListContainer` is just a name i chose, it can defiantly be `App.js`. As for declaring `state` in the `constructor` as `this.state = {...}` i chose to use a [proposal feature of class fields and properties](https://github.com/tc39/proposal-class-fields). it's just "syntactic sugar". – Sagiv b.g Sep 27 '18 at 23:02
  • @AshleyE. any reason why you're not closing this question? – Sagiv b.g Sep 30 '18 at 20:01
  • Down-voter care to explain why would you want to downvote this answer? Would love to learn where i m wrong – Sagiv b.g Sep 30 '18 at 20:05
  • No! sorry I did that accidentally! @Sagiv – Ashley E. Sep 30 '18 at 20:06
  • I thought I upvoted it. Im pretty sure these are my first times really using stack so sorry I don't really know how it all functions just yet – Ashley E. Sep 30 '18 at 20:09
  • i'll upvote you again because you have been a huge help! – Ashley E. Sep 30 '18 at 20:09
  • Thanks. I'm glad i could help – Sagiv b.g Sep 30 '18 at 20:10
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181034/discussion-between-sagiv-b-g-and-ashley-e). – Sagiv b.g Sep 30 '18 at 20:11