The React Blog Series: Part Four This article is part four in a weekly series on building a blog with React and is an extension of the code created in previous parts. The React Blog Series Adding a Redux to a React Blog ( ) Writing Unit Test for a React Blog with Redux Sagas Building a website with React and Bulma Part One: Building a Blog with React and Contentful Part Two: Import your Medium Feed into React Part Three: Part Four: Replacing Redux Thunks with Redux Sagas Part Five: Part Six: In Progress Here it is, the moment you have all be waiting for. — Redux!!! npm install react-redux You might also have install redux as a dev dependancy. Rumor is solves this problem but you can also manually install it to the depentancies. Note: yarn npm install redux --save-dev Adding a Store So this might be an unpopular opinion because I know everyone has a hard on for ReDucks, but thats not the right way to do it. When you put your store files in with your components, you’re combining your Presentation Layer with your application Business Layer and State. If something new comes along that replaces Redux, you won’t have to removed files from all our folders, just remove the whole store. But we will separate our file type in store. The Fractal Concept Remember we are using the fractal file structure key value concepts. In the store we are going to follow this pattern with and which means our app will never talk to the files in our folder. They will (the key, the public actions) which will talk to the files in its folder (the value, the private classes). In most cases the our will get data from somewhere and at it to our store. key = public actions (thunks) value = private (types reducers and actions), only talk to a thunk thunk Confused yet? I though so. Here is an example of a User store file structure. Our components will only ever talk to , which in turn will talk the files in the user folder. This will make more sense as time goes on. User.js src- app ... app code ...- store User.js user actions reducer types Set Up In the folder add a folder called . src store Then add three files: , , and index.js initialState.js rootReducer.js Setup index.js import { createStore, applyMiddleware } from 'redux'import { rootReducer } from './rootReducer'import reduxImmutableStateInvariant from 'redux-immutable-state-invariant'import thunk from 'redux-thunk' export function configureStore(initialState) {return createStore(rootReducer,initialState,applyMiddleware(thunk,reduxImmutableStateInvariant()))} Setup initialState.js export default { } Setup rootReducer.js import { combineReducers } from 'redux' export const rootReducer = combineReducers({ }) And to hook into the app open the apps in the and and the store and wrap the app. index.js src folder import React from 'react';import ReactDOM from 'react-dom'import registerServiceWorker from './registerServiceWorker'import { BrowserRouter as Router } from 'react-router-dom'import App from './App' _// Redux Store_import { Provider } from 'react-redux'import { configureStore } from './src/store' import './index.css' const store = configureStore() ReactDOM.render(( <Router><App /></Router> ), document.getElementById('root'))registerServiceWorker() <Provider store={store}> </Provider> ConfigureStore is our Store’s , we are not currently passing any initial state during set up. index.js Also, if you need to app to retrieve any initial data, would call it just after we set the store to configureStore. Redux Blog Store Now that the easy part is over, let’s roll up our selves and make some magic. In our store folder create a filed called and folder called . In the blog folder create three fills: , , and Blog.js blog actions.js reducer.js types.js Let’s start with our types. These are the constants our actions use call dispatches. First, we just need one get load our blog data into our store. In add store/blog/types.js /*** Blog Types*/ export const LOAD_BLOG_SUCCESS = 'LOAD_BLOG_SUCCESS' Before we go to far let’s add a blog object with an empty array for posts as our initial state for blog. This will be passed when setting up our reducer. It also allows the app to load without data yet with out throwing null exceptions. In add store/initialState.js export default {blog: {posts: []}} Next, let’s set up our reducer. This is what updates our state which is where our data gets stored. The actions will call these and via a switch statement it will look for the type that was called by the action. In add store/blog/reducer.js import initialState from '../../store/initialState'import * as types from './types' /*** Blog Reducer*/ export default function blogReducer(state = initialState.blog, action) {switch (action.type) {case types.LOAD_BLOG_POSTS_SUCCESS:return {...state,posts: action.posts}default:return state}} Next, we need to set up an action to dispatch the type and set its data as the payload (posts). In add store/blog/actions.js import * as types from './types' /*** Blog Actions*/ export function loadBlogSuccess(post) {return { type: types.LOAD_BLOG_SUCCESS, post}} Now, we need to include this in our rootReduce. The into one giant mega reducer that creates a global state accessible every where in the application. rootReducer combines all the store reducers In add store/rootReducer.js import { combineReducers } from 'redux' import blog from './blog/reducer' export const rootReducer = combineReducers({ }) blog we didn’t import it as blogReducer, because what ever you call it in the combineReduces is how you access it in the rest of the app. is what allows us to name things anything we want when we import the default function. We could have imported it as a then the combineReducers functions done but I personally think this looks better. #CodeIsArt Notice Export default blogReducer blog: blogReducer, Testing that Redux is Working Right now our code is working. It doesn’t do anything yet because its not actually doing anything we can see, but it is working I can prove it. and First you need the Redux Dev Tools extension for chrome. https://github.com/zalmoxisus/redux-devtools-extension npm install --save-dev redux-devtools-extension Then we need to hook it up. In we are going to remove the because we will be setting those in the reducers of each store, we will wrap our middleware with function. store/index.js initialState composeWithDevTools import { createStore, applyMiddleware } from 'redux'import { rootReducer } from './rootReducer'import reduxImmutableStateInvariant from 'redux-immutable-state-invariant'import thunk from 'redux-thunk' import { composeWithDevTools } from 'redux-devtools-extension'; export function configureStore() {return createStore(rootReducer, applyMiddleware(thunk,reduxImmutableStateInvariant()) )} composeWithDevTools( ) Then install the extension in chrome and open the chrome inspector (I usually right click on the page and click Inspector). In the top nav you should now have Redux option. Click it and refresh your app. The Redux tab sometimes has to be open when the app loads for it to work, this is because the connection is established on site load in the function. createStore(…) Now, you should see your Redux state and history. It defaults to so click the tab and you should see your default object Diff State Pretty cool, right! Putting Blog Data in the Blog Store Remember that in file is the (the thunk) of our store. This is where we will put our thunks and retrieve our data when the site loads. Blog.js public actions We can reuse some of our logic from from because it will no longer need to make the call to Contentful any more. That will happen in our thunk. app/Blog.js Let’s set up our thunk. Open and add store/Blog.js import * as contentful from 'contentful'import * as actions from './blog/actions' const client = contentful.createClient({space: 'qu10m4oq2u62',accessToken: 'f4a9f68de290d53552b107eb503f3a073bc4c632f5bdd50efacc61498a0c592a'}) const error = err => console.log(err) export function loadBlog() {return dispatch =>client.getEntries().then(({items}) => {dispatch(actions.loadBlogSuccess(items))}).catch(error)} The thunk automatically passes dispatch into our functions which is what the first parameter there is doing. This code will make the call out to Contentful and the send that data to the loadBlogSuccess actions which will store the data on our blog state under posts. But it wont happen automatically, we will need to call this function when the site loads. We will do this by adding a line to our file root index.js ... _// Redux Store_import { Provider } from 'react-redux'import { configureStore } from './store' import { loadBlog } from './store/Blog' import './index.css' const store = configureStore() store.dispatch(loadBlog()) ReactDOM.render(( ... We are telling the store that once its created, to dispatch the function. loadBlog() When you refresh the app and check that Redux dev tools you should now see some data in blog > posts Using the Store in a Component This is a pretty easy step. Redux give us a function that we can use called mapStateToProps that, as you can probably guess, maps our redux state… to our page props. Then we “connect” the mapStateToProps and Blog.js to Redux. Here‘s the magic, open app/Blog.js and remove our old code to get posts and add/update 8 lines of code. import React from 'react' import * as contentful from 'contentful'import BlogItem from './blog/BlogItem'import PageHeader from './components/PageHeader'import PageContent from './components/PageContent' import { connect } from 'react-redux' class Blog extends React.Component {render() {return (<div><PageHeader color="is-info" title="Code Blog">Your standard <strong>JavaScript</strong> programming blog, albeit, probably not very good, but I will at least try to keep it entertaining. This blog is a chronological mix of random posts on Angular, React, Functional Programming, and my <strong>project walkthroughs</strong>.</PageHeader><PageContent>{ (({fields}, i) =><BlogItem key={i} {...fields} />)}</PageContent></div>)}} this.props.blog.posts.map function mapStateToProps(state, ownProps) {return {blog: state.blog}} export default connect(mapStateToProps)(Blog) Blammo! Redux! Using Blog State Correctly I will be the first to admit that when I first started using Redux I was using it as a data store, and it’s so much more then that. It is your apps state, where its going, where its been, what its doing, what it knows… Using it only to store data is an insult to its existence. For example, our app runs fine right now because Contentful is awesome and fast and we only have very small about of data, but what if we had a lot of data or a slow connect. we don’t want to load a blank play for the user. Let’s created a blog state for loading, that is we can display a loading icon while its getting data from Contentful. store/initialState.js export default {blog: { posts: []}} loading: false, store/blog/types.js _/*** Blog Types*/_export const BLOG_LOADING = 'BLOG_LOADING'export const LOAD_BLOG_SUCCESS = 'LOAD_BLOG_SUCCESS' store/blog/reducer.js _/*** Blog Reducer*/_import initialState from '../../store/initialState'import * as types from './types' export default function blogReducer(state = initialState.blog, action) {switch (action.type) {case types.BLOG_LOADING:return {...state,loading: action.isLoading}case types.LOAD_BLOG_SUCCESS:return {...state,posts: action.posts,loading: false}default:return state}} Yes. State only contains posts and loading, yet we still spread it out. If you where keeping score, you might have noticed it earlier. It’s a good practice, because we are setting a new state object every time. Buy always spreading state first, we get all the previous values and don’t forget any, then the new fields simple overwrite the previous values. Like **Object.assign({}, previouseState, { …newStateValues})** store/blog/actions.js _/*** Blog Actions*/_import * as types from './types' export function blogLoading( ) {return { type: types.BLOG_LOADING, isLoading}} isLoading = true export function loadBlogSuccess(posts) {return { type: types.LOAD_BLOG_SUCCESS, posts}} We are setting to a default of true so we don’t we won’t normally have to pass any data in when dispatch blogLoading. , by making it so we pass in something like false, means we don’t have to create an action to set loading to false, like say if there was an error with loading. isloading BUT can store/Blog.js import * as contentful from 'contentful'import * as actions from './blog/actions' const client = contentful.createClient({space: 'qu10m4oq2u62',accessToken: 'f4a9f68de290d53552b107eb503f3a073bc4c632f5bdd50efacc61498a0c592a'}) export function loadBlog() {return dispatch => **dispatch(actions.blogLoading())** client.getEntries().then(({items}) => {dispatch(actions.loadBlogSuccess(items))}).catch( ) } { return error => {console.log(error)dispatch(actions.blogLoading(false))} } So we need to do some rearranging, we will clean this up later, but we need to able to call dispatch to call our actions for . blogLoading Now, you should see some additional actions being fired off in your Redux Dev tools. Update the View to Add a Loading Icon Back in lets set up some placeholder text to make sure our logic works. app/Blog.js <div><PageHeader color="is-info" title="Code Blog">Your standard <strong>JavaScript</strong> programming blog, albeit, probably not very good, but I will at least try to keep it entertaining. This blog is a chronological mix of random posts on Angular, React, Functional Programming, and my <strong>project walkthroughs</strong>.</PageHeader> <PageContent>{ this.props.blog.posts.map(({fields}, i) =><BlogItem key={i} {...fields} />)}</PageContent> </div> { this.props.blog.loading? <div>Loading</div>: } We are wrapping the previous code with ternary operator to handle the if statement. This means, if loading is true, we will show the loading div, else we show the content dev. If you’re like me, Contentful is just too fast and you can’t see it. We can wrap the dispatch on the getEntries success promise with a timeout. setTimeout(() => dispatch(actions.loadBlogSuccess(items)), 5000) Now you should be able to see the loading div. , you can the history jump feature in the Redux Dev Tools. I’m gonna let you discover that little gem on your own ;) Or Now, in create which will be a styled component for our spinner. app/components Loader.js Now back in our let’s add the spinner. Replace with app/Blog.js <div>Loading</div> <Loader className="has-text-primary"></Loader> and don’t forget to import the loader import { Loader } from ‘./components/Loader’ The Loader defaults to white, so you need to either hard code the color or using we can just give it the class (like has-text-primary) Bulma has-text-$color . I found a bug :( Back in the clean up phase of I forgot to change the text for Read More and Back to Blog in the Blog Items Nav. Building a Blog with React and Contentful It’s an easy fix with a simple ternary operator in the using the prop. <Link> to In can the Link to this app/blog/shared/BlogNav.js <Link className="level-item button is-small is-link is-outlined" to={to}> </Link> { to === '/blog' ? 'Back to Blog' : 'Read More'} Add the Store to the Medium Page Now we can do the exact same things for out Medium page. I’m not going to show you each and every step because that’s not how we learn, but I will list them out for you. Create types Add state to initial state Set up reducers Set up action Add reducers to rootReducer : Setup our thunk Step 1: Step 2: Step 3: Step 4: Step 5: Step 6 (hint below) import * as actions from './medium/actions'import axios from 'axios' const fetchPosts = () => axios.get(`https://cors.now.sh/https://us-central1-aaronklaser-1.cloudfunctions.net/medium?username=@aaron.klaser`) const setPosts = ({data}) => Object.values(data.payload.references.Post).map(({ id, title, createdAt, virtuals, uniqueSlug }) => Object.assign({},{title,createdAt,subtitle: virtuals.subtitle,image: virtuals.previewImage.imageId ? `https://cdn-images-1.medium.com/max/800/${virtuals.previewImage.imageId}` : null,url: `https://medium.com/@aaron.klaser/${uniqueSlug}`})) export const loadMedium = () => async dispatch => {dispatch(actions.mediumLoading())const data = await fetchPosts()return dispatch(actions.loadMediumSuccess(setPosts(data)))} Add state to component Step 7: Test it and move on with life. You are now a Redux master! Let’s Review Add Redux and friends to your project. Set up the Store using fractal file sturcture Set up the store’s types reducer and actions for loading data Set up the store’s thunk, which is our public actions class Add the store to the page component Added a loading state and loading animation Fixed a previews bug Added the store to the Medium page Next — Replacing Redux Thunks with Redux Sagas