How to set up React.js with a Ruby on Rails Project Part 2 – Redux
Tags: react, rails
Published: 2021-05-31 00:22:07

Previously, we set up our Ruby on Rails app to use React.

Now, we need to do a few more things to make sure our app is really functional. We still have to

Set up our model in rails
Have our frontend connect to our backend
Integrate Redux so React works better.
Let’s get started.

Setting up our Post model and controller in rails
This is going to be pretty common Rails code. First, create the model in “app/models/Post.rb”.
```ruby
class Post < ApplicationRecord
end
```
Next, we’re going to set up our serializer. This basically turns our model into JSON that we can send to our frontend. Create “app/serializers/post_serializer.rb” and put the following:
```ruby
class PostSerializer
  include FastJsonapi::ObjectSerializer
  attributes :title, :body
end
```
The attributes are attributes on our model that we’re going to expose as JSON. This reminds me, we need to add the FastJsonapi gem. Go to your gemfile and add:
```ruby
gem 'fast_jsonapi'
```
Run bundle install.

Now we need to set up our model in the database. Run the following:
```
rails g migration create_posts
```
And in the migration file:
```ruby
class CreatePosts < ActiveRecord::Migration[6.1]
  def change
    create_table :posts do |t|
      t.string :title
      t.string :body
      t.timestamps
    end
  end
end
```
Then run the migration:
```
rails db:migrate
```
Now, on to the controller. Set up your controller code in 
“app/controller/api/v1/posts_controller.rb”. This is common to your usual Rails CRUD controller code, but we’re going to be rendering JSON instead of rendering views or redirecting.

Here’s the code for the controller:
```ruby
module Api
  module V1
    class PostsController < ApplicationController
 
      def index
        posts = Post.all
        render json: PostSerializer.new(posts).serialized_json
      end
    
 
    def show
      post = Post.find(params[:id])
      render json: PostSerializer.new(post).serialized_json
    end
 
    def create 
      post = Post.new(post_params)
 
      if post.save
        render json: PostSerializer.new(post).serialized_json
      else
        render json: {error: post.errors.messsages}
      end
    end
 
    def update
      post = Post.find(params[:id])
      if post.update(post_params)
        render json: PostSerializer.new(post).serialized_json
      else
        render json: { error: post.errors.messages }
      end
    end
 
    def destroy
      post = Post.find(params[:id])
 
      if post.destroy
        head :no_content
      else
        render json: { error: post.errors.messages }
      end
    end
 
    private 
     
    def post_params
      params.require(:post).permit(:title, :body)
    end
  end
end
```
Now’s a good time to test all of these actions with something like Postman. Go ahead and test out your API before moving on to the front end.

We’re going to write a lot of code in the upcoming sections to connect to our backend. It’s important that your backend is working properly.

Open Rails console and add a few records so we can see our data. Here’s what I did.
```
Post.create(title: "one", body:"something")
 
Post.create(title: "two", body:"something else")
```
Now you should be getting some records back when you hit your index endpoint for your posts.

Adding Redux to Ruby on Rails
Create a folder and folder “app/javascript/src/api/api.js” This is what we’re going to use to talk to our back end. Here’s what our file is going to look like:
```javascript
import axios from 'axios'
 
const ROOT_PATH = '/api/v1'
const POSTS_PATH = `${ROOT_PATH}/posts`
 
export const getPosts = () => {
  return axios.get(POSTS_PATH)
}
```
We’re importing axios so we can make http requests to our backend. Then, we’re setting up some constants for our routes. Finally, we’re creating a function that makes a get request to our posts route.

Add axios using yarn:
```
yarn add axios
```
Now’s the time to add redux. I’m going to try to explain the best I can, but I assume you have some knowledge of how redux works before you start trying to add redux to Rails.

Create an actions folder in “app/javascript/src/actions” and create a posts.js file in that folder. In that file put this:
```javascript
import * as api from '../api/api'
 
export const getPosts = () => async (dispatch) => {
  const { data } = await api.getPosts()
}
```
We’re importing our api so we can use the methods there. We are also creating a function that just calls our api and returns the data. The “dispatch” section might look strange, but we’re doing that so redux-thunk works.

We’re going to come back to this function later, but this is enough to test it out.

EDIT: We’re not going to test out this function before we add to it. Sit tight and we’ll come back to this function.

Go to your index.jsx file at “app/javascript/packs/index.jsx” and make the file look like this
```javascript
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import App from '../src/components/App'
 
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import reducers from '../src/reducers'
 
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
 
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)))
 
document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Provider store={store}>
      <Router>
        <Route path="/" component={App}/>
      </Router>
    </Provider>,
    document.body.appendChild(document.createElement('div')),
  )
})
```
So what’s going on with all this code? Well, first we are importing everything we need from react-redux and redux-thunk here:
```javascript
import { Provider } from 'react-redux'
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import reducers from '../src/reducers'
```
We’re also importing a reducers file that we will create in a second.

Then, this line is setting up Redux so we can work with the Chrome redux dev tools. If you don’t have this set up, the Chrome extension won’t work:
```javascript
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
```
Next, we’re creating our store that lets us work with state. We’re also telling our app we want to use redux-thunk.
```javascript
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)))
```
Finally, we’re wrapping our app in the Provider tag. This has to do with accessing the store or state in our app.
```javascript
<Provider store={store}>
      <Router>
        <Route path="/" component={App}/>
      </Router>
    
</Provider>
```
That’s it for that file. Now we need to create that reducer we just imported. But first, make sure you add the packages using yarn.

yarn add react-redux redux-thunk
Create a reducers folder in “app/javascript/src” and create two files. Create a “posts.js” file and an “index.js” file. Let’s open the “posts.js” file first.

This file is going to keep track of the posts on your apps state. This file’s job is to update all the posts when certain actions are dispatched from your actions files.

This is what the file looks like:
```javascript
import { GET_POSTS } from '../types/index'
 
export default (posts = [], action ) => {
  switch (action.type) {
    case GET_POSTS:
      return action.payload
    default:
      return posts
  }
}
```
Let’s break down what’s happening here. First, we’re importing a GET_POSTS type. We’ll create that in a second.

Next, we’re exporting a function and setting the initial state of posts to an empty array. Then we have the switch statement.
```javascript
switch (action.type) {
   case GET_POSTS:
     return action.payload
   default:
     return posts
}
```
What this is doing is saying “Whenever I see the GET_POSTS action, I’m going to take the payload from that action and set my posts equal to that payload. For all other actions (default), I’m just going to return the posts and do nothing.

Later, when we use our actions, we will send types like GET_POSTS that tell this reducer to use the data we pass it. If any other action types are passed to it, it won’t do anything.

Before we forget, let’s create that types folder and file in “app/javascript/src/types/index.js”. This will help us later on if we mistype any of our types.
```javascript
export const GET_POSTS = "GET_POSTS"
```
Now we go to our “app/javascript/src/reducers.index.js” file. This file just combines all your reducers.
```javascript
import { combineReducers } from 'redux'
import posts from './posts'
 
export default combineReducers({
  posts: posts
})
```
What this does is tells redux that we want a key on our state called “posts” and set that equal to the posts in our state.

Now that we have our reducers set up, we can go back to our action creator file and dispatch actions. Basically, this lets our actions talk to our reducers. Back in “apps/javascript/src/actions/posts.js” make your file look like this.
```javascript
import * as api from '../api/api'
import { GET_POSTS } from '../types/index'
 
export const getPosts = () => async (dispatch) => {
  const { data } = await api.getPosts()
  dispatch({
    type: GET_POSTS,
    payload: data.data
  })
}
```
Here’s what we’re doing here. We are using our api to get data from our rails backend. Then, with “dispatch” we are telling all of our reducers “hey, if you are subscribed to the GET_POSTS action, I have some data for you.”

We only have one reducer right now, but all of the reducers would look at this action and the only ones that are subscribed to GET_POSTS will actually do anything. In our case, our posts reducer is looking out for this action type. It’s going to see the data in the payload and then set that in our posts key on our state.

Now let’s actually use all of this code we set up!

Back in our Posts component at “app/javascript/src/components/Posts/Posts” write the following.
```javascript
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getPosts } from '../../actions/posts'
 
 
const Posts = () => {
  const dispatch = useDispatch()
  const posts = useSelector(state => state.posts)
  useEffect(() => {
      dispatch(getPosts())
  }, [])
 
  if (posts.length === 0) { return <div>loading...</div>}
 
  console.log(posts)
 
  return (
    <div>
      <h1>Posts</h1>
      <p>This is our posts page.</p>
    </div>
  )
}
 
export default Posts
```
What’s going on here?

We are getting some functions from react-redux and getting our action creator function.
```javascript
import { useDispatch, useSelector } from 'react-redux'
import { getPosts } from '../../actions/posts'
```
We’re setting up our dispatch function here.
```javascript
const dispatch = useDispatch()
```
Next, we’re telling react to create a variable called posts and set it equal to the posts in the redux store.
```javascript
const posts = useSelector(state => state.posts)
```
Now, we’re saying “when this component loads, go get all the posts using my action creator.
```javascript
useEffect(() => {
     dispatch(getPosts())
}, [])
```
If our page loads before our data comes back, we’re going to have a loading signal. Otherwise, if you start trying to access your data before if comes back from the server, your app will crash.
```javascript
if (posts.length === 0) { return <div>loading...</div>}
```
Then, we’re just console.loging our posts. You should be able to see them in the Chrome redux dev tools, too.
```javascript
console.log(posts)
```
Awesome, now our react app can read data from redux store, data that is from our backend. We’re at the home stretch!

We don’t just want to console.log our data though. So, let’s fix that. In our return function, we going to put another function like so.
```javascript
return (
   <div>
     <h1>Posts</h1>
     {renderPosts()}
   </div>
}
```
Let’s make a function in this same file called renderPosts. Here, we’re going to loop through each of our posts and render a component.
```javascript
const renderPosts = () => {
    return posts.map(post => {
      return <PostListItem key={post.id} post={post} />
    })
}
```
We’re passing the current post to each item. We’re also giving it a key, otherwise react will yell at us and it will hurt performance.

Import the list item at the top.

import PostListItem from './PostListItem'
Then create it at “app/javascript/src/components/Post/PostListItem”.
```javascript
import React from 'react'
 
 
const PostListItem = ({post}) => {
  return(
    <div>
      <h2>{post.attributes.title}</h2>
      <p>{post.attributes.body}</p>
    </div>
  )
}
 
export default PostListItem
```
You should now see all of your posts.

In the next article, I'll cover CRUD operations in Rails and React. Stay tuned!

If you want to learn more about web development, make sure to [follow me on Twitter](https://twitter.com/heyjoshlee).