Black Friday Sale Upgrade Your Home →

React Todo List Example Toggling & Filtering

React Todo List Example (Toggling a Todo)

Building onto what we've been working on, it's time to dispatch our 'TOGGLE_TODO' action. We will do this by clicking on the individual todo items in our bulleted list.

Inside of our TodoApp component, we map each of the todo items into an <li>. We add a click handler so that when a user clicks on an item, we will dispatch an action to the store of type 'TOGGLE_TODO' along with the id to be toggled (we get the id from the todo object.)

In the UI, we want the todo item to appear as crossed out if it has been completed, so we'll use the textDecoration style property.

JAVASCRIPT
.
. // TodoApp component stuff
.
<ul>
{this.props.todos.map(todo =>
<li key={todo.id}
onClick={() => {
store.dispatch({
type: 'TOGGLE_TODO',
id: todo.id
});
}}
style={{
textDecoration:
todo.completed ?
'line-through' :
'none'
}}>
{todo.text}
</li>
)}
</ul>
.
. // More TodoApp component stuff
.

Recap of how toggling a todo item works

Inside the click handler, we dispatch the 'TOGGLE_TODO' action with a type of 'TOGGLE_TODO' and the id of the todo being rendered.

When an action is dispatched, the store will call the root reducer, which will call the todos() reducer with the array of todos & the action.

Since this action is of type 'TOGGLE_TODO', the todos() reducer delegates the handling of every todo item to the todo() reducer by using the map() function to call it for every todo item in state:

JAVASCRIPT
const todos = (state = [], action) => {
switch (action.type) {
// case 'ADD_TODO' stuff
case 'TOGGLE_TODO':
return state.map(t =>
todo(t, action)
);
// default case stuff
}
}

The todo() reducer receives the todo item as state, and 'TOGGLE_TODO' as action. For every todo item whose id doesn't match the id in the action (remember the action's id was supplied by clicking the <li>), we just return the previous state (i.e. the todo object as it was).

However, if the id of the todo matches the id of the action, we'll use ES6 notation to return a new object with all the properties of the original todo, but with the completed field toggled.

JAVASCRIPT
const todo = (state, action) => {
// case 'ADD_TODO' stuff
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state;
}
return {
...state,
completed: !state.completed
};
// default case stuff
};

The updated todo item will be included in the todos field under the new application state, and because we subscribe to the render() function, it's going to get the next state of the application via store.getState() and pass the new version of the todos array to the TodoApp component to be mapped and rendered as a bulleted list (where completed items have a line through them).

JAVASCRIPT
const render = () => {
ReactDOM.render(
<TodoApp
todos={store.getState().todos}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();

Thus, our cycle is complete again.

React Todo List Example (Filtering Todos)

We've created a user interface for our Todo list that lets us add and toggle todo items. Now we will implement the visibility filter to show only the items that the user wants to see.

We will start by creating a new FilterLink component that the user will click to switch the current visible items. This component accepts the filter prop (just a string) & children (the contents of the link).

The component will be a simple <a> tag that when clicked will dispatch an action of type 'SET_VISIBILITY_FILTER' along with a filter prop so the reducer knows which filter is being clicked.

We pass the children down to the <a> tag so the text of the link can be specified.

JAVASCRIPT
.
. // Reducer code, etc.
.
const FilterLink = ({
filter,
children
}) => {
return (
<a href='#'
onClick={e => {
e.preventDefault();
store.dispatch({
type: 'SET_VISIBILITY_FILTER',
filter
});
}}
>
{children}
</a>
)
}
.
.
.

Now that we have created FilterLink, we can use it in our TodoApp component:

JAVASCRIPT
.
. // TodoApp component stuff including the the <ul> of todo items...
. // This <p> tag is to be rendered below the list.
<p>
Show:
{' '}
<FilterLink
filter='SHOW_ALL'
>
All
</FilterLink>
{' '}
<FilterLink
filter='SHOW_ACTIVE'
>
Active
</FilterLink>
{' '}
<FilterLink
filter='SHOW_COMPLETED'
>
Completed
</FilterLink>
</p>
.
. // close the containing `<div>` and the TodoComponent
.

Now we've got some links to select the filter, but they don't have a visible effect yet because we don't interpret the value of the visibility filter.

We need to create a getVisibleTodos() function that will help us filter the todos according to the filter value.

getVisibleTodos() will take two arguments: The list of todos and the filter. Inside will be a switch statement that operates based on the current filter value.

JAVASCRIPT
.
. // FilterLink component creation
.
const getVisibleTodos = (
todos,
filter
) => {
switch (filter) {
case 'SHOW_ALL':
return todos;
case 'SHOW_COMPLETED':
// Use the `Array.filter()` method
return todos.filter(
t => t.completed
);
case 'SHOW_ACTIVE':
return todos.filter(
t => !t.completed
);
}
}

Now we need to call getVisibleTodos() from our TodoApp component before we render the list.

Inside the render() function of the TodoApp component, we get the visible todos by calling getVisibleTodos() with the list of todos and the visibilityFilter from our props.

We will now use visibleTodos instead of this.props.todos when rendering our list.

JAVASCRIPT
.
.
.
class TodoApp extends Component {
render() {
const visibleTodos = getVisibleTodos(
this.props.todos,
this.props.visibilityFilter
);
.
. // Input and Button stuff
.
<ul>
{visibleTodos.map(todo =>
.
. // `<li>` click handling and item rendering
.
)}
</ul>
.
. // `<p>` filter selection stuff
.
}
}

In order to use the visibilityFilter inside of our TodoApp component, we must pass it as a prop inside of our render() function. We could do this explicitly, but it's faster to spread over all of the straight fields inside the state object and pass all of them as props to the TodoApp component.

JAVASCRIPT
const render = () => {
ReactDOM.render(
<TodoApp
{...store.getState()}
/>,
document.getElementById('root')
);
};
store.subscribe(render);
render();

Now when we add some todo items and then "complete" them, we can show the list according to our selected visibility filter.

However, it would be nice to differentiate between our filter links by showing which one we have selected.

We'll start by using ES6 destructuring inside of the TodoApp component to extract todos and visibilityFilter from the props. Now we can access them directly instead of having to type "this.props." every time.

JAVASCRIPT
class TodoApp extends Component {
render() {
const {
todos,
visibilityFilter
} = this.props;
const visibleTodos = getVisibleTodos(
todos,
visibilityFilter
);
return (
.
. // input, button, and list stuff
.

Now we'll include the current visibilityFilter with our FilterLinks so it can know which is the current one and apply different styles accordingly.

JAVASCRIPT
<p>
Show:
{' '}
<FilterLink
filter='SHOW_ALL'
currentFilter={visibilityFilter}
>
All
</FilterLink>
{' '}
<FilterLink
filter='SHOW_ACTIVE'
currentFilter={visibilityFilter}
>
Active
</FilterLink>
{' '}
<FilterLink
filter='SHOW_COMPLETED'
currentFilter={visibilityFilter}
>
Completed
</FilterLink>
</p>

Now that we've included the visibilityFilter, we go back to our FilterLink declaration to add currentFilter as a prop along with a condition that says when the filter is the current filter, it will become static text.

JAVASCRIPT
const FilterLink = ({
filter,
currentFilter,
children
}) => {
if (filter === currentFilter) {
return <span>{children}</span>
}
.
. // rest of `FilterLink` as defined earlier
.
}

To review how changing a visibility filter works

  1. Clicking one of the filter types dispatches an action of type 'SET_VISIBILITY_FILTER' and passes filter which is a prop to the FilterLink component (every one of the 3 links will have a different filter prop).

  2. The store.dispatch() function calls our todoApp() root reducer with the state and the action, which in turn calls the visibilityFilter() reducer with the part of the state and the action.

Note that when the action is of type 'SET_VISIBILITY_FILTER', it doesn't care about the previous state. It just returns action.filter as the next state of the visibilityFilter() reducer. The root reducer will use this new field as part of its new state object.

Because the render() function is subscribed to changes in store, it's going to get the new state object and pass all its keys as props to the TodoApp component.

  1. The TodoApp component receives all the todos as well as the newly updated visibilityFilter as its props, which are then passed to the getVisibleTodos() function The currently visible todos are calculated based on the current visibilty filter ('SHOW_ALL', 'SHOW_COMPLETED', or 'SHOW_ACTIVE').

  2. Depending on which filter is selected, getVisibleTodos() may return a brand new array of todos containing only the appropriate items. This returned array is then enumerated and rendered inside of TodoApp's render() function.

  3. The visibilityFilter field is also used by the FilterLinks as the currentFilter because the FilterLink wants to know if its filter is the current one so it can render the style appropriately (i.e. the current filter is active, so the user shouldn't be able to click it).

...thus the cycle is complete.

  Previous      Next