import React from node modules
the { Component }
bit is like a shorthand for
var Component = React.Component;
Remember, React has to be in scope because the code generated after its transpiled by babel relies on React being there React.createElement
shorthand
const TodoItem = ({ text, handleClick }) => (
<li>{text} <button onClick={handleClick}>x</button></li>
);
written longhand is
const TodoItem = function(props) {
const text = props.text;
const handleClick = props.handleClick;
return (
<li>{text} <button onClick={handleClick}>x</button></li>
);
}
TodoItem
is a "functional component"
it takes in arguments (props) and returns jsx markup
in the shorthand version, i'm using "destructuring" to pick out the text
and handleClick
properties from props
so essnetially props
is an object like:
const props = {
text: 'drink a beer',
handleClick: () => {}, // some function
}
const { text, handleClick } = props;
// compare that to
const text = props.text;
const handleClick = props.handleClick;
// we're pulling out the text and handleClick properties off an object
you can do that with any object.
we're using ES6 class syntax to create a React Component
We're using class
because this component will have state
its not a functional component like our TodoItem
link to classes
constructor
passes in props, just like our functional component above
but we have to tell React to do something with those props so it sets up all the magic react gives to us, so we have to call
super(props)
here were dealing with class inheritance (look this up, theres a lot there)
here were setting our initial state
our state is an object with a todos
property, which is defaulted with some todos
addTodo is a method we call when we want to add a todo
our <input onKeyUp={this.addTodo.bind(this)} />
receives this method as the keyup handler
so whenever we type in the input, this method gets invoked
line 25, it checks if the key pressed is the Enter
key
if so, we read the inputs value through e.target.value
make a new todo
item out of it, then add it to our state
we have to use .bind(this)
to it because otherwise the this
value inside addTodo
would point to the input
itself, just like youd expect while using jQuery
we're using the setState
method,
setState
comes from the React.Component
that we're extend
ing
it lets our React component App
know that we changed its state, and it should re-render
setState
takes an object, with the property we want to change/set, in this case we want to update our todos
array on our state
so we're using the spread operator to take all the values currently in our state, and carry them over to our new state
const todo = e.target.value;
this.setState({
todos: [todo, ...this.state.todos],
});
e.target.value = '';
this.state.todos
is now a brand new array, with our newly added todo
item, along with all the values in our previous state, by spreading them over our new array. essentially concatenating our old state with our new state
and then we reset our input tag back to an empty input
removeTodo(i)
here we're setting up an event handler to remove a todo item by its index
again we're using setState
to set our todos
array
we're making use of the .slice()
method in 2 places.
the .slice()
method creates a copy of an array,
so we want to first grab all the todos up until, but not including, the index of our todo item in our todos
array, and we're using the ...
spread operator again, to grab all the values in our new array, spread them out into our new array,
and then again for the second half of our todos,
we use todos.slice(i + 1, this.state.todos.length),
to skip over i, i + 1
, and then slice it until the end of the array.
essentailly 3 arrays are being created
this.setState({
todos: [ // <-- first, array literal that will be our new todos array
//
],
})
[
...this.state.todos.slice(0, i), // <-- second array
...this.state.todos.slice(i + 1, this.state.todos.length), <-- third array
]
render()
heres where the magic happens
react takes in the state, and renders our view
most of the magic is going on in the <ul>
we're making use of the array method .map
.map
loops over the array, in this case todos
and applies a function to each item in the array in turn
much like you would do with a for loop
written long hand it is
{this.state.todos.map(function(todo, i) {
return (
<TodoItem
key={i}
text={todo}
handleClick={() => this.removeTodo(i)}
/>
)}
}
the return
value of the function gets added to a new array
and example
function square(n) {
return n * n;
}
function double(n) {
return n * 2;
}
var nums = [1, 2, 3];
var squaredNums = nums.map(square);
var doubledNums = nums.map(double);
console.log(squaredNums); // 1, 4, 9
console.log(doubledNums); // 2, 4, 6
written with for loops
let squaredNums = [];
for (var i = 0; i < nums.length; i++) {
const num = nums[i];
const squared = square(num);
squared.push(squared);
// or simply
squared.push(square(nums[i]));
}
the .map
method maps
an input to an output
double(5)
=> 10
square(12)
=> 144
the .map
passes 2 arguments to our function, the item in the array, and the index of that item in the array
much like a for loop for (var i = 0; i < todos.length; i++){}
so in our app were mapping over our state, taking our todos as input, and outputting a list of <TodoItems />
the key={i}
property on out <TodoItem>
helps react keep track of the items its rendered on the page. so when our state updates,
for instance by adding a todo, it wont have to rerender our whole list item, it can safely inject a new todo item into our list. and then when we delete an item it can remove that item from our ul
without affecting any other element on our page
and then for our TodoItem, for it to be able to remove the todo from our state, we supply the handleClick
property our TodoItem
expects to bind to its button
, we supply it with the i
to it knows which index to remove from our todos
and we give it a text
property which is the todo
item in our array so that it can render it