The application is cleaner, but now has that bug of infinite redirecting. You will first fix that.
In the App
component, it would change state in response to the LoginPanel
calling the updateToken
method. The LoginPanel
no longer does that because it dispatches its information to a thunk that makes the AJAX call and, in turns, dispatches an action that updates the Redux store. Then, LoginPanel
uses the value from the store to no that something good has happened. You need to have App
do the same.
Since updateToken
is no longer used, remove the method and all of its uses from the App
component.
When you have that done, you may notice that the route that shows the LoginPanel
looks like this.
That is rendering LoginPanel
and passing its props to it. That’s just like using the “component” property of the Route
component, so change it to use that instead of the more expensive “render” property.
You’re still in a render loop, so that hasn’t fixed it. Time to connect App
to the Redux store.
LoginPanel
, import the connect
function from the “react-redux” module.LoginPanel
, declare mapStateToProps
and mapDispatchToProps
functions after the component.mapStateToProps
function, map the token to a property named “token” identically to the way it is done in LoginPanel
.mapDispatchToProps
just returning an empty object.App
to the Redux store using those functions.That puts the value of the token into the props. In the actual App
component, now find everywhere that uses this.state.token
and replace it with this.props.token
.
Try logging in, again. Still doesn’t work. That’s because App
relies on another setting in its state named “needLogin” which is just a Boolean value that is basically the opposite of whether the token has a value. If there is a token, there is no need to login. If there is no token, there is a need to login. You can figure that out in the mapStateToProps
function! Create another property, one named “needLogin”, in the object returned from the function. If there is a value in state.authentication.token
, then set it to false
. If there is no value (or an empty string) in state.authentication.token
, then set “needLogin” to true
. Once you have that, find every use of this.state.needLogin
and replace it with this.props.needLogin
.
You have now fixed the problem with the infinite redirects! And, once you log into the application, you can check that the App
component is rendering the PokemonBrowser
component. However, it is not showing anything. That’s because the App
component calls its loadPokemon
method in the componentDidMount
method. There’s no token at that time in its state because the token now comes from Redux.
What would be great is if, after logging in, the login
action loaded the Pokemon for you, put them in the store, and PokemonBrowser
could just use them directly.
Oh, that’s what you’ll do next.
Now, you need some actions, thunks, and reducers for Pokemon, not authentication. Create a new file src/store/pokemon.js. In there, create the following items.
LOAD
with a value of ‘pokedex/pokemon/LOAD’load
that takes in a list of Pokemon and creates an action with the type of LOAD
and the list of Pokemon in itstate
parameterNow, you need to make a thunk that will make the AJAX call. Call your think “getPokemon”. The thunk needs the token from the state to make its API call. The function that gets the “dispatch” parameter can also get a second parameter, a function conventionally called “getState”. Your thunk could look like this.
export const getPokemon = () => async (dispatch, getState) => {
const { authentication: { token } } = getState();
// AJAX call
// Handle response
};
Then, in configureStore.js, import the default reducer and add it to the combineReducers
argument as the “pokemon” property next to “authentication”.
Now, you just need to kick off that AJAX call. It seems only reasonable that the component that actually needs it kicks it off, the PokemonBrowser
component. In PokemonBrowser
, do the following:
connect
function from “react-redux”getPokemon
thunk you just createdmapStateToProps
function that maps the list of Pokemon from state.pokemon.list
(or whatever you called your property in the reducer) to a property named “pokemon”mapDispatchToProps
function that returns an object with a property named “getPokemon” that dispatches the getPokemon
thunk you importedconnect
, mapStateToProps
, and mapDispatchToProps
componentDidMount
method to the PokemonBrowser
component and call this.props.getPokemon()
from itNow, when you log into the application, you should see two actions in your Redux store!
More importantly, you should see the list of Pokemon in the PokemonBrowser
actually appear in the browser.
Previously, all of the fetching logic for the list of Pokemon was handled by the App
component. You can now get rid of the loadPokemon
method and clean up any calls to it. Also, anywhere that refers to this.state.pokemon
or using setState
to update the “pokemon” property, you should get rid of all of that. This makes the handleCreated
method empty, so get rid of that and all references to it, too, in the App
component.
Because the App
component doesn’t make any AJAX calls, anymore, it doesn’t need the token from the state. Remove the “token” property in mapStateToProps
and everywhere token
is used in the App
component. Include the call to local storage, too, in the deleting of things. You can remove the import of baseUrl
, too, because there are no AJAX calls in the file.
Now, because cProps
in the render
method is empty, delete it and its uses in the PrivateRoute
components below it. Now that the code is not using cProps
, you can delete the parameter and its use from the PrivateRoute
component on line 8 (or so) of the src/App.js file.
Actions that use outside resources like AJAX calls and local storage must be created in thunks. Back in the src/store/authentication.js module, do two things:
TOKEN_KEY
and set it equal to some non-empty stringlogin
thunk, between getting the token from the response and dispatching the setToken
action, write the token to local storage using the constant TOKEN_KEY
You need a new thunk to do this. Create a thunk named loadToken
that takes no parameters, and returns a function that accepts a “dispatch” parameter. Then, implement it to read the value from local storage using the TOKEN_KEY
constant. If a value comes back, have it dispatch the setToken
action.
In the App
component, import the loadToken
thunk. Use it in the mapDispatchToProps
by mapping the dispatch of the loadToken
thunk to a property of the same name.
Invoke that loadToken
method in the componentDidMount
method of the App
component.
The moment you do that, the page should refresh and you should see the Pokemon browser rather than the login form.
You’re halfway home!
The application is cleaner, but now has that bug of infinite redirecting. You will first fix that.
In the App
component, it would change state in response to the LoginPanel
calling the updateToken
method. The LoginPanel
no longer does that because it dispatches its information to a thunk that makes the AJAX call and, in turns, dispatches an action that updates the Redux store. Then, LoginPanel
uses the value from the store to no that something good has happened. You need to have App
do the same.
Since updateToken
is no longer used, remove the method and all of its uses from the App
component.
When you have that done, you may notice that the route that shows the LoginPanel
looks like this.
That is rendering LoginPanel
and passing its props to it. That’s just like using the “component” property of the Route
component, so change it to use that instead of the more expensive “render” property.
You’re still in a render loop, so that hasn’t fixed it. Time to connect App
to the Redux store.
LoginPanel
, import the connect
function from the “react-redux” module.LoginPanel
, declare mapStateToProps
and mapDispatchToProps
functions after the component.mapStateToProps
function, map the token to a property named “token” identically to the way it is done in LoginPanel
.mapDispatchToProps
just returning an empty object.App
to the Redux store using those functions.That puts the value of the token into the props. In the actual App
component, now find everywhere that uses this.state.token
and replace it with this.props.token
.
Try logging in, again. Still doesn’t work. That’s because App
relies on another setting in its state named “needLogin” which is just a Boolean value that is basically the opposite of whether the token has a value. If there is a token, there is no need to login. If there is no token, there is a need to login. You can figure that out in the mapStateToProps
function! Create another property, one named “needLogin”, in the object returned from the function. If there is a value in state.authentication.token
, then set it to false
. If there is no value (or an empty string) in state.authentication.token
, then set “needLogin” to true
. Once you have that, find every use of this.state.needLogin
and replace it with this.props.needLogin
.
You have now fixed the problem with the infinite redirects! And, once you log into the application, you can check that the App
component is rendering the PokemonBrowser
component. However, it is not showing anything. That’s because the App
component calls its loadPokemon
method in the componentDidMount
method. There’s no token at that time in its state because the token now comes from Redux.
What would be great is if, after logging in, the login
action loaded the Pokemon for you, put them in the store, and PokemonBrowser
could just use them directly.
Oh, that’s what you’ll do next.
Now, you need some actions, thunks, and reducers for Pokemon, not authentication. Create a new file src/store/pokemon.js. In there, create the following items.
LOAD
with a value of ‘pokedex/pokemon/LOAD’load
that takes in a list of Pokemon and creates an action with the type of LOAD
and the list of Pokemon in itstate
parameterNow, you need to make a thunk that will make the AJAX call. Call your think “getPokemon”. The thunk needs the token from the state to make its API call. The function that gets the “dispatch” parameter can also get a second parameter, a function conventionally called “getState”. Your thunk could look like this.
export const getPokemon = () => async (dispatch, getState) => {
const { authentication: { token } } = getState();
// AJAX call
// Handle response
};
Then, in configureStore.js, import the default reducer and add it to the combineReducers
argument as the “pokemon” property next to “authentication”.
Now, you just need to kick off that AJAX call. It seems only reasonable that the component that actually needs it kicks it off, the PokemonBrowser
component. In PokemonBrowser
, do the following:
connect
function from “react-redux”getPokemon
thunk you just createdmapStateToProps
function that maps the list of Pokemon from state.pokemon.list
(or whatever you called your property in the reducer) to a property named “pokemon”mapDispatchToProps
function that returns an object with a property named “getPokemon” that dispatches the getPokemon
thunk you importedconnect
, mapStateToProps
, and mapDispatchToProps
componentDidMount
method to the PokemonBrowser
component and call this.props.getPokemon()
from itNow, when you log into the application, you should see two actions in your Redux store!
More importantly, you should see the list of Pokemon in the PokemonBrowser
actually appear in the browser.
Previously, all of the fetching logic for the list of Pokemon was handled by the App
component. You can now get rid of the loadPokemon
method and clean up any calls to it. Also, anywhere that refers to this.state.pokemon
or using setState
to update the “pokemon” property, you should get rid of all of that. This makes the handleCreated
method empty, so get rid of that and all references to it, too, in the App
component.
Because the App
component doesn’t make any AJAX calls, anymore, it doesn’t need the token from the state. Remove the “token” property in mapStateToProps
and everywhere token
is used in the App
component. Include the call to local storage, too, in the deleting of things. You can remove the import of baseUrl
, too, because there are no AJAX calls in the file.
Now, because cProps
in the render
method is empty, delete it and its uses in the PrivateRoute
components below it. Now that the code is not using cProps
, you can delete the parameter and its use from the PrivateRoute
component on line 8 (or so) of the src/App.js file.
Actions that use outside resources like AJAX calls and local storage must be created in thunks. Back in the src/store/authentication.js module, do two things:
TOKEN_KEY
and set it equal to some non-empty stringlogin
thunk, between getting the token from the response and dispatching the setToken
action, write the token to local storage using the constant TOKEN_KEY
You need a new thunk to do this. Create a thunk named loadToken
that takes no parameters, and returns a function that accepts a “dispatch” parameter. Then, implement it to read the value from local storage using the TOKEN_KEY
constant. If a value comes back, have it dispatch the setToken
action.
In the App
component, import the loadToken
thunk. Use it in the mapDispatchToProps
by mapping the dispatch of the loadToken
thunk to a property of the same name.
Invoke that loadToken
method in the componentDidMount
method of the App
component.
The moment you do that, the page should refresh and you should see the Pokemon browser rather than the login form.
You’re halfway home!