└── README.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | TylerMcGinnis.com's Redux Curriculum 4 | ======== 5 | 6 | #### For more information on this course, [click here](https://tylermcginnis.com/courses/redux) 7 | 8 | ## Objective 9 | Build a functioning "Would you Rather" application using React.js, Redux, Firebase, and Immutable.js. The end result of this project can be found [HERE](https://tylermcginnis.com/Redux-Curriculum). 10 | 11 | ## Notes 12 | The goal here is to give you just enough guidance for you to struggle without drowning. Note that the steps below are just suggestions. The ideal situation is you look at the completed project, then you build it. However, if you're not up for such things, feel free to follow the (vague by design) steps below. If you get stuck, all steps have coinciding branches for you to reference as a last case scenario. 13 | 14 | ## Step 0: Examine the Final Product 15 | * Head over [HERE](https://tylermcginnis.com/Redux-Curriculum) and play around with the final project. Think about how you would separate your different components and functionality. 16 | 17 | ## Step 1: Set up a HelloWorld Component 18 | Before I ever start a React app, no matter how complex, I always create a HelloWorld component just to make sure that I've tied everything together properly. I don't expect you to have all this memorized, but do your best to wire up everything by yourself. If you do get stuck you can refer to the 'step1' branch. 19 | 20 | * Create a new project or fork this repository 21 | * npm install the dependencies you'll need (for a basic HelloWorld app). Include ES6+. 22 | 23 | ```npm install --save react react-dom``` 24 | ```npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 css-loader html-webpack-plugin style-loader webpack webpack-dev-server``` 25 | 26 | * Create and configure your .babelrc file 27 | * Create and configure your webpack.config.js file 28 | * Create an app directory and in your app directory create and configure your index.html file 29 | * In your app directory create and configure your index.js file to render a HelloWorld component 30 | * Start webpack and make sure everything is working 31 | * Add a `production` command to your scripts property in package.json which runs `webpack -p` 32 | * Add a `start` command to your scripts property in package.json which runs `webpack-dev-server` 33 | * Run `npm run start` from your terminal then check `localhost:8080` to make sure everything is rendering correctly 34 | 35 | ## Step 2: Improve Webpack 36 | If you followed my example above, our webpack configurations are super minimal. Let's go ahead and beef them up a bit by adding CSS Module source maps, path file resolving, source maps, Hot Module Replacement, and a production build. 37 | 38 | * We're going to need babel-preset-react-hmre, so npm install that as a dev dependency. 39 | * In order for us to use ES+ features in our webpack.config.js file we need to change the name to webpack.config.babel.js. Do that now. 40 | * As we did in the videos, we're going to create a different configuration object for both production and development then merge them together based on if we're in production mode or not. First, utilize `process.env.npm_lifecycle_event` to create a variable to know if we're in 'production' mode or not. Remember, `process.env.npm_lifecycle_event` will tell us what command was ran in order to start our server. If we run `npm run production`, then `process.env.npm_lifecycle_event` will be production. 41 | * Let your .babelrc file know if you're in production by utilizing process.env.BABEL_ENV. 42 | * If the previous step is done correctly you can now set up different presets in your .babelrc file based on which mode we're running in. Head over to your .babelrc file and make it so when we're in development mode (or we ran `npm run start` to start our server), then we set the "react-hmre" preset. Don't worry if you don't have all of this memorized, you shouldn't, if you do copy/paste a lot of this configuration code, just make sure you know what's going on. 43 | * Back in your webpack.config.babel.js file create a productionPlugin variable which uses Webpack's DefinePlugin property to set `process.env.NODE_ENV` to production. This is critical to have fast performing code in production. 44 | * Create three variables, `base`, `developmentConfig`, and `productionConfig`. Base has all of the config properties that are shared with with `developmentConfig` and `productionConfig`, and the others have that config settings based on the specific build we're running (prod vs development). 45 | * Now, we need to export an object that combines our base variable with either `developmentConfig` or `productionConfig`. Use Object.assign to do this. 46 | * Run both `npm run start` and `npm run production` to make sure everything is still working correctly (your app should still just render 'Hello World!'. Remember, `npm run start` just starts a local server which your files will be served from. `npm run production` should create a `/dist` folder. 47 | 48 | ## Step 3: Basic Routing 49 | Now that our webpack config is set up properly and we have our HelloWorld app, let's go ahead and tie in some basic routing with React Router. 50 | 51 | * We're going to use React Router to handle our routing, run `npm install --save react-router` in your project. 52 | * Inside of your `app` folder, create a `containers` folder. 53 | * Inside that folder create an `index.js` file, a `Main` folder, and a `Home` folder. 54 | * Inside your `Main` folder create a `MainContainer.js` file and inside your `Home` folder create a `HomeContainer.js` file. 55 | * Now, we're going to use our `index.js` file we made to make our imports easier just like we did in the video. Head over to your `index.js` file and add the proper exports (or MainContainer.js and HomeContainer.js) 56 | * Now inside of both `HomeContainer.js` and `MainContainer.js` create a React component which just renders a string or 'Home' or 'Main'. 57 | * Now let's set up some basic routing. Inside your `app` folder create a `config` folder and inside of that create a `routes.js` file. 58 | * Inside that routes file create declarative Routes which will render `MainContainer` as the main parent route (/) and `HomeContainer` as the `IndexRoute`. 59 | * Now our initial routes are set up, we need to render those when we call ReactDOM.render. Header over to your `app/index.js` file and instead of rendering `Main`, render your Routes. 60 | * If you refresh your view you should now see 'Main'. Great! But we should also be seeing 'Home' as well. The reason we're not is because we're not rendering any children components inside of Main. Remember, MainContainer is our main parent route. When we transition to different URLs, different children routes are going to becoming active. We need to render those children routes. Head over to `MainContainer.js` and instead of just rendering 'Main', we want to render 'Main' as well as any children routes that are passed to it (`this.props.children`). Make those changes now. 61 | * Reload your app and now you should see both Main and Home since our HomeContainer component is our IndexRoute (which becomes active when no other routes 'path' match the URL, in this case we have no other routes, so HomeContainer is always active). 62 | 63 | ##Step 4: Main and Home Styling 64 | If you checkout the [final solution](http://www.reactjsprogram.com/Redux-Immutable-Curriculum), you'll notice the basic home page. Our routing is set up so now let's just style the HomeContainer and MainContainer components 65 | 66 | * Create a `components` folder in your `app` folder 67 | * Create a `Home` folder in your components folder and create a `Home.js` file inside that `Home` folder 68 | * Format and Style your Home component 69 | * Add styling/format to your Home component 70 | * Create an `index.js` file inside your components folder and export Home.js for easier imports 71 | * Now in your `HomeContainer` you need to render your `Home` component rather than the text 'Home' 72 | * Right now your App should look like this 73 | 74 | 75 | 76 | ## Step 5: Navigation Skeleton 77 | Our navigation bar is going to be fundamental to our application. Even though all the pieces that it entails won't be set up, in this section you'll go ahead and set up the navigation assuming you'll tie in the rest of the pieces later. 78 | 79 | * In your components folder create a Navigation component which takes in an `isAuthed` property. 80 | * Looking at the finished example you'll notice that the navbar changes based on if you're logged in or not. On the left side if you're logged in you'll see a 'Home' link and if you're not you'll see Home and Authenticate. On the right if you're logged in you'll see a button to create a new question as well as a logout button. 81 | * Using React Router's `Link` component, create your Navigation component using the following paths as your Links, `/` for home, `/logout`, and `/auth`. Even though these routes aren't created yet, we'll do that later. 82 | * Instead of having a Button that pops open a Modal, for now just write 'Modal' 83 | * Style your navigation component appropriately. 84 | * Inside of MainContainer import your newly created Navigation component and render it initially passing in `false` for `isAuthed` (test if it works) then pass in `true` and test that your UI is changing based on the `isAuthed` prop. 85 | 86 | ## Step 6: Designing your Redux State 87 | Now that we're starting to get into authentication, we're going to start to have state in our application. Before we do that, it's a good idea to have a general idea of what the shape of your application's state is going to look like. This may not be practical in larger applications, but we're going to map out the shape of Firebase, Redux, our Reducers and Actions creators before we continue to work on any future part of the app. If done correctly, this activity will be hugely beneficial when your start to actually build your app. 88 | 89 | * Head back over to the finished app here and really play around with it. Think of all of the different pieces of state that are living and changing in the app. 90 | * Create a reduxSchema.js file in your root directory 91 | * Fill out this file to be a representation of what your full state tree will be once everything is tied up. You're not going to actually use this file, but you will refer to it when you're building your reducers and actions creators. 92 | * Remember if you're struggling, refer to the `step6` branch, just don't use it as a crutch. 93 | 94 | ## Step 7: Desigining your Firebase Schema 95 | Now we're going to do exactly what we did in Step 6, but instead we're going to do it for Firebase. Reflect on the difference we talked about between a Firebase schema and a Redux Schema. 96 | 97 | * Create a firebaseSchema.js file and add a representation of what your Firebase schema will look like. Again this will just be for reference only. Don't be afraid to screw it up the first time. It usually takes a few attempts to get it right. 98 | 99 | ## Step 8: Hooking up Redux 100 | Now that we've designed both our Firebase and our Redux state, let's go ahead and hook up Redux as well as create our first users module with Redux to keep track of all of the user's state as well as the currently authed user. 101 | 102 | * Create the following path under your `app` folder, `redux/modules` 103 | * Inside of `modules` create a `users.js` file. 104 | * Create a users reducer which for now just returns the initial state under the default switch case. 105 | * Inside of `modules` create an index.js file. This file will export all of our reducers. For now, just export the users reducer which you just created. 106 | * Now head over to your `app/index.js` in order to hook up Redux. 107 | * Instead of waiting until later to hook up Redux dev tools as well as React Router Redux, we're going to do it all now. 108 | * `npm install --save react-router-redux redux-thunk redux react-redux` 109 | * First thing we need to do is import the neccessary properties. This can get a bit hairy so I'll offer some more help through the next few steps. 110 | * ``` 111 | import { createStore, applyMiddleware, compose, combineReducers } from 'redux' 112 | import { Provider } from 'react-redux' 113 | import thunk from 'redux-thunk' 114 | import { routerReducer, syncHistoryWithStore } from 'react-router-redux' 115 | import * as reducers from 'redux/modules' 116 | ``` 117 | * Now that we have everything we need, let's go ahead and create our store. 118 | ``` 119 | const store = createStore(combineReducers({...reducers, routing: routerReducer}), compose( 120 | applyMiddleware(thunk), 121 | window.devToolsExtension ? window.devToolsExtension() : (f) => f 122 | )) 123 | ``` 124 | * Just a refesher of what's going on above, we use combineReducers in order to be able to merge React Router Redux's `routerReducer` with our own reducers. We also use applyMiddleware to make it possible for us to use Redux Thunks in our application. 125 | * Next, we need to pass `syncHistoryWithStore` but React Router's hashHistory and our store we created in order to create a new history for us. Don't forget to import hashHistory. 126 | * Now, instead of just rendering our routes, we want to render Redux's Provider passint it our store we created. 127 | * Nested inside of the Provider is going to be our Routes, however, we need to pass our new history that we created a few steps ago to our Routes to use rather than hashHistory. Head over to `routes.js` and instead of returning routes, return a getRoutes function which takes in an empty (for now) `checkAuth` function as well as our `history` variable. 128 | * Now back in the `index.js` file invoke `getRoutes` as a child of `Provider` passing it a blank function which just returns true and the `history` variable we created earlier. 129 | * Head over [HERE](https://github.com/zalmoxisus/redux-devtools-extension) and download the Redux Dev Tools. Once that's downloaded load up the app and open up the 'Redux' devtools tab. Click on "State" and you should be able to see our `users` state as well as `routing` from React Router Redux. 130 | * Phew! That was a LOT. But now we're in a great position to create a very nice React/Redux application. Again, memorizing all of the steps isn't as important as understanding them all. If you cheat and look at my code, cool. But if you cheat and look at my code while blindly copy/pasting, not cool. If you have a specific question, I'm always available in the Slack channel. 131 | 132 | ## Step 9: Auth action creators 133 | Now that Redux is set up properly, we want to create some action creators that we can eventually invoke once our user authenticates. When the user is browsing the app we're going to rely on Redux to keep track of it that user is authenticated or not. 134 | 135 | * Inside of your users.js Redux module, create an `authUser` action creator which sets an `isAuthed` property on your state to true and saves the authed users `uid` to an `authedId` property on the state. 136 | * Create an action creator called `unauthUser` which changes `isAuthed` on the users state to false and resets `authedId` back to an empty string. 137 | * Now that we have this `isAuthed` variable living in our state, head over to MainContainer.js and connect your MainContainer component so instead of passing in `true` to the component, we pass in the true value (which by default should be false.) 138 | * Right now your App should look like this 139 | 140 | 141 | 142 | ## Step 10: Prep for Firebase Auth 143 | In order to make our app able to have Facebook authentication, there are a few steps we need do take. 144 | 145 | * If you haven't already head over to firebase.google.com and make an account 146 | * Once you do that go ahead and make a new project and call it whatever you would like. 147 | * Once your project is created head to your projects dashboard and click on "Auth" side panel button. 148 | * From the "Authentication" page click on "SIGN-IN-METHOD" and then click on Facebook. 149 | * Notice we'll need an APP ID as well as an App Secret. We'll get these from Facebook. In the mean time, copy the url that is at the bottom in the gray box it should look something like this "https://YOUR-PROJECT-XXXX.firebaseapp.com/__/auth/handler". 150 | * Now head over to [Facebook's Developer's Site](https://developers.facebook.com/) and either sign up or hover over the "My Apps" section in the top right and select "Add a New App" then select Website. 151 | * Enter in a name for your app and click "Create a new Facebook App" 152 | * Enter your contact email and choose a category. 153 | * (Pass the spam filter if needed) 154 | * Now when your app is approved in the top right hand corner click on "Skip Quick Start" 155 | * Now on the left hand sidebar click on "Add Product" 156 | * Then click "Get Started" under the Facebook Auth section. 157 | * Now if you still have that URL we copied from Firebase go ahead and paste that in the section titled "Valid OAuth redirect URIs" then click "Save Changes" in the lower right. 158 | * Now head back to your "Dashboard" and then copy your App ID and App Secret and paste them in the appropriate sections under the URL we went to in the Firebase dashboard earlier. 159 | * If you followed everything correctly your app should now be able to use Facebook authentication. 160 | 161 | ## Step 11: Firebase Configuration 162 | 163 | * Inside your `config` folder create a constants.js file. 164 | * Here is where we're going to initialize firebase. But before we do that, go ahead and `npm install --save firebase` 165 | * Now back in your Firebase console select "Add Firebase to your web app" and copy both the config object as well as the firebase.initializeApp invocation. 166 | * Inside of your constants.js file import firebase and initialize your app. 167 | * export two variables from this file. `export const ref = firebase.database().ref()` and `const firebaseAuth = firebase.auth` 168 | * We'll be importing those variables later to interact with our Firebase database as well as our Firebase Auth module. 169 | 170 | ## Step 12: Authentication Methods 171 | Now that our Firebase is initialized, let's go ahead and create some helper methods to assist us with authentication. 172 | 173 | * Inside of `app` create a `helpers` folder then inside of that create a `auth.js` file. 174 | * Create and export as default a function called `auth` which uses `firebaseAuth().signInWithPopup` to auth with Firebase. 175 | * Create and export a function called `logout` which unauthenticates from Firebase. 176 | * Create an export a function called saveUser which takes in a user and saves that user to `users/AUTHED-USERS-ID/info` then returns the user. 177 | * From this point on, all of the weird Webpack/Firebase/etc setup issues are over. So by design these instructions will get less and less detailed. Remember if you get stuck, try to find the solution on your own. If you can't, check out the branch which corresponds with the step. If you're still stuck, ask for help in the Slack channel. 178 | 179 | ## Step 13: Authenticate! 180 | Now is our moment we've been prepping for. We're going to authenticate with Firebase and save the user (as well as their authed state), into Redux. 181 | 182 | * Even though we have our `authUser` action creator, that isn't doing anything more than just returning an object. Instead, create an action creator called `fetchAndHandleAuthedUser` which is in charge of authenticating the user, saving the users data into Firebase, then authenticating the user (while saving it) to Redux. To do this step you'll leverage the functions we created last step in auth.js as well as a few new action creators you'll make. 183 | * Once you've finished this action creator update your users Reducer so it correctly modifies the state based on the action creators that were invoked. 184 | * Now you should have all of your authentication logic encapsulated inside of `fetchAndHandleAuthedUser`, but now we need to actually create the UI which will eventually run that function. 185 | * Create a FacebookAuthButton stateless functional component which takes in an `isFetchin` prop as well as an `onAuth` prop. 186 | * Now create an `Authenticate` stateless functional component that is going to be the UI for the `/auth` route. It may be a good idea to use the FacebookAuthButton we created in the last step. 187 | * Once you've finished that component, you should have a good understanding of what Authenticate needs in order to properly function. So the only step now is to create an AuthenticateContainer which is hooked up to Redux and passes Authenticate everything that it needs. Make sure once you authenticate you redirect the user to the '/results' route, even though it doesn't exist yet. 188 | * Now the last step is we need to add the AuthenticateContainer we just created to our routes.js file. 189 | * Go ahead and test your code out now. Fix any errors you have and make sure you can route to the /auth route when you click the "Authenticate" button on the home screen. 190 | * If you've been following my code you should see that the FacebookAuth button show's "Loading". Why is this? If you look at user.js the initial state that was set has `isFetching` as true. `isFetching` is what the FacebookAuthButton uses to know if it should show 'Loading' or 'Login with Facebook'. 191 | * We can fix this with a check in our MainContainer. First, inside of users.js create an action creator which changes `isFetching` to false and update the user's Reducer as well. 192 | * Now, import the action creator you just made into MainContainer, don't forget to use `bindActionCreators` or just dispatch the action creator itself. 193 | * Now temporarily, call your function which changes `isFetching` to false inside of MainContainer's `componentDidMount` method. 194 | * Now when MainContainer mounts, `isFetching` should be changed to true which means that the FacebookAuthButton will display the correct text. To test this, head over to your `/auth` route. If you're uncomfortable with this last step check out the code. If you've understood everything until this point you're doing extremely well. Once you get comfortable with action creators and how they're imported into container components, managing your app state becomes a breeze. Our app is going to get more functionality, but there aren't really a whole lot of "new" things from here on out. 195 | 196 | ## Step 14: /results View 197 | Your app should be in the state where authentication works fine but once you're done authenticating you get an error that says "Warning: [react-router] Location "/results" did not match any routes". This is obviously because we haven't set up that route yet. Let's do that now. 198 | 199 | * Create a stateless functional component called Results which just (for now) renders the text "Results" 200 | * Create a container component which renders the Results component we created in the previous step. 201 | * Set up a route so when you go to `/results` you get the container component created in the previous step. 202 | * Your app should be at the point where you can authenticate, and when you do, you get redirected to the `/results` view and the navigation bar changes to be what it should look like when you're authenticated. 203 | 204 | ## Step 15: Auth Checks 205 | Currently there are two issues with our authentication set up. The first one is that if you hit refresh, our Redux state gets reset which means `isAuthed` changes to false. The second is related in that we shouldn't be able to access the `/results` route unless we're authenticated. Let's tackle problem #1 first. 206 | 207 | * Firebase has a `.onAuthStateChanged` method which we can use in order to listen to any authentication changes (or initializations) our app goes through. So what we can do is when MainContainer mounts, set up this listener so that whenever we auth, we make sure Redux knows about it. At this point we've already set up the action creator we need (`authUser`) so it's just a matter of hooking it up. 208 | * Head over to `MainContainer.js` and when it mounts, invoke `firebaseAuth().onAuthStateChanged` and pass it a callback. This callback will receive one parameter which is either null or the authed user. If it's the authed user, format it and save it to Redux. If not, then we want to removeFetchingUser (which should already be there). 209 | * Great so now we're checking and setting up an authentication listener when our app boots up, but you'll notice there's a weird lag in the UI as Firebase sets up that listener. There are a lot of ways to fix this but a simple one for now is to make it so if `isFetching` in Redux's `users` module is true, then just have `MainContainer` render null. If not, then render the UI. This works because we know that when we call `fetchingUserSuccess` after our `onAuthStateChanged` callback runs, then `isFetching` will get switched the false and the correct UI will render. 210 | * Now when you hit refresh, if you've already authenticated with Facebook before your app should save the authed user in Redux and then redirect you to the `/results` page. 211 | 212 | ## Step 16: Redirects 213 | So far so good. But now we want to set up some authentication checks with React Router. Many of our Routes we don't want users to access unless they're verified (and vice versa). To do this we're going to leverage React Router's `onEnter` property. The good news is we've done the hardest part when we set up our `checkAuth` function inside of our `index.js` page. No we just need to fill it out with whatever route logic checks we need then add it to the routes we want to check. The idea is that whatever route we attatch this function to will get called whenever a user enters this route. 214 | 215 | * By leveraging `store.getState()` go ahead and inside of `checkAuth` grab `isAuthed` and `isFetching` off the `users` state in Redux. 216 | * First thing we want to do is if `isFetching` is still true, just return. The reason we need this is because we don't want to do any route redirects if Firebase is still figuring out if the user is authed or not. 217 | * Next, grab the next path name by using `nextState.location.pathname` where `nextState` is the first argument that will be passed to `checkAuth`. 218 | * First check if the next path name is `/` (our index route) or if it's `/auth`, if it is and `isAuthed` is true, go ahead and call `replace` (second argument to checkAuth) passing is `/results`. Basically what this is saying is that if the user is already authenticated, we don't want them to be able to go to the home page or the page to authenticate but instead take them to `/results`. 219 | * Next, if the path name isn't `/` or `/auth` and the user is not authenticated, then take them to `/auth`. This protects any routes that aren't `/` and `/auth` and will redirect the user to `/auth` instead. 220 | * Now head over to `routes.js` where `getRoutes` is defined and add `checkAuth` as a prop on all of the routes you want to run `checkAuth` logic on. I have it on `/auth`, `/results`, and the IndexRoute. 221 | * Now if everything is working you should only be able to access specific routes based on your auth state. 222 | 223 | ## Step 17: Modal Module 224 | Next step is to make it so you can post a new "wouldYouRather" question. To do this, let's first start with our modal Redux module. As always, it's a good idea to start with Redux when you're implementing a feature as the most important aspect of your application is usually how your app's state will change with the new feature. 225 | 226 | * Create a `modal.js` file in `redux/modules`. If you're using an `index.js` file for easier imports make sure you add `modal.js` to that file. 227 | * Create and implement three action creators, `openModal`, `closeModal`, and `updateDecisionText` with their accompanying constants and switch statements in the reducer. 228 | * Create, impelment, and export a Redux Thunk action creator called `saveAndCloseModal`. 229 | * If you haven't already finish `modal.js` by adding some initial state to your reducer and anything else you think you'll need for this module. 230 | * If everything worked you should now see your initial `modal` state in Redux dev tools. 231 | 232 | ## Step 18: Modal UI 233 | Now that our Modal Redux module is set up, we just need to implement the UI so we can actually submit a new decision. The only tip for this one is that I use `react-modal` from NPM. We've done all of these steps a few times now so start to venture out more on your own. Check the branch if you get stuck. 234 | 235 | ## Step 19: Fetch Decisions 236 | At this point you should be able to submit decisions to Firebase and have them persisted. The problem now is we want to go ahead and fetch those decisions, set a listener so we'll be aware if they change, then show those decisions to the view in list on the '/results' view. In this section we'll build our the decisions modules which will handle all of our decisions state. We'll also include a Thunk function which will make the request to Firebase and set up the listener. 237 | 238 | Again, I'll tell you the action creators (and Thunks) that I created, and you can choose to follow my lead or create your own path. The biggest thing to remember is that at this point, really understand the shape of your decisions state and understand how that state can be modified.If you understand those two things, creating the action creators which modify the state will be easier. 239 | 240 | Implement the following action creators (and Thunks) with their accompanying constants and items in the decisions reducer. 241 | 242 | * settingDecisionsListener 243 | * settingDecisionsListenerError 244 | * settingDecisionsListenerSuccess 245 | * setAndHandleDecisionsListener (Thunk for ^ three action creators) 246 | * addDecision 247 | * fetchAndHandleSingleDecision (Thunk for addDecision) 248 | 249 | One thing you may have noticed is that it will be a good idea to know if you're already listening to certain endpoints or not. You can keep this state anywhere you like, but to make future changes easier, I like to keep listeners in their own Redux module. To follow my lead, create a `listeners` modules which has an `addListener` function, then when you add the `decisions` listener in `decisions.js`, also add that to `listeners` so we know what we're listening to. 250 | 251 | Now your state tree should look like this ↓. Notice we haven't actually fetched any decisions yet (or set any listeners). That's the next step. 252 | 253 | 254 | 255 | ## Step 20: Results UI 256 | Now that we have the ability to fetch decisions (and update our Redux state), now let's actually do that once the user lands on `/results` and we haven't fetched those already. 257 | 258 | * In your Results component update the component to take in in the following props, and neatly shows the Results to the view. `isFetching`, `decisions`, `error`. If you're following along with my code you'll notice that I installed `react-loader` to show a nice spinner when the data is loading. Feel free to do that as well. 259 | * Now that the component is built you should be thinking that we need some way to get data to this component - which means we should create a container component which is connected to Redux. Do that now and pass Results the data it needs. 260 | * If you're feeling fancy make it so you only fetch decisions if they either haven't been fetched already or they're "stale" meaning it's beena while since they've been fetched. 261 | * Also if you're feeling extra fancy go ahead and make it so that when you fetch a new decision it takes the author of that decision and caches that user under Redux. 262 | * Now your app and state tree should look similar to this, notice we've now set a listener as well. 263 | 264 | 265 | 266 | ## Step 21: Fetch Users Made Decisions 267 | You'll notice that on the final version on the `/results` view the UI shows a green line on the left with a checkmark on the right of the Result if the user has taken that decision or a red line (and an empty cirlce) if they haven't. In order to implement that we need to go ahead and fetch every decision that the user has made when the app loads. 268 | 269 | * Head back to your Results component and add a new prop called `decisionsMade`. This prop will be all of the decisions the user has made. Now, in order to know if they've made a particular decision or not, we can look if `props.decisionsMade[decisionId]` is truthy or not. Update the UI to show the checkmark/circle as well as the red or green line. 270 | * Because we're now assuming Results is going to receive `decisionsMade`, we actually need to pass those in. Head over to your ResultsContainer and get `decisionsMade` which we'll put on each users object in the `users` module. So again, each user will eventually have a `decisionsMade` property. Make sure you pass `decisionsMade` down to Results. 271 | * At this point all we need to do is update the authed users `decisionsMade` property. We'll do this right when the MainComponent mounts. 272 | * Head over to your `users` module and create a new Thunk function called `fetchAndAddUsersMadeDecisions` and fill it out to make the appropriate request. 273 | * Once that's done go ahead and add it to `fetchAndHandleAuthedUser` so that when a user authenticated, we'll fetch their made decisions. 274 | * Now, head over to MainContainer and also call `fetchAndHandleAuthedUser` when the component mounts so we'll have their made decisions on a refresh. 275 | * At this point your app should look like this, 276 | 277 | 278 | 279 | ## Step 22: Decision View 280 | We're so close! The second to last thing we need to do is make the Decisions clickable. Obviously the whole point of Would You Rather is that you can choose between two decisions. I'm going to leave this one entirely up to you. Here's the end result for the (what I'm calling) `/decision` view. If you get stuck, there is a branch. 281 | 282 | 283 | 284 | ## Step 23: Logout 285 | Last but not least, we need to hook up our `Logout` button to actually unauthenticate us. At this point you should have the hang of this. I'm going to leave this one up to you as well. Check out the branch if you get stuck. 286 | 287 | ## Congrats! 288 | If you got through this, great job. If you're reading this and you've made it this far, be proud. This was a pretty advanced project. Take a look at my code and compare to what we did differently. 289 | 290 | Suggestion or just want to say thanks? You can find me on Twitter at [@tylermcginnis33](http://twitter.com/tylermcginnis33) 291 | --------------------------------------------------------------------------------