How to Develop a Signup system with React, Redux, and Rails

I have always been fascinated with one’s ability to create or recreate. Software development for gives me a way to translate my imaginations into useful tangible things. The feeling I get after going through challenges to figure out how to put my ideas together, is incomparable. Full-stack development is the combination of both front-end technologies and back-end technologies to create a fully functional application. In this tutorial, we are going to combine front-end technology called React and Redux with a back-end technology called Rails to create a simple sign-up system.

In summary, React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It lets you compose complex UIs from small and isolated pieces of code called “components”. Each component may have data specific to it called state or data from other components as props or it may not have data at all. Redux is a predictable state container for JavaScript apps. On the other hand, Rails is just a back-end technology for persisting data.

Let us build a simple signup system with the three technologies mentioned above.

In this tutorial, we will create a react API that communicates with rails API to perform sign up. Our Sign up UI will be a form with the following fields (username, email, password, and image).

Steps for setting up Front-end

Let’s start by creating a sign-up page.

Create action creators and reducers to update the redux state

Send user data to the back-end for persistence with Axios.

Steps for setting up Back-end

Create your rails API 

Create a DB for API

Create a user table and model

Migrate table

Create a users_controller to handle data persistence

Front-end implementation

Create react app

make sure you have node install and create a react project with the command below

npx create-react-app signup

This command will generate your basic react-app that you can start building your application on.

Create the UI for sign up

create a file to house your sign-up component .ie ./src/SignUp.js

create a react class component inside the

SignUp.js

The sign-up component at this moment is going to be a basic react application that renders a form and has event handlers to track the changes in the state of each input field. 

import React, { Component } from 'react';

class Signup extends Component {
        constructor(props) {
                super(props);
                this.state = {
                        username: '',
                        email: '',
                        password: '',
                        picture: '',
                };
        }

        handleUsernameChange = (e) => {
                this.setState({
                        username: e.target.value,
                });
        }

        handleEmailChange = (e) => {
                this.setState({
                        email: e.target.value,
                });
        }

        handlePasswordChange = (e) => {
                this.setState({
                        password: e.target.value,
                });
        }

        handleAvatarChange = (e) => {
                this.setState({
                        picture: e.target.files[0],
                });
        }

render() {
  return (
          <form onSubmit={this.onSubmitHandler}>
                  <h1>{message}</h1>
                                <input
          onChange={this.handleUsernameChange}
          type="text"
          name="username"
          placeholder="Username"
          required
          />

        <input
          onChange={this.handleEmailChange}
          type="email"
          name="email"
          placeholder="Email"
          required
        />
          
        <input
          onChange={this.handlePasswordChange}
          type="password"
          name="password"
          placeholder="Password"
          required
                        />
                        
        <input
          onChange={this.handleAvatarChange}
          type="file"
          accept="/images/*"
        />      
        <button type="submit">
          Create Account
        </button>
    </form>
                );
        }
}
export default Signup;

Connect sign-up component to Redux

Bind React to Redux by issuing the command below, then import and use the

connect 

method from

react-redux 

library to bind React component(sign-up component) to Redux

npm i react-redux
import

connect

inside the

SignUp.js.
import { connect } from 'react-redux';

with this set up we can now mapStateToProps or mapDispatchToProps, after your Redux store is set up.

At this point we have to set up Redux action and Redux reducers to be able to either mapStateToProps or mapDispatchToProps. In other words, read from the Redux store or send data to the Redux store. Inside the the Redux action we will dispatch a function to our reducer as we also persist data to our back-end Rails Api with axios. let us install axios for enable us use axios.

npm i axios
Let create these files inside

./src/redux/action.js, ./src/redux/reducer.js,

now we implement the Redux actions as

import axios from 'axios';

const CREATE_USER = 'CREATE_USER';
const CREATE_USER_ERROR = 'CREATE_USER_ERROR';

const createUser = newUser => async (dispatch) => {
        try {
                dispatch({ type: CREATE_USER, ...newUser });
                const response = await axios({
                        method: 'POST',
                        url: 'Api route for posting data to database',
                        data: { user: newUser },
                        crossdomain: true,
                });
                const { token } = response.data;
                localStorage.setItem('jwt', token);        
        } catch {
                dispatch({ type: CREATE_USER_ERROR });
        }
};
export { createUser }

The createUser method is going to send data to our rails API and wait for a response. I also sends data to update the Redux store.

We are going to configure our API to return some

Json

data that would contain information about whether our request from the front end to sign-up a user was successful or not.

If successful, we will login the user right after successful signup and maintain a session for the user. To maintain a session for user, we store the token generated from the back-end response(returned as part of our positive response) inside localStorage. This is handled in these lines

const { token } = response.data;
localStorage.setItem('jwt', token);

we are storing tho token in the localstorage hash on the key ‘jwt’

Now we have to create our reducer for the

CREATE_USER and CREATE_USER_ERROR
const initialState = {
        isLogin: false,
        user: {
                username: '',
                email: '',
                password: '',
                picture: '',
        }
};
const authReducer = (state = initialState, action) => {
        switch (action.type) {
                case 'CREATE_USER': return {
                        ...state.user,
                        isLogin: true,
                        user: {
                                username: action.username,
                                email: action.email,
                                password: action.password,
                                picture: action.picture,
                        },
                };
                case 'CREATE_USER_ERROR': return {
                        isLogin: false,
                };
                default: return state;
        }
};
export default authReducer;

CREATE_USER_ERROR is dispatched when a negative response is returned from our server. With this in place we almost good to go. But there would be two problems if we try to signup a user. we need to properly handle the picture attribute and set up redux-thunk middleware since we are dispatching a function in our createUser action creator.

To handle our image upload, we will store our image to cloudinary and copy the path of the image in cloudinary to our database.

storing images directly into the server is not recommended. you it is good to use third parties such amazon s3, cloudinary, etc…

for this we will update our S

ignUp.js

to include

handleImageUpload() and onSubmitHandler(). 
handleImageUpload() 

will be responsible for storing the image file selected to cloudinary and return the URL reference of that image. This URL reference is then sent as part of data dispatch to redux and the rails back-end.

onSubmitHandler(),

this method on the other hand will be responsible for sending data to our action creator. We shall import the

createUser 

action creator method, and passed data to from the form to createUser({sign up state data}) to be update the redux store and send data to the rails API. In this app

createUser 

will mapped to

newUser

, hence we will reference create user as

NewUser

.

mapDispatchToProps= dispatch=>({
createUser: newUser
})

we destructure state data and get the URL reference, pass it as an argument to newUser, for dispatch.

const {username, email, password,picture}=this.state
//get image path, from handleImageUpload
newUser({username,email,password,picture})

Check below for full implementation.

    handleImageUpload = async (imageFile) => {
                const formData = new FormData();
                formData.append('file', imageFile);
                formData.append('upload_preset', 'avatar');
                const response = await axios({
                        url: 'your cloudinary api url',
                        method: 'POST',
                        data: formData,
                });
                return response.data.secure_url;
        }

        onSubmitHandler = async (e) => {
                e.preventDefault();
                const {username, email, password } = this.state;
                let { picture } = this.state;
                const { newUser, user } = this.props;
                const imgPath = await this.handleImageUpload(picture);
                picture = imgPath;
                await newUser({username, email, password, picture});
                if (user.isLogin === true) {
                        const { history } = this.props;
                        history.push('/');
                } else {
                        this.setState(
                                {
                                        message: 'welcome',
                                },
                        );
                }
        }

with this in place we can mapDispatchToProps() and mapStateToProps() to dispatch our data to redux, and read data from the store. Check below.

const mapStateToProps = state => ({
        user: state.user,
});
const mapDispatchToProps = dispatch => ({
        newUser: estate => dispatch(createUser(estate)),
});
Signup.propTypes = {
        newUser: PropTypes.func.isRequired,
        user: PropTypes.object.isRequired,
        history: PropTypes.func.isRequired
};
export default connect(mapStateToProps, mapDispatchToProps)(Signup);

our resultant SignUp.js code will become like this

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import axios from 'axios';
import { createUser } from '../../redux/actions/authAction';

class Signup extends Component {
        constructor(props) {
                super(props);
                this.state = {
                        username: '',
                        email: '',
                        password: '',
                        picture: '',
                        message: '',
                };
        }

        handleUsernameChange = (e) => {
                this.setState({
                        username: e.target.value,
                });
        }

        handleEmailChange = (e) => {
                this.setState({
                        email: e.target.value,
                });
        }

        handlePasswordChange = (e) => {
                this.setState({
                        password: e.target.value,
                });
        }

        handleAvatarChange = async (e) => {
                this.setState({
                        picture: e.target.files[0],
                });
        }

        handleImageUpload = async (imageFile) => {
                const formData = new FormData();
                formData.append('file', imageFile);
                formData.append('upload_preset', 'avatar');
                delete axios.defaults.headers.common.Authorization;
                const response = await axios({
                        url: 'your cloudinary api url',
                        method: 'POST',
                        data: formData,
                });
                return response.data.secure_url;
        }

        onSubmitHandler = async (e) => {
                e.preventDefault();
                const {
                        username, email, password,
                } = this.state;
                let { picture } = this.state;
                const { newUser, user } = this.props;

                const imgPath = await this.handleImageUpload(picture);
                picture = imgPath;
                await newUser({
                        username, email, password, picture,
                });
                if (user.isLogin === true) {
                        const { history } = this.props;
                        history.push('/');
                } else {
                        this.setState(
                                {
                                        message: 'welcome',
                                },
                        );
                }
        }

render() {
  const { message } = this.state;
        return (
                <form onSubmit={this.onSubmitHandler} >
                        <h1>{message}</h1>
                  <input onChange={this.handleUsernameChange} type="text" required />
                        <input onChange={this.handleEmailChange} type="email" required />
                        <input onChange={this.handlePasswordChange} type="password" required />
                        <input onChange={this.handleAvatarChange} type="file" accept="/images/*" />
      <Link to="/login">login</Link>
                        <button type="submit">Create Account</button>
                </form>
                );
        }
}
const mapStateToProps = state => ({
        user: state.user,
});
const mapDispatchToProps = dispatch => ({
        newUser: estate => dispatch(createUser(estate)),
});
Signup.propTypes = {
        newUser: PropTypes.func.isRequired,
        user: PropTypes.object.isRequired,
        history: PropTypes.object.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(Signup);
let us create a store and add redux-thunk middleware to allows us to write action creators that return a function instead of an action. move to index.js and set it up like this.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import rootReducer from './redux/reducers/index';
import App from './component/App';

const initialState = {
        user: {
                isLogin: false,
                newuser: {
                        username: '',
                        email: '',
                        password: '',
                        picture: '',
                },
        },
};
const store = createStore(rootReducer, initialState, applyMiddleware(thunk));
ReactDOM.render(
        <Provider store={store}>
                <App />
        </Provider>,
        document.getElementById('root'),
);

now our front-end is ready, we can now focus on the back-end

Create rails Api

rails new backend --api --database=postgresql -T
rails g model User username:string email:string password:string picture:string
rails db:migrate
Now we include

jwt gem

and

rack-cors gem

in gemfile.

then we run

bundle install

in console.

configure cors to accept request from a source of your choice. Cors setting determines which domain should your api grant access to. read more on rack-cor gem we will configure our cors to accept request from all domain source by setting

origin '*'  in config/application.rb
 Rails.application.config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins '*'

        resource '*',
                 headers: :any,
                 methods: %i[get post put patch delete options head]
      end
    end

we are going to skip testing and also ignore validations. We are going to focus on the controller and its actions. Let create a controller for our user model

rails g controller users 

we will new a create action to create a user, inside our users_controller.rb we create a create method with this code.

class UsersController < ApplicationController
  def create
    new_user = User.new(user_params)
    if new_user.save
      token = JsonWebToken.encode(user_id: new_user.id)
      time = Time.now + 24.hours.to_i
      render json: { token: token, time: time }, status: :ok
    else
      head(:unprocessable_entity)
    end
  end

  private

  def user_params
    params.require(:user).permit(
      :username,
      :email,
      :country,
      :password,
      :picture
    )
  end
end
create user method accept parameters and save it to database. Then get the user’s id, encrypt the id with

jwt gem

and add a time stamp to the encrypted user_id. The time stamp determines the time of expiry of the token, in this case, it expires after 24hrs. This means a user’s session will be destroyed after 24hrs from sign up.

we can now set up our route inside

config/route.rb
resources :users, only: %i[create]

Get front end to communicate with back-end

run rails routes in console to view the url for your api. In this case our post requests is going to be made on

backend_domain_name/users

Conclusion

Now we will be able to save a users information inside our rails back-end with these steps and returned a token to maintain a session. A similar result can be used for sign in just that for signing in the instead of saving we compare email and password to check if they exist then we create a token that would be used by user maintain a session.

Note this token are sent as part of the response to our the front end. The session handling is then than in the front end.

read original article here