visit
Note: This is an advanced topic, basic to intermediate knowledge in React Redux and Ruby on rails is a prerequisite for this tutorial. Visit this link for , for and .
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 stateSend user data to the back-end for persistence with Axios.Steps for setting up Back-end
Create your rails API Create a DB for APICreate a user table and modelMigrate tableCreate a users_controller to handle data persistenceCreate 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.jscreate 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 themethod fromconnect
library to bind React component(sign-up component) to Reduxreact-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
read more on axios from the axios Let create these files inside
./src/redux/action.js, ./src/redux/reducer.js,
now we implement the Redux actions asimport 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 }
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;
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
})
const {username, email, password,picture}=this.state
//get image path, from handleImageUpload
newUser({username,email,password,picture})
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);
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'),
);
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 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
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
ConclusionNow 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.