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.
Part One: Building a website with React and BulmaPart Two: Building a Blog with React and ContentfulPart Three: Import your Medium Feed into ReactPart Four: Adding a Redux to a React BlogPart Five: Replacing Redux Thunks with Redux SagasPart Six: (In Progress) Writing Unit Test for a React Blog with Redux Sagas
Here it is, the moment you have all be waiting for. — Redux!!!
npm install react-redux
Note: You might also have install redux as a dev dependancy. Rumor is yarn solves this problem but you can also manually install it to the depentancies.
npm install redux --save-dev
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.
Remember we are using the fractal file structure key value concepts. In the store we are going to follow this pattern with key = public actions (thunks) and value = private (types reducers and actions), which means our app will never talk to the files in our folder. They will only talk to a thunk (the key, the public actions) which will talk to the files in its folder (the value, the private classes). In most cases the our thunk will get data from somewhere and at it to our store.
Confused yet? I though so.
Here is an example of a User store file structure. Our components will only ever talk to User.js, which in turn will talk the files in the user folder. This will make more sense as time goes on.
src- app
... app code ...- store
In the src
folder add a folder called store.
Then add three files: index.js, initialState.js, and 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 index.js
in the src folder and and the store and wrap the app.
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((<Provider store={store}><Router><App /></Router></Provider>), document.getElementById('root'))registerServiceWorker()
ConfigureStore is our Store’s index.js, we are not currently passing any initial state during set up.
Also, if you need to app to retrieve any initial data, would call it just after we set the store to configureStore.
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 Blog.js and folder called blog. In the blog folder create three fills: actions.js, reducer.js, and 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 store/blog/types.js
add
/*** 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 store/initialState.js
add
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 store/blog/reducer.js
add
/*** 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.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 store/blog/actions.js
add
/*** Blog Actions*/import * as types from './types'
export function loadBlogSuccess(post) {return { type: types.LOAD_BLOG_SUCCESS, post}}
Now, we need to include this in our rootReduce. The rootReducer combines all the store reducers into one giant mega reducer that creates a global state accessible every where in the application.
In store/rootReducer.js
add
import { combineReducers } from 'redux'import blog from './blog/reducer'
export const rootReducer = combineReducers({blog})
Notice 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. Export default is what allows us to name things anything we want when we import the default function. We could have imported it as blogReducer a then the combineReducers functions done blog: blogReducer, but I personally think this looks better. #CodeIsArt
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 and I can prove it.
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 store/index.js
we are going to remove the initialState because we will be setting those in the reducers of each store, we will wrap our middleware with composeWithDevTools function.
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,composeWithDevTools(applyMiddleware(thunk,reduxImmutableStateInvariant())))}
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 createStore(…) function.
Now, you should see your Redux state and history. It defaults to Diff so click the State tab and you should see your default object
Pretty cool, right!
Remember that in Blog.js file is the public actions (the thunk) of our store. This is where we will put our thunks and retrieve our data when the site loads.
We can reuse some of our logic from from app/Blog.js
because it will no longer need to make the call to Contentful any more. That will happen in our thunk.
Let’s set up our thunk. Open store/Blog.js
and add
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 root index.js
file
...
_// 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 loadBlog() function.
When you refresh the app and check that Redux dev tools you should now see some data in blog > posts
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 { connect } from 'react-redux'import * as contentful from 'contentful'import BlogItem from './blog/BlogItem'import PageHeader from './components/PageHeader'import PageContent from './components/PageContent'
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>{ this.props.blog.posts.map(({fields}, i) =><BlogItem key={i} {...fields} />)}</PageContent></div>)}}
function mapStateToProps(state, ownProps) {return {blog: state.blog}}
export default connect(mapStateToProps)(Blog)
Blammo! Redux!
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: {loading: false,posts: []}}
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(isLoading = true) {return { type: types.BLOG_LOADING, isLoading}}
export function loadBlogSuccess(posts) {return { type: types.LOAD_BLOG_SUCCESS, posts}}
We are setting isloading to a default of true so we don’t we won’t normally have to pass any data in when dispatch blogLoading. BUT, by making it so we can 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.
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())** return client.getEntries().then(({items}) => {dispatch(actions.loadBlogSuccess(items))}).catch(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.
Back in app/Blog.js
lets set up some placeholder text to make sure our logic works.
<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>{ this.props.blog.loading? <div>Loading</div>: <PageContent>{ this.props.blog.posts.map(({fields}, i) =><BlogItem key={i} {...fields} />)}</PageContent>}</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.
Or, you can the history jump feature in the Redux Dev Tools. I’m gonna let you discover that little gem on your own ;)
Now, in app/components
create Loader.js which will be a styled component for our spinner.
Now back in our app/Blog.js
let’s add the spinner. Replace <div>Loading</div>
with
<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 Bulma we can just give it the class has-text-$color (like has-text-primary).
Back in the clean up phase of Building a Blog with React and Contentful I forgot to change the text for Read More and Back to Blog in the Blog Items Nav.
It’s an easy fix with a simple ternary operator in the <Link>
using the to
prop.
In app/blog/shared/BlogNav.js
can the Link to this
<Link className="level-item button is-small is-link is-outlined" to={to}>{ to === '/blog' ? 'Back to Blog' : 'Read More'}</Link>
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.
Step 1: Create typesStep 2: Add state to initial stateStep 3: Set up reducersStep 4: Set up actionStep 5: Add reducers to rootReducerStep 6: Setup our thunk (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/[email protected]`)
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)))}
Step 7: Add state to component
Test it and move on with life. You are now a Redux master!