14. Fetching Data on Route Change
To start, we'll remove the fetchTodos
test API call from our index.js
entry point because we want to fetch the todos from inside our VisibleTodoList
component.
We'll start by importing fetchTodos
into VisibleTodoList.js
import { fetchTodos } from '../api';
The VisibleTodoList
component is generated by the connect
and withRouter
calls that each generate an intermediate component that injects props.
A good place to call our fetchTodos
API would be inside the componentDidMount()
lifecycle hook. However, we can't override the life cycle hooks of generated components! This means we have to create a new React component.
Creating a New React Component
Inside of VisibleTodoList.js
, we'll import React
and the Component
base class from React. We will then declare a React component class called VisibleTodoList
that extends the base component class.
import React, { Component } from 'react';
// other imports...
class VisibleTodoList extends Component {
render() {
return <TodoList {...this.props} />;
}
}
.
.
.
We still want to render the presentational TodoList
component exactly as before. The only purpose of adding this new class is to add the lifecycle hooks. Any props will be passed down to the TodoList
.
Now that VisibleTodoList
is defined as a class above, we can't declare another constant with the same name, so we reassign the VisibleTodoList
binding to point to the wrapped component. We'll also change the connect()
call to wrap our new class instead.
VisibleTodoList = withRouter(connect(
mapStateToProps,
{ onTodoClick: toggleTodo }
)(VisibleTodoList));
export default VisibleTodoList;
The component generated by the connect()
call will render the VisibleTodoList
class we defined. The result of the connect
and withRouter
wrapping calls is the final VisibleTodoList
component that we export from the file.
Adding Lifecycle Hooks
When the component mounts, we want to fetch the todos for the current filter.
It will be convenient to have the filter directly available as a prop, so we change mapStateToProps
to calculate the filter
from params
like it used to do, but we will also pass it as one of the properties of the return
object. So now we'll get both the todos
and the filter
itself inside of the VisibleTodoList
component.
Updating mapStateToProps
const mapStateToProps = (state, { params }) => {
const filter = params.filter || 'all';
return {
todos: getVisibleTodos(state, filter),
filter,
};
};
Going back to the lifecycle method, we can use this.props.filter
inside componentDidMount
. When the todos are fetched, fetchTodos
returns a Promise. We can use the then
method to access the resolved todos
, and log the current filter
and the todos
we just received from the fake backend.
Implementing componentDidMount
class VisibleTodoList extends Component {
componentDidMount() {
fetchTodos(this.props.filter).then(todos =>
console.log(this.props.filter, todos)
);
}
With our current implementation, running the app will show the all
filter being printed, and the corresponding todos
.
However, nothing will happen when filters are changed, because componentDidMount
only runs once. To fix this, we need to add a second lifecycle hook called componentDidUpdate
.
Implementing componentDidUpdate
// inside the `VisibleTodoList` below `componentDidMount()`
componentDidUpdate(prevProps) {
if (this.props.filter !== prevProps.filter) {
fetchTodos(this.props.filter).then(todos =>
console.log(this.props.filter, todos)
);
}
}
componentDidUpdate
receives the previous props as an argument. We then compare the current and the previous values of the filter. If the current filter is not the same as the previous filter, we call fetchTodos()
for the current filter.