This video is available to students only

Building the Event Sign Up App

Learn how to build our very own Event Sign-Up App using the React Redux library with a centralized state management store.

Building the Event SignUp app#

For the remaining three lessons in this module we’ll be building three approaches to using Redux. In this first lesson we’re going to build arguably the most complex of the three approaches. We’ll be using the React Redux library to help us, before transitioning to the Redux Toolkit for the next lesson.

Finally, we’ll see how to implement the redux pattern without any additional libraries using React’s built-in useReducer Hook and the Context system.

First, though, let’s get going with building the Event SignUp app and the vanilla React Redux package.

Project setup#

We’re using Create React App for the remaining lessons and the setup process should be starting to feel familiar by now.

Open up a terminal window to the parent folder that you want to create the new project in and run the following command:

Once the command finishes and our app base is installed, run it to test it’s set up correctly with:

View the running starter app in your browser at http://localhost:3000 .

Cleaning up the starter project#

Just like in previous lessons, let’s clean up the starter app by doing the following:

  • Open index.js and remove the reference to import ‘./index.css'

  • Delete the /src/index.css and /src/App.css files

  • Open the main App.js file and empty the file so we can add our demo-specific code later on.

Adding project dependencies#

Before we create the files we need for our demo, let’s bring in the required dependencies.

Bringing Redux, React Redux, and Redux Thunk onboard#

The first dependencies to add are Redux, React Redux and Redux Thunk (we’ll explain this last one when we come to configure our redux store). Back in a terminal window, make sure you’re in the root project location and enter the following command:

With that done, we can access the various methods and helpers that React Redux provides us.

Adding UUID#

Since we’ll be adding event attendees to our fake event, it’s good practice to give each of them a unique ID value. We could just increment the ID to the next highest in the array of event attendees, but to make it more realistic we’ll assign a uniquely generated ID instead.

To do that, let’s import the very helpful React UUID package with the following command:

Adding Bulma#

We’ll stick with the Node version of Bulma for this demo, adding it to our project using this command:

Adding Sass#

Finally, we’ll also add in Node Sass to compile our .scss files for us. Bring in the final package as a dev-only dependency as follows:

Creating our app’s files#

With all our dependencies added, let’s create all the files we need for the project and build them out.

  1. index.js - we’ll need to make a scant few changes in here to wire up our redux store with the rest of the application.

  2. App.js - the main starting point of our app.

  3. /assets/style.scss - we’ll add in some basic styling niceties in the scss version of Sass.

  4. /components/EventDetails.jsx - a presentational component that will display details of an individual event.

  5. /components/EventSignUpForm.jsx - a Bulma-styled form component whose logic will enable us to add new attendees to the global store via redux methods.

  6. /components/EventSignUpList.jsx - the other side of the event form coin, this component will allow administrators to edit attendees, remove them, and see their details at a glance in a list format.

  7. /components/Header.jsx - another presentational component with scant logic that will act as a navbar and display a message to the user if they’re logged in.

  8. /config/configureStore.js - a small but mighty JavaScript file that configures the redux store at the heart of the redux pattern.

  9. /data/initialState.js - a starting point for the global state in our app.

  10. /reducers/eventReducer.js - this will hold all the reducer functions, state-altering magic and actions for dealing with the event portion of our app’s state.

  11. /reducers/reducers.js - this will be the main reducer file that we associate with our store and app’s redux - effectively it acts as a container for the other, more specific reducers, such as the event reducer.

With the files created, let’s work through them to add in the details needed.

Style.scss#

Open up /assets/style.scss and copy in the following styles:

The import statement at the top of the file brings the Bulma CSS framework styles into the project. Next, we set a couple of SCSS variables and a couple of styles to bring a little nicety to our base app.

initialState.js#

Our app’s state represents a central data storage facility that we can use to house any relevant data we need to share and use across our app. When we create our redux store, we’ll supply it with an initial dataset that represents a starting point; the initial state of our state as it were.

The /data/initialState.js file is going to house such a starting point, so open it up and enter in the following:

There’s very little here, just a plain vanilla JavaScript object that we’re exporting to use elsewhere. You’ll notice that we’ve created an events property on the object that will hold any state data relevant to the events section of the app. You can define your app’s state however you like, but I prefer to have a single initial state file and split it into slices like this.

reducers.js#

Now let’s tackle the reducers.js file. A redux store can only deal with a single reducer. This presents a problem when we have a larger app working on affecting different slices of the app’s state with very different reducer statements and actions and other moving redux parts.

If we were to have only a single reducer, you can imagine that this would grow huge over time and become a maintenance nightmare for us developers. A much better approach would be to have multiple reducers that only deal with a distinct area of the app, however we would then fall foul of the ‘single reducer to a store’ rule…

Fortunately for us the React Redux library provides us with a handy function, combineReducers , which allows us to define our reducer files separately and combine them into a single master reducer (of sorts) that we can then pass into the redux store.

That’s what we’ll be doing with the reducers.js file. First, we’ll import the combineReducers function from the ‘redux` package:

And then import the eventReducer from the /reducers/eventReducer.js file. In a bigger app you might have more reducers, but we’ve only got the one here.

Finally, we’ll export the result of the combineReducers function which accepts an object of key:value pairs, each key being the reference name of that slice of state the reducer affects, and each value being the reducer that affects it. In our case here, they’re one and the same so we can use the shorthand JavaScript object property notation.

eventReducer.js#

Now we come to the main event, even though we’re only a couple of files into the demo and have a few more to complete. However, logically, it makes sense to work through the eventReducer file now and cement the key concepts about Redux in place before creating and wiring up the other parts of the redux puzzle.

Open up the /reducers/eventReducer.js file and let’s begin with the imports:

We’re pulling in the uuid library and our initialState file here. The uuid package will be used to generate a unique ID value when we come to add a new event attendee into state via the upcoming reducer function.

Action types#

Action types are simple string constants that describe the type of action that we want to perform against state. You do not have to do this, or use action types at all, they are just a well-adopted convention. If you choose not to use them then you’ll have to pass around string values here and there. This introduces lots of potential for mistakes such as spelling errors, and that’s without considering that you might want to change any of those strings — in this case, we only have to change them in one place.

Let’s define the action types for the eventReducer now:

Nothing but a simple JavaScript object whose properties describe the action we want to take.

Actions#

Actions are different from action types which are simple describing strings. Actions are functions that return both the type of change that we want to perform against our app’s state (such as adding a new user, deleting a row or fetching a result), and a payload, an object containing the changes to state .

For example, if we wanted to add a new user, we’d create an action that had a type of add-new-user and a payload that was a new user object.

Again, you don’t have to actually do this. However, ultimately we’re going to call a dispatch function (provided to us by the Redux library) which dispatches a notification to the reducer to make some changes to our app’s state. This dispatch function requires a type of action (as we’ve already created) and a payload.

Without creating any action functions, we’d have to call each and every dispatch like this:

As it stands this might not look like much, but imagine that you have a bunch of these updates in the same component and across multiple areas of the app. It’ll start to become cumbersome and bloated to keep doing this as time goes on. Plus, we’re back to the potential for errors and making it harder to make changes; if we wanted to change the payload here, we’d have to find every single instance of this and change it across multiple files.

Compare this with having a single function call that is passed any data changes as parameters:

It’s much easier and cleaner to define and export a series of action functions in each relevant reducer file that accepts one of more state data changes as parameters, and that return a nicely formatted action object.

That’s what we’ll do now. We’re going to create three action functions here:

  • addEventAttendee for adding new event attendees

  • toggleEventAttendance for toggling whether or not someone is coming to the event

  • deleteEventAttendee to remove anyone who is no longer needed from our event signup list

We’re exporting each function so we can call it across our app in whichever component needs to use it. You can see that they’re all pretty straightforward arrow functions. There’s nothing specific to Redux here, but we’ve formatted the return object with a type property (using one of the action types we defined earlier) and a payload which contains any relevant data that needs to be used by the upcoming reducer to affect our state.

Reducer#

The reducer is where all roads point to, and is responsible for making the changes and updates in state.

Remember, it’s important to understand that app state must be immutable, that is, we do not change it. When we talk about changes or updates it’s mainly for conversational convenience. In reality, the whole redux setup is centered around making a new copy of state with changes applied to it.

It’s going to look a little unwieldy at first, but stick with it and you’ll soon make sense of it as the pattern will become familiar. We’ll also be looking at the Redux Toolkit in the next lesson which reduces a lot of this initial complexity in writing reducers.

Let’s define the reducer function with just a single action handler in it:

Again, the reducer is a standard arrow function that accepts a value for state and an action. These values will be passed to the reducer by the React Redux library when everything is wired up so the only thing we have to do at this stage is supply an initial value for state. In our case, this will be the events portion of the initialState object we imported earlier.

The main body of the reducer will be a switch statement that checks the incoming action argument’s type property for a match. When we find a match (in this case when action.type equals actions.ADD_EVENT_ATTENDEE) we can perform some updates to state.

First, we pull out the id and content values from the action’s payload property via destructuring. Then, we need to return a new, updated copy of state with our changes applied.

Notice that we’re including the copy of existing state using the ... spread syntax. Also keep in mind that this reducer will be acting upon the events portion of the overall state. This is wired up via the Redux library, but it stems from our inclusion of the state = initialState.events when we defined this reducer function.

We want to update the eventAttendees property (an array) so we use the spread syntax again and add the new event attendee info from the content object. We’ll add the id value too and set a new property, attending to true because when someone signs up for the first time we assume they’re attending.

Another point of note is that you should always return state from a reducer, even if it’s unaltered. Notice at the end of the switch statement we’re just returning the plain state object via the default switch catch-all case.

Next, we’ll sort out the code to handle the TOGGLE_EVENT_ATTENDANCE and DELETE_EVENT_ATTENDEE actions.

OK, so things are starting to look a little messy and that’s a fair criticism that’s levelled at redux from time to time. However, you can start to see a similar pattern between these different action matches. For the TOGGLE_EVENT_ATTENDANCE case:

  • We pull out the id value and use it to search for a matching event attendee during an Array.map() loop.

  • When we find a match, we update the attending property to its reverse.

  • Finally, we return a new state copy once more with our updatedEventAttendees variable assigned to the eventAttendees state property.

With DELETE_EVENT_ATTENDEE we’re performing a very similar process to the one we've just done, only this time the updatedEventAttendees variable will be populated via Array.filter(), removing the event attendee whose id value matches the one supplied to the reducer.

Well done! You’ll be pleased to know that we’re done with the reducer file and it’s all downhill from here, as we build out the remaining files.

Completed reducer file#

With all the code in, the finished and complete eventReducer.js file will look like this:

configureStore.js#

Open up the /config/configureStore.js file and fill out the following code:

What we’re doing in this file is stitching together our root reducer, initial state values, and what we call enhancers. Enhancers are higher-order functions that enhance a redux store, adding in middleware, persistence capabilities or other additional capabilities that we may want our store to have.

In our case we’re adding a very common middleware, Redux Thunk, which we pulled into our project in the dependencies section. The Thunk middleware allows for asynchronous actions during redux operations. Granted, it’s not something we’re doing here in this demo, but you’ll use this frequently, especially when dealing with API calls or other asynchronous mechanisms in larger projects out in the real world.

  • So, we pull in our imports, the root reducer and initial state.

  • Next, we define an arrow function that creates a middleware enhancer by calling the applyMiddleware function and passing it the thunkMiddleware import.

  • The composedEnhancers is a little redundant here as we only have the one, but we call the compose function from redux and pass it the result of the middlewareEnhancer variable.

  • All that remains is to call the createStore function and pass it our reducer, starting state and the composedEnhancers variable, returning the store value to the caller.

You can find more information on applying middleware at the official Redux documentation, especially in the glossary section.

It might look a little alien at this point, but for now, just be aware that adding middleware to your redux stores is quite common in a lot of projects and the process is usually the same:

  1. Define a store-configuring function

  2. Within it, apply any middleware you need

  3. Compose an enhancer from the applied middleware

  4. Create a redux store that wires up your reducers, initial state and enhancers

  5. Return the store for use

Header.jsx#

Our Header component will be a straightforward website header with some navigation elements in it. Open up the /components/Header.jsx file and let’s start with the classic React import line.

After this, let’s define the entire presentational component like this:

We’re destructuring three properties from props here. isSignedIn will be a boolean value to determine if we’re logged in to the admin side of things. handleSignInClick and handleSignOutClick will be used to handle the button clicks for signing in and out respectively.

 

This page is a preview of Beginner's Guide to Real World React

No discussions yet