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. :)
{text}