Redux Guided Project
This will have two sections - one to move the title functionality built in the last Guided Project for The Reducer Pattern into Redux, then the next to build a list of "Dragon Club Members" where members can acheive "Dragon Status" by having their dragonStatus boolean value toggled. The second part can be used to let students do a lot of the work either on their own, or by them telling you what to do while you type.
Objectives:
explain what Redux is and the problem it solves
create a Redux Store and connect it to a React application
use the connect() function to "connect" React components to the Redux store
write Actions and Action Creators to describe state changes
write Reducers to respond to actions and update state
Sandboxes
Starting sandbox: https://codesandbox.io/s/vyrlxjmmjl Solution sandbox: https://codesandbox.io/s/async-dust-w7j8772p15
Title
Adding Redux to a React app
Add redux and react-redux as dependencies
import { createStore } from 'redux';
Add a store const, then create a store by invoking
createStore
and passing in a reducerconst store = createStore(reducer);
Import and pass in the title reducer we created in the last module
import { Provider } from 'react-redux';
Wrap
<App />
inside<Provider></Provider>
and pass our newly created store toProvider
as the "store" prop
Initialize the titleReducer
The initialization of the titleReducer from useReducer() happens in the useReducer hook. Since we aren't going to be using this call, we need to update the titleReducer.js to use the initialState.
export const titleReducer = (state
= initialState, action) => {
Refactor useReducer() out of Title
Since we are using redux and the reducer that comes with it, we don't need the reducer that's in Title.js
delete
useReducer
from the import on the react linedelete the
const [state, dispatch] = useReducer(titleReducer, initialState);
linedelete the
import { initialState, titleReducer } from "../reducers/titleReducer";
lineadd a props variable to the function component definition
comment out/delete the
dispatch
calls from the onClick handlers
Connect a component to the Redux store
We want to bring our title from the Redux store into the
Title
component, and render that as the title. We will "connect" Title with the connect function.import { connect } from 'react-redux';
connect
gets invoked twice
First invocation it takes in a function and an object (more on those in just a minute)
Second invocation it takes in the component we are connecting
Recognize this pattern? It's an HOC!!!
Let's talk about the first argument in the first function call.
The function is a function that will get the entire Redux state tree passed to it as an argument. We will then map whatever pieces of state we need in this function to the props of this function. So we will write a new function call "mapStateToProps", since that is what it is doing.
Pass
mapStateToProps
in to connect -export default connect(mapStateToProps, {})(Title);
To "map" the state to
Title
's props, we return an object. This object's properties will be properties on the props object inTitle
. The value for the object properties will be from state
Now we have a "title" props in
Title
. Go look in the React tools. Let's rewrite this a bit to make the distinction between on the "titles" more clear:
Now we have a "titleOnProps" prop and "editingOnProps" prop in
Title
. Go look in the React tools.In the header, change the
state.title
to this -`{props.titleOnProps}`In the conditional statement, change the
state.editing
to this -`{props.editingOnProps}`
Actions, actions types, and action creators
Now we have our title from Redux rendering. Let's use the input to give us a way to update our Redux state tree, therefore letting us change the title
Add an "actions" folder with an "index.js" file.
We are going to add an action creator - a function that creates actions. Write a function called
updateTitle
. It will take intitle
as an argument. Console.logtitle
so we can make sure it's working when we go to test this function.
In
Title.js
-import { updateTitle } from '../actions';
Explain that by calling the file in the actions folder,
index.js
, we can import from the directory now, instead of the file. This helps us a ton later when we have a lot of files in one directory.
Now let's discuss the second argument of the first invocation of
connect
.
The second argument is an object that takes in action creators and adds them to props for the connected component. Let's add
updateTitle
.
In the React tools, check out props. We now have our action creator function passed in as a prop!
Add an
onClick
handler on the button that will invoke a function calledupdateTitle
(not to be confused with the action creator, which is available in this component asprops.updateTitle
).Create a function on the class called
updateTitle
. It will take in an event, callpreventDefault
, then callprops.updateTitle
and pass in the input text
Type in the input, and click the button. You should have your text logged from the action creator function.
Back in the actions file. We want the action creator to create and return an action. An action is an object. That's it. This object HAS to have a "type" property, and then sometimes has a "payload" property. The object will tell the store how to update.
Let's return the action object from the action creator.
Since misspelled strings (like "UPDATE_TITLE") can be very very hard to debug, we have a convention we use to help us avoid that bug. We create action types - a variable whose value is the string - and use the action type instead of a string.
This is where some Redux black magic happens. When an action creator returns an action object, that object gets passed into the reducer we made. Let's go handle that now.
Combine reducers
explain we want to now implement the club members interface
create a friendsReducer.js
create an index.js with combineReducer
show that this changes the access to state in the mapStateToProps from
state.title
tostate.titleReducer.title
Dragon Club Members
Move the
members
state from theDragonList
component to theinitialState
object in thereducer/friendsReducer.js
file.Get the list of members from state, and render the list in the DOM. (Can have students lead here)
import connect into
DragonList
connect component
build
mapStateToProps
function and get membersfrom the stateleave the action creators object that you pass into
connect
an empty object for now -export default connect(mapStateToProps, {})(DragonList)
Show members on props. Map over members and render list
Action creator
Go to the action creator file and create a new action creator called
addNewMember
.Also create a "ADD_MEMBER" action type
import that into the component, and pass it into the connect function
Talk about how calling it straight from the import in our code won't dispatch theaction it's returning. It needs to be passed into the connect function.
Now on the "Add Member" button, invoke an "addMember" function in the component - pass it the event.
"addMember" will run
e.preventDefault
, then invokethis.props.addNewMember
and pass inthis.state.newMember
console.log "member" that is coming in to the action creator
the action creator should return an action with that member as the payload.
Reducer
In the
reducer/index.js
file, import your new action type. Then write the case for the action. Talk through the aspects of immutibility here (Don't forget that the new member you're adding to the array needs to be an object!)
(If you have time) Walk through the magic with debugger!
In the component, inside the
addFriend
function, writedebugger;
just above the action creator invocation.Open the project in a new window and open the devtools.
Add a friend. This will take you to thedev tools where you will see the code.
Step "into" the function using the down arrow. This takes you into the
bindActionCreator
function. This is inside "connect". It will call your action creator (hover over actionCreator to see the function name), and pass in your arguments (hover over arguments).If you highlight
actionCreator.apply(this, arguments)
you will see the action that is going to be returned when redux calls the action creator.Notice that this is all wrapped inside the
dispatch
function. More on that later...Step into the function call - now we are in the action creator. Hover over newFriend to see your text. Talk about this returning an action back to where we just were.
Click step into a few times until it returns you to the
bindActionCreator
function. Talk about how our action that was just returned is about to be passed intodisptach
.Click step into again until you are inside the dispatch function.
First three
if
's are simply checking that everything is okay (action is an object and has a type property)Press the step over button until you get into the try block. This is the magic sauce.
Note that we are going to call our reducer, pass in the current state, and our action. Remind students that a reducer takes in the state and an action. This is where the reducer is actually called!
Step into the reducer call. It takes us to our reducer, updates that state, and returns a new state object
Step out of the reducer, and hover over currentState on line 211. It will have our updated state. Push resume to get out of debugger mode.
Repeat this a couple times going faster and faster each time.
Toggle dragon status
Add
onClick
handler on the friend element inside the .map inDragonList
. It will invoke athis.toggleStatus
function in the class and pass it the event and the index of the friend.Build an action creator for toggling. It should get the index of the member that was clicked on passed into it.
Import that into the component and pass it through connect. Then the
toggleStatus
function should invoke it, and pass it the index.Console.log to make sure it's working
In the reducer, build out the case. Again, talk through the logic here.
You can build out a visual way to see that a member has been clicked on, but I usually just inspect it with the react tab and show that the dragon status is toggled
Last updated