React with Rails Using Foreman

Total Blog Views: 58

Blog Status: publish

Created By: swaz_ahmed Created at: 02-06-2025

Tags: ruby on rails devise React react with rails without webpecker rack-cors material UI foreman

Integrating React with Rails Using Foreman: A Comprehensive Guide

In the modern web development landscape, creating seamless, dynamic user experiences is essential. Ruby on Rails, a robust server-side framework, has long been a favorite for building powerful backend systems. On the other hand, React, a popular JavaScript library, excels in crafting rich, interactive user interfaces. Integrating React into a Rails application combines the best of both worlds, allowing developers to leverage Rails' powerful backend capabilities while delivering a highly responsive and engaging frontend.

Why Integrate React with Rails and Foreman?

Before diving into the integration process, let’s explore why combining React with Rails and using Foreman is a great approach:

  • Separation of Concerns: React handles the front-end view layer, while Rails takes care of the back-end, promoting a clean separation of concerns between the two parts of your application.
  • Enhanced User Experience: React’s ability to build fast, dynamic, and interactive UIs enhances the user experience, while Rails efficiently manages the server-side logic and database interactions.
  • Efficient Process Management: Foreman makes managing multiple processes (like running the Rails server and the React development server simultaneously) simple and efficient, keeping everything organized in a single configuration.
  • Scalability: Both React and Rails are highly scalable on their own, and using Foreman to streamline your development process ensures that scaling your application remains straightforward as your app grows.
  • Rich Ecosystem: Leveraging the powerful ecosystems of both React and Rails, alongside the convenience of Foreman, accelerates development and allows for flexible, maintainable, and robust web applications.

 

Setting Up the Rails Application:

To get started, we will first setup rails app api only using :-

rails new blog --api

Then after setting the rails app we will add devise gem, rack-cors gem then setup devise model, controller etc Except view.

 

Creating React app:-

We will create react app named "frontend" inside rails app.

npx create-react-app frontend

Lets setup up our UI so that it can perform Sign_in, Sign_up and Log_out before proceedings to foreman.

Now Run below command, also I will be using material UI, If you don't want ignore it's step :-

cd frontend

npm install @mui/material @emotion/react @emotion/styled (material-UI)

npm install

Hence, You have sucessfully created react app. Now time to add UI functionality of Sign_in, Sign_out, and Sign_up.

You will see index.js :-
 

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Change it to :-

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';
import { CssBaseline } from '@mui/material';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <Router>
      <CssBaseline />
      <App />
    </Router>
  </React.StrictMode>
);

Don't forget to install router library using :-

npm install react-router-dom

Now change your App.js file to :-

import React, { useState, useEffect } from 'react';
import { Route, Routes, Navigate } from 'react-router-dom';
import Login from './components/Login';
import Register from './components/Register';
import Logout from './components/Logout';
import { Container, AppBar, Toolbar, Typography, Button, Box, Snackbar, Alert } from '@mui/material';
import { useNavigate } from 'react-router-dom';

const App = () => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [openSnackbar, setOpenSnackbar] = useState(false);
  const [snackbarMessage, setSnackbarMessage] = useState('');
  const navigate = useNavigate();

  useEffect(() => {
    const authStatus = sessionStorage.getItem('isAuthenticated');
    if (authStatus) {
      setIsAuthenticated(true);
    }
  }, []);

  const handleLogout = () => {
    setIsAuthenticated(false);
    sessionStorage.removeItem('isAuthenticated');
    setSnackbarMessage('Logged out successfully');
    setOpenSnackbar(true);
    navigate('/login');
  };

  const handleLoginSuccess = () => {
    setIsAuthenticated(true);
    sessionStorage.setItem('isAuthenticated', 'true');
    setSnackbarMessage('Logged in successfully');
    setOpenSnackbar(true);
    navigate('/');
  };

  const handleRegisterSuccess = () => {
    setIsAuthenticated(true);
    sessionStorage.setItem('isAuthenticated', 'true');
    setSnackbarMessage('Registered successfully');
    setOpenSnackbar(true);
    navigate('/');
  };

  return (
    <div>
      <AppBar position="sticky">
        <Toolbar>
          <Typography variant="h6" sx={{ flexGrow: 1 }}>
            My Blog
          </Typography>

          <Box>
            {isAuthenticated ? (
              <Button color="inherit" onClick={handleLogout}>
                Logout
              </Button>
            ) : (
              <>
                <Button color="inherit" onClick={() => navigate('/login')}>
                  Login
                </Button>
                <Button color="inherit" onClick={() => navigate('/register')}>
                  Register
                </Button>
              </>
            )}
          </Box>
        </Toolbar>
      </AppBar>

      <Container sx={{ marginTop: 3 }}>
        <Routes>
          <Route
            path="/"
            element={isAuthenticated ? (
            <div>
              <Typography variant="h4">Welcome to the React + Rails App!</Typography>
              </div>
              ) : ( <Navigate to="/login" /> )}
          />
          <Route path="/login" element={<Login onLogin={handleLoginSuccess} />} />
          <Route path="/register" element={<Register onRegister={handleRegisterSuccess} />} />
          <Route path="/logout" element={<Logout onLogout={handleLogout} />} />
        </Routes>
      </Container>

      <Snackbar
        open={openSnackbar}
        autoHideDuration={3000}
        onClose={() => setOpenSnackbar(false)}
      >
        <Alert onClose={() => setOpenSnackbar(false)} variant="filled" severity="success" sx={{ width: '100%' }}>
          {snackbarMessage}
        </Alert>
      </Snackbar>
    </div>
  );
};

export default App;

Setting Up Components :-

For better readability we will create folders inside "src" :-

api and components (folder name)

Folder(api):-

Inside api folder we will create file named "connection.jsx" :-

import axios from 'axios';

const apiUrl = 'localhost:5100';
const api = axios.create({
  baseURL: `${apiUrl}/users`,
  headers: {
    'Content-Type': 'application/json',
  },
});

export default api;

Here we have stored the rails URL into "api" also install axios :-

npm install axios

 

Folder(components):-

create files inside components folder

Login.jsx, Logout.jsx and Register.jsx

Change the Files-

Login.jsx :-

import React, { useState } from 'react';
import { TextField, Button, Container, Typography, CircularProgress } from '@mui/material';
import api from '../api/connection';
import axios from 'axios';

const Login = ({ onLogin }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      const response = await api.post('/sign_in', {
          email,
          password,
      });
      const token = response.data.token;
      localStorage.setItem('authToken', token);
      axios.defaults.headers['Authorization'] = `Bearer ${token}`;

      onLogin();

    } catch (err) {
      const errorMessage = err.response?.data?.errors || 'Login failed. Please try again.';
      setError(errorMessage);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Container maxWidth="xs">
      <Typography variant="h5" gutterBottom>Login</Typography>
      {error && <Typography color="error" variant="body2" gutterBottom>{error}</Typography>}

      <form onSubmit={handleSubmit}>
        <TextField
          label="Email"
          variant="outlined"
          fullWidth
          margin="normal"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          disabled={loading}
        />
        <TextField
          label="Password"
          variant="outlined"
          type="password"
          fullWidth
          margin="normal"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          disabled={loading}
        />
        <Button
          type="submit"
          variant="contained"
          color="primary"
          fullWidth
          disabled={loading}
        >
          {loading ? <CircularProgress size={24} color="inherit" /> : 'Login'}
        </Button>
      </form>
    </Container>
  );
};

export default Login;

Logout.jsx:-

import React from 'react';
import { Button } from '@mui/material';
import api from '../api/connection';

const Logout = ({ onLogout }) => {
  const handleLogout = async () => {
    try {
      await api.delete('/sign_out');
      onLogout();
    } catch (err) {
      console.error('Logout failed:', err);
    }
  };

  return <Button onClick={handleLogout}>Logout</Button>;
};

export default Logout;

Register.jsx :-
 

import React, { useState } from 'react';
import { TextField, Button, Container, Typography, CircularProgress } from '@mui/material';
import api from '../api/connection';
import { useNavigate } from 'react-router-dom';

const Register = ({ onRegister }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [passwordConfirmation, setPasswordConfirmation] = useState('');
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (password !== passwordConfirmation) {
      setError("Passwords don't match");
      return;
    }

    setLoading(true);
    setError(null);

    try {
      const response = await api.post('/', {
        user: {
          email,
          password,
          password_confirmation: passwordConfirmation,
        }
      });

      onRegister();
      setEmail('');
      setPassword('');
      setPasswordConfirmation('');
      navigate('/login');
    } catch (err) {
      console.log(err);
      const errorMessage = err.response?.data?.errors || 'Registration failed. Please try again.';
      setError(errorMessage);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Container maxWidth="xs">
      <Typography variant="h5" gutterBottom>
        Register
      </Typography>
      {error && <Typography color="error" variant="body2" gutterBottom>{error}</Typography>}

      <form onSubmit={handleSubmit}>
        <TextField
          label="Email"
          variant="outlined"
          fullWidth
          margin="normal"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          disabled={loading}
        />
        <TextField
          label="Password"
          variant="outlined"
          type="password"
          fullWidth
          margin="normal"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          disabled={loading}
        />
        <TextField
          label="Confirm Password"
          variant="outlined"
          type="password"
          fullWidth
          margin="normal"
          value={passwordConfirmation}
          onChange={(e) => setPasswordConfirmation(e.target.value)}
          disabled={loading}
        />
        <Button
          type="submit"
          variant="contained"
          color="primary"
          fullWidth
          disabled={loading}
        >
          {loading ? <CircularProgress size={24} color="inherit" /> : 'Register'}
        </Button>
      </form>
    </Container>
  );
};

export default Register;

Hence you have setup React app.

 

Setting Up :-

Now we have setup both react and rails app. Time to connect them.

We will use the foreman gem to manage our custom commands.

Terminal:-

gem install foreman

 Now create the following files in rails app, type following command in terminal:-

touch Procfile

cd bin

touch dev

touch api

touch front

cd ..

 

Change Files :-

Inside bin folder you will see 3 files which we have created above dev, api and front change them to...

dev

foreman start

api

rails server

front

cd frontend && npm start

 

Final Touch:-

Now setup Procfile which we have created in rails app root directory.

 

Procfile

front: bin/front
api: bin/api

 

Now you have setup both react and rails app using foreman gem to run your app just type on terminal

bin/dev

 

Conclusion :- 

Integrating React into a Rails application using Foreman for process management provides an efficient and organized development environment. By following this guide, you now have the foundational knowledge of how to set up React within a Rails app, ensuring smooth coordination between the front-end and back-end. Foreman simplifies running multiple processes (like Rails and React development servers) simultaneously, making it easier to manage your application's workflow. This approach helps you build high-performance, dynamic web applications while maintaining a streamlined and effective development process.


swaz_ahmed

I am swaz_ahmed blogger on shadbox. I am influencer,content writer,author and publisher. Feel free to ask me any question and suggestions.



Comments



Buy traffic for your website

About Shadbox

we have the “Get things executed” lifestyle at our place of work. There are not any excuses, no if’s or however’s in our dictionary. committed to navigating the ship of creativity to create cell answers, we resolve the real-lifestyles troubles of our clients and their clients. Our passion for work has won us many awards, year after 12 months.

Services

Downloads