As you might remember from the Redux-based Pokedex project, implementing Redux results in a lot of boilerplate code. Using Redux hooks can help clean up and get rid of a lot of boilerplate code. In this phase you will refactor the Redux-based project to use React hooks and implement Redux hooks!
Begin by creating a new branch for your Redux-based application:
Take a moment to download the Redux-based Pokedex hooks starter project. Make sure you are in your new redux-hooks-app
branch:
Delete all of your project’s current code and move the files of redux-based-pokedex-solution
into your current directory (react-hooks-pokedex-starter
). For example, if both project directories are within the same directory, you can use the mv
command from the two project folders’ parent directory.
Now take a moment to commit the start of your Redux-based project:
In this phase, you’ll be refactoring all your component files to use Redux hooks instead of the mapStateToProps
, mapDispatchToProps
, and Redux connect
functions. Just like in phase 1, you might hit bugs and break your application while refactoring your application’s components. Make sure to test that your refactored code is working before moving on to refactor the next component. As a general overview, you’ll be refactoring the code for the following components:
LogoutButton
LoginPanel
PokemonDetail
PokemonForm
PokemonBrowser
There are two ways you can refactor your project:
useDispatch
and useSelector
hooks in a container component.useDispatch
and useSelector
hooks within the component itself.You can choose either method to refactor your project. At this point, you are a full-fledged React developer - it’s time for you to start planning your own React code! Talk things through with your partner and decide which way you would like to begin refactoring your project. Remember, as you are using this project as practice for implementing Redux hooks, you can always implement Redux hooks into a container component for one component, and then implement Redux hooks directly in another component for the next component. Just make sure to choose one method to stick with for your personal React projects!
If you chose to use the useDispatch
and useSelector
hooks in a container component, this means you should use a namespace to import actions into each component file. For example, in the LogoutButton.js
file, you currently have the following import statement that imports the logout
action creator function:
In order to remove confusion about whether an invocation of logout()
is invoking the logout
prop or the logout
action creator function, you can update the import statement for your logout action to use an AuthAction
namespace:
This way, you can create a container component to replace what is happening under the hood with the mapStateToProps
, mapDispatchToProps
, and connect
functions. You can reference the logout action with the AuthAction
namepsace, like so: AuthAction.logout
.
Based on the mapStateToProps
and mapDispatchToProps
functions in the LogoutButton.js
file, you can tell that the component is accessing Redux by receiving loggedOut
and logout
props:
const mapStateToProps = state => {
return {
loggedOut: !state.authentication.token,
};
};
const mapDispatchToProps = dispatch => {
return {
logout: () => dispatch(logout()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LogoutButton);
Take a moment to import the useSelector
and useDispatch
from the Redux library into the file:
Now you’ll write a container component that will replace the mapStateToProps
, mapDispatchToProps
, and connect
functions! Start by setting up the container component that returns the LogoutButton
component to use the useDispatch
prop. You’ll also want to have the LogoutButton.js
file export the LogoutButtonContainer
component (instead of the higher-order component returned by the connect
function):
const LogoutButtonContainer = () => {
const dispatch = useDispatch();
return <LogoutButton />;
};
export default LogoutButtonContainer;
Feel free to visit the Redux Hooks documentation to view
useDispatch
examples.
Now that you have the container component and dispatch
set up, you can pass dispatched version of the logout
action as a prop into the LogoutButton
component:
const LogoutButtonContainer = () => {
const dispatch = useDispatch();
const logout = () => dispatch(AuthAction.logout());
return <LogoutButton logout={logout} />;
};
At this point, the container component is taking care of what the mapDispatchToProps
function took care of! Now let’s use the useSelector
hook to take care of what the mapStateToProps
function took care of:
const LogoutButtonContainer = () => {
const dispatch = useDispatch();
const logout = () => dispatch(AuthAction.logout());
const loggedOut = useSelector(state => !state.authentication.token);
return <LogoutButton logout={logout} />;
};
Feel free to visit the Redux Hooks documentation to view
useSelector
examples.
Now that you’ve gone over how to create a container component that implements Redux Hooks for the LogoutButton
component, follow the same pattern to implement Redux hooks into container components for your LoginPanel
, PokemonDetail
, PokemonForm
, and PokemonBrowser
components. Feel free to practice implementing Redux hooks directly within a component instead!
If you chose to use the useDispatch
and useSelector
hooks within the component itself, you’ll need to do some refactoring so that your component doesn’t receive any props. Instead of receiving slices of state and dispatchable action functions as props, you will use the useSelector
hook to access a slice a state from within the component and the useDispatch
hook to dispatch actions from within the component.
Based on the mapStateToProps
and mapDispatchToProps
functions in the LogoutButton.js
file, you can tell that the component is accessing Redux by receiving loggedOut
and logout
props.
const mapStateToProps = state => {
return {
loggedOut: !state.authentication.token,
};
};
const mapDispatchToProps = dispatch => {
return {
logout: () => dispatch(logout()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LogoutButton);
Take a moment to import the useSelector
and useDispatch
from the Redux library into the file.
Now you’ll want to remove all the props that the LogoutButton
receives. Instead of receiving props to access the loggedOut
state and dispatched logout
function, you’ll use the useSelector
and useDispatch
hook you just imported into the file. At this point, your LogoutButton
component should look something like this:
const LogoutButton = () => {
if (loggedOut) {
return <Redirect to="/login" />;
}
return (
<div id="logout-button-holder">
<button onClick={handleClick}>Logout</button>
</div>
);
};
Now you’ll use Redux hooks within the LoginButton
component so that you can remove the mapStateToProps
, mapDispatchToProps
, and connect
functions! Instead of receiving a loggedOut
prop, you’ll use the useSelector
hook to access the state’s authentication.token
.
const LogoutButton = () => {
const loggedOut = useSelector(state => !state.authentication.token);
// CODE SHORTENED FOR BREVITY
};
Feel free to visit the Redux Hooks documentation to view
useSelector
examples.
Notice how the logout
thunk action creator function has already been imported into your LogoutButton.js
file. You’ll use useDispatch
hook to return a reference to the dispatch
function from the Redux store:
Then you can use the dispatch
function to dispatch the logout
function:
const LogoutButton = () => {
const loggedOut = useSelector(state => !state.authentication.token);
const dispatch = useDispatch();
const handleClick = () => dispatch(logout());
// CODE SHORTENED FOR BREVITY
};
Feel free to visit the Redux Hooks documentation to view
useDispatch
examples.
Lastly, you’ll want to remove the mapStateToProps
and mapDispatchToProps
functions from the file, and replace the connect
function to an export statement that exports the LoginButton
component by default:
Now that you’ve gone over how to refactor your LogoutButton
component, follow the same pattern to implement Redux hooks into your LoginPanel
, PokemonDetail
, PokemonForm
, and PokemonBrowser
components. Feel free to practice creating a container component that utilizes Redux hooks instead!
useParams
Notice the references to the React Router match
prop accessed in your PokemonBrowser
and PokemonDetail
components. Instead of having your component take in a match
prop to access the route parameters, you’ll implement the useParams
prop and use object destructuring to access the pokemonId
parameter in the PokemonBrowser
component and the id
parameter in the PokemonDetail
component. Feel free to visit React Router documentation to view examples of using the useParams
hook.
Once you have finished refactoring, take a moment to commit your changes to your redux-hooks-app
branch:
Now that you have practiced refactoring your application to implement Redux hooks, it’s time to work on a Context-based project utilizing React’s useContext
hook! In the next phase, you’ll branch off from your application’s main branch to create a new context-hooks-app
branch for the project.