One place for hosting & domains

      How To Set Up a Ruby on Rails Project with a React Frontend


      The author selected the Electronic Frontier Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Ruby on Rails is a popular server-side web application framework, with over 42,000 stars on GitHub at the time of writing this tutorial. It powers a lot of the popular applications that exist on the web today, like GitHub, Basecamp, SoundCloud, Airbnb, and Twitch. With its emphasis on programmer experience and the passionate community that has built up around it, Ruby on Rails will give you the tools you need to build and maintain your modern web application.

      React is a JavaScript library used to create front-end user interfaces. Backed by Facebook, it is one of the most popular front-end libraries used on the web today. React offers features like a virtual Document Object Model (DOM), component architecture, and state management, which make the process of front-end development more organized and efficient.

      With the frontend of the web moving toward frameworks that are separate from the server-side code, combining the elegance of Rails with the efficiency of React will let you build powerful and modern applications informed by current trends. By using React to render components from within a Rails view instead of the Rails template engine, your application will benefit from the latest advancements in JavaScript and front-end development while still leveraging the expressiveness of Ruby on Rails.

      In this tutorial, you will create a Ruby on Rails application that stores your favorite recipes then displays them with a React frontend. When you are finished, you will be able to create, view, and delete recipes using a React interface styled with Bootstrap:

      Completed Recipe App

      If you would like to take a look at the code for this application, check out the companion repository for this tutorial on the DigitalOcean Community GitHub.

      Prerequisites

      To follow this tutorial, you need to have the following:

      • Node.js and npm installed on your development machine. This tutorial uses Node.js version 10.16.0 and npm version 6.9.0. Node.js is a JavaScript run-time environment that allows you to run your code outside of the browser. It comes with a pre-installed Package Manager called npm, which lets you install and update packages. To install these on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the “Installing Using a PPA” section of How To Install Node.js on Ubuntu 18.04.

      • The Yarn package manager installed on your development machine, which will allow you to download the React framework. This tutorial was tested on version 1.16.0; to install this dependency, follow the official Yarn installation guide.

      • Installation of the Ruby on Rails framework. To get this, follow our guide on How to Install Ruby on Rails with rbenv on Ubuntu 18.04, or How To Install Ruby on Rails with rbenv on CentOS 7. If you would like to develop this application on macOS, please see this tutorial on How To Install Ruby on Rails with rbenv on macOS. This tutorial was tested on version 2.6.3 of Ruby and version 5.2.3 of Rails, so make sure to specify these versions during the installation process.

      • Installation of PostgreSQL, as shown in Steps 1 and 2 of our tutorial How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 18.04 or How To Use PostgreSQL with Your Ruby on Rails Application on macOS. To follow this tutorial, use PostgreSQL version 10. If you are looking to develop this application on a different distribution of Linux or on another OS, see the official PostgreSQL downloads page. For more information on how to use PostgreSQL, see our How To Install and Use PostgreSQL tutorials.

      Step 1 — Creating a New Rails Application

      In this step, you will build your recipe application on the Rails application framework. First, you’ll create a new Rails application, which will be set up to work with React out of the box with little configuration.

      Rails provides a number of scripts called generators that help in creating everything that’s necessary to build a modern web application. To see a full list of these commands and what they do, run the following command in your Terminal window:

      This will yield a comprehensive list of options, which will allow you to set the parameters of your application. One of the commands listed is the new command, which creates a new Rails application.

      Now, you will create a new Rails application using the new generator. Run the following command in your Terminal window:

      • rails new rails_react_recipe -d=postgresql -T --webpack=react --skip-coffee

      The preceding command creates a new Rails application in a directory named rails_react_recipe, installs the required Ruby and JavaScript dependencies, and configures Webpack. Let’s walk through the flags that are associated with this new generator command:

      • The -d flag specifies the preferred database engine, which in this case is PostgreSQL.
      • The -T flag instructs Rails to skip the generation of test files, since you won’t be writing tests for the purposes of this tutorial. This command is also suggested if you want to use a Ruby testing tool different from the one Rails provides.
      • The --webpack instructs Rails to preconfigure for JavaScript with the webpack bundler, in this case specifically for a React application.
      • The --skip-coffee asks Rails not to set up CoffeeScript, which is not needed for this tutorial.

      Once the command is done running, move into the rails_react_recipe directory, which is the root directory of your app:

      Next, list out the contents of the directory:

      This root directory has a number of auto-generated files and folders that make up the structure of a Rails application, including a package.json file containing the dependencies for a React application.

      Now that you have successfully created a new Rails application, you are ready to hook it up to a database in the next step.

      Step 2 — Setting Up the Database

      Before you run your new Rails application, you have to first connect it to a database. In this step, you'll connect the newly created Rails application to a PostgreSQL database, so recipe data can be stored and fetched when needed.

      The database.yml file found in config/database.yml contains database details like database name for different development environments. Rails specifies a database name for the different development environments by appending an underscore (_) followed by the environment name to your app’s name. You can always change any environment database name to whatever you prefer.

      Note: At this point, you can alter config/database.yml to set up which PostgreSQL role you would like Rails to use to create your database. If you followed the Prerequisite How To Use PostgreSQL with Your Ruby on Rails Application and created a role that is secured by a password, you can follow the instructions in Step 4 for macOS or Ubuntu 18.04.

      As earlier stated, Rails offers a lot of commands to make developing web applications easy. This includes commands to work with databases, such as create, drop, and reset. To create a database for your application, run the following command in your Terminal window:

      This command creates a development and test database, yielding the following output:

      Output

      Created database 'rails_react_recipe_development' Created database 'rails_react_recipe_test'

      Now that the application is connected to a database, start the application by running the following command in you Terminal window:

      • rails s --binding=127.0.0.1

      The s or server command fires up Puma, which is a web server distributed with Rails by default, and --binding=127.0.0.1 binds the server to your localhost.

      Once you run this command, your command prompt will disappear, and you will see the following output:

      Output

      => Booting Puma => Rails 5.2.3 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.1:3000 Use Ctrl-C to stop

      To see your application, open a browser window and navigate to http://localhost:3000. You will see the Rails default welcome page:

      Rails welcome page

      This means that you have properly set up your Rails application.

      To stop the web server at anytime, press CTRL+C in the Terminal window where the server is running. Go ahead and do this now; you will get a goodbye message from Puma:

      Output

      ^C- Gracefully stopping, waiting for requests to finish === puma shutdown: 2019-07-31 14:21:24 -0400 === - Goodbye! Exiting

      Your prompt will then reappear.

      You have successfully set up a database for your food recipe application. In the next step, you will install all the extra JavaScript dependencies you need to put together your React frontend.

      Step 3 — Installing Frontend Dependencies

      In this step, you will install the JavaScript dependencies needed on the frontend of your food recipe application. They include:

      Run the following command in your Terminal window to install these packages with the Yarn package manager:

      • yarn add react-router-dom bootstrap jquery popper.js

      This command uses Yarn to install the specified packages and adds them to the package.json file. To verify this, take a look at the package.json file located in the root directory of the project:

      You'll see the installed packages listed under the dependencies key:

      ~/rails_react_recipe/package.json

      {
        "name": "rails_react_recipe",
        "private": true,
        "dependencies": {
          "@babel/preset-react": "^7.0.0",
          "@rails/webpacker": "^4.0.7",
          "babel-plugin-transform-react-remove-prop-types": "^0.4.24",
          "bootstrap": "^4.3.1",
          "jquery": "^3.4.1",
          "popper.js": "^1.15.0",
          "prop-types": "^15.7.2",
          "react": "^16.8.6",
          "react-dom": "^16.8.6",
          "react-router-dom": "^5.0.1"
        },
        "devDependencies": {
          "webpack-dev-server": "^3.7.2"
        }
      }
      

      You have installed a few front-end dependencies for your application. Next, you’ll set up a homepage for your food recipe application.

      Step 4 — Setting Up the Homepage

      With all the required dependencies installed, in this step you will create a homepage for the application. The homepage will serve as the landing page when users first visit the application.

      Rails follows the Model-View-Controller architectural pattern for applications. In the MVC pattern, a controller's purpose is to receive specific requests and pass them along to the appropriate model or view. Right now the application displays the Rails welcome page when the root URL is loaded in the browser. To change this, you will create a controller and view for the homepage and match it to a route.

      Rails provides a controller generator for creating a controller. The controller generator receives a controller name, along with a matching action. For more on this, check out the official Rails documentation.

      This tutorial will call the controller Homepage. Run the following command in your Terminal window to create a Homepage controller with an index action.

      • rails g controller Homepage index

      Note:
      On Linux, if you run into the error FATAL: Listen error: unable to monitor directories for changes., this is due to a system limit on the number of files your machine can monitor for changes. Run the following command to fix it:

      • echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

      This will permanently increase the amount of directories that you can monitor with Listen to 524288. You can change this again by running the same command and replacing 524288 with your desired number.

      Running this command generates the following files:

      • A homepage_controller.rb file for receiving all homepage-related requests. This file contains the index action you specified in the command.
      • A homepage.js file for adding any JavaScript behavior related to the Homepage controller.
      • A homepage.scss file for adding styles related to the Homepage controller.
      • A homepage_helper.rb file for adding helper methods related to the Homepage controller.
      • An index.html.erb file which is the view page for rendering anything related to the homepage.

      Apart from these new pages created by running the Rails command, Rails also updates your routes file which is located at config/routes.rb. It adds a get route for your homepage which you will modify as your root route.

      A root route in Rails specifies what will show up when users visit the root URL of your application. In this case, you want your users to see your homepage. Open the routes file located at config/routes.rb in your favorite editor:

      Inside this file, replace get 'homepage/index' with root 'homepage#index' so that the file looks like the following:

      ~/rails_react_recipe/config/routes.rb

      Rails.application.routes.draw do
        root 'homepage#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      This modification instructs Rails to map requests to the root of the application to the index action of the Homepage controller, which in turn renders whatever is in the index.html.erb file located at app/views/homepage/index.html.erb on to the browser.

      To verify that this is working, start your application:

      • rails s --binding=127.0.0.1

      Opening the application in the browser, you will see a new landing page for your application:

      Application Homepage

      Once you have verified that your application is working, press CTRL+C to stop the server.

      Next, delete the contents of the ~/rails_react_recipe/app/views/homepage/index.html.erb file. By doing this, you will ensure that the contents of index.html.erb do not interfere with the React rendering of your frontend.

      Now that you have set up your homepage for your application, you can move to the next section, where you will configure the frontend of your application to use React.

      Step 5 — Configuring React as Your Rails Frontend

      In this step, you will configure Rails to use React on the frontend of the application, instead of its template engine. This will allow you to take advantage of React rendering to create a more visually appealing homepage.

      Rails, with the help of the Webpacker gem, bundles all your JavaScript code into packs. These can be found in the packs directory at app/javascript/packs. You can link these packs in Rails views using the javascript_pack_tag helper, and you can link stylesheets imported into the packs using the stylesheet_pack_tag helper. To create an entry point to your React environment, you will add one of these packs to your application layout.

      First, rename the ~/rails_react_recipe/app/javascript/packs/hello_react.jsx file to ~/rails_react_recipe/app/javascript/packs/Index.jsx.

      • mv ~/rails_react_recipe/app/javascript/packs/hello_react.jsx ~/rails_react_recipe/app/javascript/packs/Index.jsx

      After renaming the file, open application.html.erb, the application layout file:

      • nano ~/rails_react_recipe/app/views/layouts/application.html.erb

      Add the following highlighted lines of code at the end of the head tag in the application layout file:

      ~/rails_react_recipe/app/views/layouts/application.html.erb

      <!DOCTYPE html>
      <html>
        <head>
          <title>RailsReactRecipe</title>
          <%= csrf_meta_tags %>
          <%= csp_meta_tag %>
      
          <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
          <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
          <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
          <%= javascript_pack_tag 'Index' %>
        </head>
      
        <body>
          <%= yield %>
        </body>
      </html>
      

      Adding the JavaScript pack to your application’s header makes all your JavaScript code available and executes the code in your Index.jsx file on the page whenever you run the app. Along with the JavaScript pack, you also added a meta viewport tag to control the dimensions and scaling of pages on your application.

      Save and exit the file.

      Now that your entry file is loaded onto the page, create a React component for your homepage. Start by creating a components directory in the app/javascript directory:

      • mkdir ~/rails_react_recipe/app/javascript/components

      The components directory will house the component for the homepage, along with other React components in the application. The homepage will contain some text and a call to action button to view all recipes.

      In your editor, create a Home.jsx file in the components directory:

      • nano ~/rails_react_recipe/app/javascript/components/Home.jsx

      Add the following code to the file:

      ~/rails_react_recipe/app/javascript/components/Home.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      export default () => (
        <div className="vw-100 vh-100 primary-color d-flex align-items-center justify-content-center">
          <div className="jumbotron jumbotron-fluid bg-transparent">
            <div className="container secondary-color">
              <h1 className="display-4">Food Recipes</h1>
              <p className="lead">
                A curated list of recipes for the best homemade meal and delicacies.
              </p>
              <hr className="my-4" />
              <Link
                to="/recipes"
                className="btn btn-lg custom-button"
                role="button"
              >
                View Recipes
              </Link>
            </div>
          </div>
        </div>
      );
      

      In this code, you imported React and also the Link component from React Router. The Link component creates a hyperlink to navigate from one page to another. You then created and exported a functional component containing some Markup language for your homepage, styled with Bootstrap classes.

      With your Home component in place, you will now set up routing using React Router. Create a routes directory in the app/javascript directory:

      • mkdir ~/rails_react_recipe/app/javascript/routes

      The routes directory will contain a few routes with their corresponding components. Whenever any specified route is loaded, it will render its corresponding component to the browser.

      In the routes directory, create an Index.jsx file:

      • nano ~/rails_react_recipe/app/javascript/routes/Index.jsx

      Add the following code to it:

      ~/rails_react_recipe/app/javascript/routes/Index.jsx

      import React from "react";
      import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
      import Home from "../components/Home";
      
      export default (
        <Router>
          <Switch>
            <Route path="/" exact component={Home} />
          </Switch>
        </Router>
      );
      

      In this Index.jsx route file, you imported a couple of modules: the React module that allows us to use React, and the BrowserRouter, Route, and Switch modules from React Router, which together help us navigate from one route to another. Lastly, you imported your Home component, which will be rendered whenever a request matches the root (/) route. Whenever you want to add more pages to your application, all you need to do is declare a route in this file and match it to the component you want to render for that page.

      Save and exit the file.

      You have now successfully set up routing using React Router. For React to be aware of the available routes and use them, the routes have to be available at the entry point to the application. To achieve this, you will render your routes in a component that React will render in your entry file.

      Create an App.jsx file in the app/javascript/components directory:

      • nano ~/rails_react_recipe/app/javascript/components/App.jsx

      Add the following code into the App.jsx file:

      ~/rails_react_recipe/app/javascript/components/App.jsx

      import React from "react";
      import Routes from "../routes/Index";
      
      export default props => <>{Routes}</>;
      

      In the App.jsx file, you imported React and the route files you just created. You then exported a component that renders the routes within fragments. This component will be rendered at the entry point of the aplication, thereby making the routes available whenever the application is loaded.

      Now that you have your App.jsx set up, it's time to render it in your entry file. Open the entry Index.jsx file:

      • nano ~/rails_react_recipe/app/javascript/packs/Index.jsx

      Replace the code there with the following code:

      ~/rails_react_recipe/app/javascript/packs/Index.jsx

      import React from "react";
      import { render } from "react-dom";
      import 'bootstrap/dist/css/bootstrap.min.css';
      import $ from 'jquery';
      import Popper from 'popper.js';
      import 'bootstrap/dist/js/bootstrap.bundle.min';
      import App from "../components/App";
      
      document.addEventListener("DOMContentLoaded", () => {
        render(
          <App />,
          document.body.appendChild(document.createElement("div"))
        );
      });
      

      In this code snippet, you imported React, the render method from ReactDOM, Bootstrap, jQuery, Popper.js, and your App component. Using ReactDOM's render method, you rendered your App component in a div element, which was appended to the body of the page. Whenever the application is loaded, React will render the content of the App component inside the div element on the page.

      Save and exit the file.

      Finally, add some CSS styles to your homepage.

      Open up your application.css in your ~/rails_react_recipe/app/assets/stylesheets directory:

      • nano ~/rails_react_recipe/app/assets/stylesheets/application.css

      Next, replace the contents of the application.css file with the follow code:

      ~/rails_react_recipe/app/assets/stylesheets/application.css

      .bg_primary-color {
        background-color: #FFFFFF;
      }
      .primary-color {
        background-color: #FFFFFF;
      }
      .bg_secondary-color {
        background-color: #293241;
      }
      .secondary-color {
        color: #293241;
      }
      .custom-button.btn {
        background-color: #293241;
        color: #FFF;
        border: none;
      }
      .custom-button.btn:hover {
        color: #FFF !important;
        border: none;
      }
      .hero {
        width: 100vw;
        height: 50vh;
      }
      .hero img {
        object-fit: cover;
        object-position: top;
        height: 100%;
        width: 100%;
      }
      .overlay {
        height: 100%;
        width: 100%;
        opacity: 0.4;
      }
      

      This creates the framework for a hero image, or a large web banner on the front page of your website, that you will add later. Additionally, this styles the button that the user will use to enter the application.

      With your CSS styles in place, save and exit the file. Next, restart the web server for your application, then reload the application in your browser. You will see a brand new homepage:

      Homepage Style

      In this step, you configured your application so that it uses React as its frontend. In the next section, you will create models and controllers that will allow you to create, read, update, and delete recipes.

      Step 6 — Creating the Recipe Controller and Model

      Now that you have set up a React frontend for your application, in this step you'll create a Recipe model and controller. The recipe model will represent the database table that will hold information about the user's recipes while the controller will receive and handle requests to create, read, update, or delete recipes. When a user requests a recipe, the recipe controller receives this request and passes it to the recipe model, which retrieves the requested data from the database. The model then returns the recipe data as a response to the controller. Finally, this information is displayed in the browser.

      Start by creating a Recipe model by using the generate model subcommand provided by Rails and by specifying the name of the model along with its columns and data types. Run the following command in your Terminal window to create a Recipe model:

      • rails generate model Recipe name:string ingredients:text instruction:text image:string

      The preceding command instructs Rails to create a Recipe model together with a name column of type string, an ingredients and instruction column of type text, and an image column of type string. This tutorial has named the model Recipe, because by convention models in Rails use a singular name while their corresponding database tables use a plural name.

      Running the generate model command creates two files:

      • A recipe.rb file that holds all the model related logic.
      • A 20190407161357_create_recipes.rb file (the number at the beginning of the file may differ depending on the date when you run the command). This is a migration file that contains the instruction for creating the database structure.

      Next, edit the recipe model file to ensure that only valid data is saved to the database. You can achieve this by adding some database validation to your model. Open your recipe model located at app/models/recipe.rb:

      • nano ~/rails_react_recipe/app/models/recipe.rb

      Add the following highlighted lines of code to the file:

      class Recipe < ApplicationRecord
        validates :name, presence: true
        validates :ingredients, presence: true
        validates :instruction, presence: true
      end
      

      In this code, you added model validation which checks for the presence of a name, ingredients, and instruction field. Without the presence of these three fields, a recipe is invalid and won’t be saved to the database.

      Save and quit the file.

      For Rails to create the recipes table in your database, you have to run a migration, which in Rails is a way to make changes to your database programmatically. To make sure that the migration works with the database you set up, it is necessary to make changes to the 20190407161357_create_recipes.rb file.

      Open this file in your editor:

      • nano ~/rails_react_recipe/db/migrate/20190407161357_create_recipes.rb

      Add the following highlighted lines, so that the file looks like this:

      db/migrate/20190407161357_create_recipes.rb

      class CreateRecipes < ActiveRecord::Migration[5.2]
        def change
          create_table :recipes do |t|
            t.string :name, null: false
            t.text :ingredients, null: false
            t.text :instruction, null: false
            t.string :image, default: 'https://raw.githubusercontent.com/do-community/react_rails_recipe/master/app/assets/images/Sammy_Meal.jpg'
            t.timestamps
          end
        end
      end
      

      This migration file contains a Ruby class with a change method, and a command to create a table called recipes along with the columns and their data types. You also updated 20190407161357_create_recipes.rb with a NOT NULL constraint on the name, ingredients, and instruction columns by adding null: false, ensuring that these columns have a value before changing the database. Finally, you added a default image URL for your image column; this could be another URL if you wanted to use a different image.

      With these changes, save and exit the file. You’re now ready to run your migration and actually create your table. In your Terminal window, run the following command:

      Here you used the database migrate command, which executes the instructions in your migration file. Once the command runs successfully, you will receive an output similar to the following:

      Output

      == 20190407161357 CreateRecipes: migrating ==================================== -- create_table(:recipes) -> 0.0140s == 20190407161357 CreateRecipes: migrated (0.0141s) ===========================

      With your recipe model in place, create your recipes controller and add the logic for creating, reading, and deleting recipes. In your Terminal window, run the following command:

      • rails generate controller api/v1/Recipes index create show destroy -j=false -y=false --skip-template-engine --no-helper

      In this command, you created a Recipes controller in an api/v1 directory with an index, create, show, and destroy action. The index action will handle fetching all your recipes, the create action will be responsible for creating new recipes, the show action will fetch a single recipe, and the destroy action will hold the logic for deleting a recipe.

      You also passed some flags to make the controller more lightweight, including:

      • -j=false which instructs Rails to skip generating associated JavaScript files.
      • -y=false which instructs Rails to skip generating associated stylesheet files.
      • --skip-template-engine, which instructs Rails to skip generating Rails view files, since React is handling your front-end needs.
      • --no-helper, which instructs Rails to skip generating a helper file for your controller.

      Running the command also updated your routes file with a route for each action in the Recipes controller. To use these routes, make changes to your config/routes.rb file.

      Open up the routes file in your text editor:

      • nano ~/rails_react_recipe/config/routes.rb

      Once it is open, update it to look like the following code, altering or adding the highlighted lines:

      ~/rails_react_recipe/config/routes.rb

      Rails.application.routes.draw do
        namespace :api do
          namespace :v1 do
            get 'recipes/index'
            post 'recipes/create'
            get '/show/:id', to: 'recipes#show'
            delete '/destroy/:id', to: 'recipes#destroy'
          end
        end
        root 'homepage#index'
        get '/*path' => 'homepage#index'
        # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
      end
      

      In this route file, you modified the HTTP verb of the create and destroy routes so that it can post and delete data. You also modified the routes for the show and destroy action by adding an :id parameter into the route. :id will hold the identification number of the recipe you want to read or delete.

      You also added a catch all route with get '/*path' that will direct any other request that doesn’t match the existing routes to the index action of the homepage controller. This way, the routing on the frontend will handle requests that are not related to creating, reading, or deleting recipes.

      Save and exit the file.

      To see a list of routes available in your application, run the following command in your Terminal window:

      Running this command displays a list of URI patterns, verbs, and matching controllers or actions for your project.

      Next, add the logic for getting all recipes at once. Rails uses the ActiveRecord library to handle database-related tasks like this. ActiveRecord connects classes to relational database tables and provides a rich API for working with them.

      To get all recipes, you'll use ActiveRecord to query the recipes table and fetch all the recipes that exist in the database.

      Open the recipes_controller.rb file with the following command:

      • nano ~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb

      Add the following highlighted lines of code to the recipes controller:

      ~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb

      class Api::V1::RecipesController < ApplicationController
        def index
          recipe = Recipe.all.order(created_at: :desc)
          render json: recipe
        end
      
        def create
        end
      
        def show
        end
      
        def destroy
        end
      end
      

      In your index action, using the all method provided by ActiveRecord, you get all the recipes in your database. Using the order method, you order them in descending order by their created date. This way, you have the newest recipes first. Lastly, you send your list of recipes as a JSON response with render.

      Next, add the logic for creating new recipes. As with fetching all recipes, you'll rely on ActiveRecord to validate and save the provided recipe details. Update your recipe controller with the following highlighted lines of code:

      ~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb

      class Api::V1::RecipesController < ApplicationController
        def index
          recipe = Recipe.all.order(created_at: :desc)
          render json: recipe
        end
      
        def create
          recipe = Recipe.create!(recipe_params)
          if recipe
            render json: recipe
          else
            render json: recipe.errors
          end
        end
      
        def show
        end
      
        def destroy
        end
      
        private
      
        def recipe_params
          params.permit(:name, :image, :ingredients, :instruction)
        end
      end
      

      In the create action, you use ActiveRecord’s create method to create a new recipe. The create method has the ability to assign all controller parameters provided into the model at once. This makes it easy to create records, but also opens the possibility of malicious use. This can be prevented by using a feature provided by Rails known as strong parameters. This way, parameters can’t be assigned unless they’ve been whitelisted. In your code, you passed a recipe_params parameter to the create method. The recipe_params is a private method where you whitelisted your controller parameters to prevent wrong or malicious content from getting into your database. In this case, you are permitting a name, image, ingredients, and instruction parameter for valid use of the create method.

      Your recipe controller can now read and create recipes. All that’s left is the logic for reading and deleting a single recipe. Update your recipes controller with the following code:

      ~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb

      class Api::V1::RecipesController < ApplicationController
        def index
          recipe = Recipe.all.order(created_at: :desc)
          render json: recipe
        end
      
        def create
          recipe = Recipe.create!(recipe_params)
          if recipe
            render json: recipe
          else
            render json: recipe.errors
          end
        end
      
        def show
          if recipe
            render json: recipe
          else
            render json: recipe.errors
          end
        end
      
        def destroy
          recipe&.destroy
          render json: { message: 'Recipe deleted!' }
        end
      
        private
      
        def recipe_params
          params.permit(:name, :image, :ingredients, :instruction)
        end
      
        def recipe
          @recipe ||= Recipe.find(params[:id])
        end
      end
      

      In the new lines of code, you created a private recipe method. The recipe method uses ActiveRecord’s find method to find a recipe whose idmatches the id provided in the params and assigns it to an instance variable @recipe. In the show action, you checked if a recipe is returned by the recipe method and sent it as a JSON response, or sent an error if it was not.

      In the destroy action, you did something similar using Ruby’s safe navigation operator &., which avoids nil errors when calling a method. This let's you delete a recipe only if it exists, then send a message as a response.

      Now that you have finished making these changes to recipes_controller.rb, save the file and exit your text editor.

      In this step, you created a model and controller for your recipes. You’ve written all the logic needed to work with recipes on the backend. In the next section, you'll create components to view your recipes.

      Step 7 — Viewing Recipes

      In this section, you will create components for viewing recipes. First you’ll create a page where you can view all existing recipes, and then another to view individual recipes.

      You’ll start off by creating a page to view all recipes. However, before you can do this, you need recipes to work with, since your database is currently empty. Rails affords us the opportunity to create seed data for your application.

      Open up the seed file seeds.rb to edit:

      • nano ~/rails_react_recipe/db/seeds.rb

      Replace the contents of this seed file with the following code:

      ~/rails_react_recipe/db/seeds.rb

      9.times do |i|
        Recipe.create(
          name: "Recipe #{i + 1}",
          ingredients: '227g tub clotted cream, 25g butter, 1 tsp cornflour,100g parmesan, grated nutmeg, 250g fresh fettuccine or tagliatelle, snipped chives or chopped parsley to serve (optional)',
          instruction: 'In a medium saucepan, stir the clotted cream, butter, and cornflour over a low-ish heat and bring to a low simmer. Turn off the heat and keep warm.'
        )
      end
      

      In this code, you are using a loop to instruct Rails to create nine recipes with a name, ingredients, and instruction. Save and exit the file.

      To seed the database with this data, run the following command in your Terminal window:

      Running this command adds nine recipes to your database. Now you can fetch them and render them on the frontend.

      The component to view all recipes will make a HTTP request to the index action in the RecipesController to get a list of all recipes. These recipes will then be displayed in cards on the page.

      Create a Recipes.jsx file in the app/javascript/components directory:

      • nano ~/rails_react_recipe/app/javascript/components/Recipes.jsx

      Once the file is open, import the React and Link modules into it by adding the following lines:

      ~/rails_react_recipe/app/javascript/components/Recipes.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      

      Next, create a Recipes class that extends the React.Component class. Add the following highlighted code to create a React component that extends React.Component:

      ~/rails_react_recipe/app/javascript/components/Recipes.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipes extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            recipes: []
          };
        }
      
      }
      export default Recipes;
      

      Inside the constructor, we are initializing a state object that holds the state of your recipes, which on initialization is an empty array ([]).

      Next, add a componentDidMount method in the Recipe class. The componentDidMount method is a React lifecycle method that is called immediately after a component is mounted. In this lifecycle method, you will make a call to fetch all your recipes. To do this, add the following lines:

      ~/rails_react_recipe/app/javascript/components/Recipes.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipes extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            recipes: []
          };
        }
      
        componentDidMount() {
            const url = "/api/v1/recipes/index";
            fetch(url)
              .then(response => {
                if (response.ok) {
                  return response.json();
                }
                throw new Error("Network response was not ok.");
              })
              .then(response => this.setState({ recipes: response }))
              .catch(() => this.props.history.push("/"));
        }
      
      }
      export default Recipes;
      

      In your componentDidMount method, you made an HTTP call to fetch all recipes using the Fetch API. If the response is successful, the application saves the array of recipes to the recipe state. If there’s an error, it will redirect the user to the homepage.

      Finally, add a render method in the Recipe class. The render method holds the React elements that will be evaluated and displayed on the browser page when a component is rendered. In this case, the render method will render cards of recipes from the component state. Add the following highlighted lines to Recipes.jsx:

      ~/rails_react_recipe/app/javascript/components/Recipes.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipes extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            recipes: []
          };
        }
      
        componentDidMount() {
          const url = "/api/v1/recipes/index";
          fetch(url)
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.setState({ recipes: response }))
            .catch(() => this.props.history.push("/"));
        }
        render() {
          const { recipes } = this.state;
          const allRecipes = recipes.map((recipe, index) => (
            <div key={index} className="col-md-6 col-lg-4">
              <div className="card mb-4">
                <img
                  src={recipe.image}
                  className="card-img-top"
                  alt={`${recipe.name} image`}
                />
                <div className="card-body">
                  <h5 className="card-title">{recipe.name}</h5>
                  <Link to={`/recipe/${recipe.id}`} className="btn custom-button">
                    View Recipe
                  </Link>
                </div>
              </div>
            </div>
          ));
          const noRecipe = (
            <div className="vw-100 vh-50 d-flex align-items-center justify-content-center">
              <h4>
                No recipes yet. Why not <Link to="/new_recipe">create one</Link>
              </h4>
            </div>
          );
      
          return (
            <>
              <section className="jumbotron jumbotron-fluid text-center">
                <div className="container py-5">
                  <h1 className="display-4">Recipes for every occasion</h1>
                  <p className="lead text-muted">
                    We’ve pulled together our most popular recipes, our latest
                    additions, and our editor’s picks, so there’s sure to be something
                    tempting for you to try.
                  </p>
                </div>
              </section>
              <div className="py-5">
                <main className="container">
                  <div className="text-right mb-3">
                    <Link to="/recipe" className="btn custom-button">
                      Create New Recipe
                    </Link>
                  </div>
                  <div className="row">
                    {recipes.length > 0 ? allRecipes : noRecipe}
                  </div>
                  <Link to="/" className="btn btn-link">
                    Home
                  </Link>
                </main>
              </div>
            </>
          );
        }
      }
      export default Recipes;
      

      Save and exit Recipes.jsx.

      Now that you have created a component to display all the recipes, the next step is to create a route for it. Open the front-end route file located at app/javascript/routes/Index.jsx:

      • nano app/javascript/routes/Index.jsx

      Add the following highlighted lines to the file:

      ~/rails_react_recipe/app/javascript/routes/Index.jsx

      import React from "react";
      import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
      import Home from "../components/Home";
      import Recipes from "../components/Recipes";
      
      export default (
        <Router>
          <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/recipes" exact component={Recipes} />
          </Switch>
        </Router>
      );
      

      Save and exit the file.

      At this point, it's a good idea to verify that your code is working correctly. As you did before, use the following command to start your server:

      • rails s --binding=127.0.0.1

      Go ahead and open the app in your browser. By clicking the View Recipe button on the homepage, you will see a display with your seed recipes:

      Recipes Page

      Use CTRL+C in your Terminal window to stop the server and get your prompt back.

      Now that you can view all the recipes that exist in your application, it's time to create a second component to view individual recipes. Create a Recipe.jsx file in the app/javascript/components directory:

      • nano app/javascript/components/Recipe.jsx

      As with the Recipes component, import the React and Link modules by adding the following lines:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      

      Next create a Recipe class that extends React.Component class by adding the highlighted lines of code:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = { recipe: { ingredients: "" } };
      
          this.addHtmlEntities = this.addHtmlEntities.bind(this);
        }
      }
      
      export default Recipe;
      

      Like with your Recipes component, in the constructor, you initialized a state object that holds the state of a recipe. You also bound an addHtmlEntities method to this so it can be accessible within the component. The addHtmlEntities method will be used to replace character entities with HTML entities in the component.

      In order to find a particular recipe, your application needs the id of the recipe. This means your Recipe component expects an id param. You can access this via the props passed into the component.

      Next, add a componentDidMount method where you will access the id param from the match key of the props object. Once you get the id, you will then make an HTTP request to fetch the recipe. Add the following highlighted lines to your file:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = { recipe: { ingredients: "" } };
      
          this.addHtmlEntities = this.addHtmlEntities.bind(this);
        }
      
        componentDidMount() {
          const {
            match: {
              params: { id }
            }
          } = this.props;
      
          const url = `/api/v1/show/${id}`;
      
          fetch(url)
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.setState({ recipe: response }))
            .catch(() => this.props.history.push("/recipes"));
        }
      
      }
      
      export default Recipe;
      

      In the componentDidMount method, using object destructuring, you get the id param from the props object, then using the Fetch API, you make a HTTP request to fetch the recipe that owns the id and save it to the component state using the setState method. If the recipe does not exist, the app redirects the user to the recipes page.

      Now add the addHtmlEntities method, which takes a string and replaces all escaped opening and closing brackets with their HTML entities. This will help us convert whatever escaped character was saved in your recipe instruction:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = { recipe: { ingredients: "" } };
      
          this.addHtmlEntities = this.addHtmlEntities.bind(this);
        }
      
        componentDidMount() {
          const {
            match: {
              params: { id }
            }
          } = this.props;
      
          const url = `/api/v1/show/${id}`;
      
          fetch(url)
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.setState({ recipe: response }))
            .catch(() => this.props.history.push("/recipes"));
        }
      
        addHtmlEntities(str) {
          return String(str)
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">");
        }
      }
      
      export default Recipe;
      

      Finally, add a render method that gets the recipe from the state and renders it on the page. To do this, add the following highlighted lines:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = { recipe: { ingredients: "" } };
      
          this.addHtmlEntities = this.addHtmlEntities.bind(this);
        }
      
        componentDidMount() {
          const {
            match: {
              params: { id }
            }
          } = this.props;
      
          const url = `/api/v1/show/${id}`;
      
          fetch(url)
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.setState({ recipe: response }))
            .catch(() => this.props.history.push("/recipes"));
        }
      
        addHtmlEntities(str) {
          return String(str)
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">");
        }
      
        render() {
          const { recipe } = this.state;
          let ingredientList = "No ingredients available";
      
          if (recipe.ingredients.length > 0) {
            ingredientList = recipe.ingredients
              .split(",")
              .map((ingredient, index) => (
                <li key={index} className="list-group-item">
                  {ingredient}
                </li>
              ));
          }
          const recipeInstruction = this.addHtmlEntities(recipe.instruction);
      
          return (
            <div className="">
              <div className="hero position-relative d-flex align-items-center justify-content-center">
                <img
                  src={recipe.image}
                  alt={`${recipe.name} image`}
                  className="img-fluid position-absolute"
                />
                <div className="overlay bg-dark position-absolute" />
                <h1 className="display-4 position-relative text-white">
                  {recipe.name}
                </h1>
              </div>
              <div className="container py-5">
                <div className="row">
                  <div className="col-sm-12 col-lg-3">
                    <ul className="list-group">
                      <h5 className="mb-2">Ingredients</h5>
                      {ingredientList}
                    </ul>
                  </div>
                  <div className="col-sm-12 col-lg-7">
                    <h5 className="mb-2">Preparation Instructions</h5>
                    <div
                      dangerouslySetInnerHTML={{
                        __html: `${recipeInstruction}`
                      }}
                    />
                  </div>
                  <div className="col-sm-12 col-lg-2">
                    <button type="button" className="btn btn-danger">
                      Delete Recipe
                    </button>
                  </div>
                </div>
                <Link to="/recipes" className="btn btn-link">
                  Back to recipes
                </Link>
              </div>
            </div>
          );
        }
      
      }
      
      export default Recipe;
      

      In this render method, you split your comma separated ingredients into an array and mapped over it, creating a list of ingredients. If there are no ingredients, the app displays a message that says No ingredients available. It also displays the recipe image as a hero image, adds a delete recipe button next to the recipe instruction, and adds a button that links back to the recipes page.

      Save and exit the file.

      To view the Recipe component on a page, add it to your routes file. Open your route file to edit:

      • nano app/javascript/routes/Index.jsx

      Now, add the following highlighted lines to the file:

      ~/rails_react_recipe/app/javascript/routes/Index.jsx

      import React from "react";
      import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
      import Home from "../components/Home";
      import Recipes from "../components/Recipes";
      import Recipe from "../components/Recipe";
      
      export default (
        <Router>
          <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/recipes" exact component={Recipes} />
            <Route path="/recipe/:id" exact component={Recipe} />
          </Switch>
        </Router>
      );
      

      In this route file, you imported your Recipe component and added a route for it. Its route has an :id param that will be replaced by the id of the recipe you want to view.

      Use the rails s command to start your server again, then visit http://localhost:3000 in your browser. Click the View Recipes button to navigate to the recipes page. On the recipes page, view any recipe by clicking its View Recipe button. You will be greeted with a page populated with the data from your database:

      Single Recipe Page

      In this section, you added nine recipes to your database and created components to view these recipes, both individually and as a collection. In the next section, you will add a component to create recipes.

      Step 8 — Creating Recipes

      The next step to having a usable food recipe application is the ability to create new recipes. In this step, you will create a component for creating recipes. This component will contain a form for collecting the required recipe details from the user and will make a request to the create action in the Recipe controller to save the recipe data.

      Create a NewRecipe.jsx file in the app/javascript/components directory:

      • nano app/javascript/components/NewRecipe.jsx

      In the new file, import the React and Link modules you have used so far in other components:

      ~/rails_react_recipe/app/javascript/components/NewRecipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      

      Next create a NewRecipe class that extends React.Component class. Add the following highlighted code to create a React component that extends react.Component:

      ~/rails_react_recipe/app/javascript/components/NewRecipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class NewRecipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            name: "",
            ingredients: "",
            instruction: ""
          };
      
          this.onChange = this.onChange.bind(this);
          this.onSubmit = this.onSubmit.bind(this);
          this.stripHtmlEntities = this.stripHtmlEntities.bind(this);
        }
      }
      
      export default NewRecipe;
      

      In the NewRecipe component’s constructor, you initialized your state object with empty name, ingredients, and instruction fields. These are the fields you need to create a valid recipe. You also have three methods; onChange, onSubmit, and stripHtmlEntities, which you bound to this. These methods will handle updating the state, form submissions, and converting special characters (like <) into their escaped/encoded values (like &lt;), respectively.

      Next, create the stripHtmlEntities method itself by adding the highlighted lines to the NewRecipe component:

      ~/rails_react_recipe/app/javascript/components/NewRecipe.jsx

      class NewRecipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            name: "",
            ingredients: "",
            instruction: ""
          };
      
          this.onChange = this.onChange.bind(this);
          this.onSubmit = this.onSubmit.bind(this);
          this.stripHtmlEntities = this.stripHtmlEntities.bind(this);
        }
      
        stripHtmlEntities(str) {
          return String(str)
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");
        }
      
      }
      
      export default NewRecipe;
      

      In the stripHtmlEntities method, you’re replacing the < and > characters with their escaped value. This way you’re not storing raw HTML in your database.

      Next add the onChange and onSubmit methods to the NewRecipe component to handle editing and submission of the form:

      ~/rails_react_recipe/app/javascript/components/NewRecipe.jsx

      class NewRecipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            name: "",
            ingredients: "",
            instruction: ""
          };
      
          this.onChange = this.onChange.bind(this);
          this.onSubmit = this.onSubmit.bind(this);
          this.stripHtmlEntities = this.stripHtmlEntities.bind(this);
        }
      
        stripHtmlEntities(str) {
          return String(str)
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");
        }
      
        onChange(event) {
          this.setState({ [event.target.name]: event.target.value });
        }
      
        onSubmit(event) {
          event.preventDefault();
          const url = "/api/v1/recipes/create";
          const { name, ingredients, instruction } = this.state;
      
          if (name.length == 0 || ingredients.length == 0 || instruction.length == 0)
            return;
      
          const body = {
            name,
            ingredients,
            instruction: instruction.replace(/n/g, "<br> <br>")
          };
      
          const token = document.querySelector('meta[name="csrf-token"]').content;
          fetch(url, {
            method: "POST",
            headers: {
              "X-CSRF-Token": token,
              "Content-Type": "application/json"
            },
            body: JSON.stringify(body)
          })
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.props.history.push(`/recipe/${response.id}`))
            .catch(error => console.log(error.message));
        }
      
      }
      
      export default NewRecipe;
      

      In the onChange method, you used the ES6 computed property names to set the value of every user input to its corresponding key in your state. In the onSubmit method, you checked that none of the required inputs are empty. You then build an object that contains the parameters required by the recipe controller to create a new recipe. Using regular expression, you replace every new line character in the instruction with a break tag, so you can retain the text format entered by the user.

      To protect against Cross-Site Request Forgery (CSRF) attacks, Rails attaches a CSRF security token to the HTML document. This token is required whenever a non-GET request is made. With the token constant in the preceding code, your application verifies the token on the server and throws an exception if the security token doesn't match what is expected. In the onSubmit method, the application retrieves the CSRF token embedded in your HTML document by Rails and makes a HTTP request with a JSON string. If the recipe is successfully created, the application redirects the user to the recipe page where they can view their newly created recipe.

      Lastly, add a render method that renders a form for the user to enter the details for the recipe the user wishes to create:

      ~/rails_react_recipe/app/javascript/components/NewRecipe.jsx

      class NewRecipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            name: "",
            ingredients: "",
            instruction: ""
          };
      
          this.onChange = this.onChange.bind(this);
          this.onSubmit = this.onSubmit.bind(this);
          this.stripHtmlEntities = this.stripHtmlEntities.bind(this);
        }
      
        stripHtmlEntities(str) {
          return String(str)
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");
        }
      
        onChange(event) {
          this.setState({ [event.target.name]: event.target.value });
        }
      
        onSubmit(event) {
          event.preventDefault();
          const url = "/api/v1/recipes/create";
          const { name, ingredients, instruction } = this.state;
      
          if (name.length == 0 || ingredients.length == 0 || instruction.length == 0)
            return;
      
          const body = {
            name,
            ingredients,
            instruction: instruction.replace(/n/g, "<br> <br>")
          };
      
          const token = document.querySelector('meta[name="csrf-token"]').content;
          fetch(url, {
            method: "POST",
            headers: {
              "X-CSRF-Token": token,
              "Content-Type": "application/json"
            },
            body: JSON.stringify(body)
          })
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.props.history.push(`/recipe/${response.id}`))
            .catch(error => console.log(error.message));
        }
      
        render() {
          return (
            <div className="container mt-5">
              <div className="row">
                <div className="col-sm-12 col-lg-6 offset-lg-3">
                  <h1 className="font-weight-normal mb-5">
                    Add a new recipe to our awesome recipe collection.
                  </h1>
                  <form onSubmit={this.onSubmit}>
                    <div className="form-group">
                      <label htmlFor="recipeName">Recipe name</label>
                      <input
                        type="text"
                        name="name"
                        id="recipeName"
                        className="form-control"
                        required
                        onChange={this.onChange}
                      />
                    </div>
                    <div className="form-group">
                      <label htmlFor="recipeIngredients">Ingredients</label>
                      <input
                        type="text"
                        name="ingredients"
                        id="recipeIngredients"
                        className="form-control"
                        required
                        onChange={this.onChange}
                      />
                      <small id="ingredientsHelp" className="form-text text-muted">
                        Separate each ingredient with a comma.
                      </small>
                    </div>
                    <label htmlFor="instruction">Preparation Instructions</label>
                    <textarea
                      className="form-control"
                      id="instruction"
                      name="instruction"
                      rows="5"
                      required
                      onChange={this.onChange}
                    />
                    <button type="submit" className="btn custom-button mt-3">
                      Create Recipe
                    </button>
                    <Link to="/recipes" className="btn btn-link mt-3">
                      Back to recipes
                    </Link>
                  </form>
                </div>
              </div>
            </div>
          );
        }
      
      }
      
      export default NewRecipe;
      

      In the render method, you have a form that contains three input fields; one for the recipeName, recipeIngredients, and instruction. Each input field has an onChange event handler that calls the onChange method. Also, there's an onSubmit event handler on the submit button that calls the onSubmit method which then submits the form data.

      Save and exit the file.

      To access this component in the browser, update your route file with its route:

      • nano app/javascript/routes/Index.jsx

      Update your route file to include these highlighted lines:

      ~/rails_react_recipe/app/javascript/routes/Index.jsx

      import React from "react";
      import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
      import Home from "../components/Home";
      import Recipes from "../components/Recipes";
      import Recipe from "../components/Recipe";
      import NewRecipe from "../components/NewRecipe";
      
      export default (
        <Router>
          <Switch>
            <Route path="/" exact component={Home} />
            <Route path="/recipes" exact component={Recipes} />
            <Route path="/recipe/:id" exact component={Recipe} />
            <Route path="/recipe" exact component={NewRecipe} />
          </Switch>
        </Router>
      );
      

      With the route in place, save and exit your file. Restart your development server and visit http://localhost:3000 in your browser. Navigate to the recipes page and click the Create New Recipe button. You will find a page with a form to add recipes to your database:

      Create Recipe Page

      Enter the required recipe details and click the Create Recipe button; you will see the newly created recipe on the page.

      In this step, you brought your food recipe application to life by adding the ability to create recipes. In the next step, you’ll add the functionality to delete recipes.

      Step 9 — Deleting Recipes

      In this section, you will modify your Recipe component to be able to delete recipes.

      When you click the delete button on the recipe page, the application will send a request to delete a recipe from the database. To do this, open up your Recipe.jsx file:

      • nano app/javascript/components/Recipe.jsx

      In the constructor of the Recipe component, bind this to the deleteRecipe method:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      class Recipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = { recipe: { ingredients: "" } };
          this.addHtmlEntities = this.addHtmlEntities.bind(this);
          this.deleteRecipe = this.deleteRecipe.bind(this);
        }
      ...
      

      Now add a deleteRecipe method to the Recipe component:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      class Recipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = { recipe: { ingredients: "" } };
      
          this.addHtmlEntities = this.addHtmlEntities.bind(this);
          this.deleteRecipe = this.deleteRecipe.bind(this);
        }
      
        componentDidMount() {
          const {
            match: {
              params: { id }
            }
          } = this.props;
          const url = `/api/v1/show/${id}`;
          fetch(url)
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.setState({ recipe: response }))
            .catch(() => this.props.history.push("/recipes"));
        }
      
        addHtmlEntities(str) {
          return String(str)
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">");
        }
      
        deleteRecipe() {
          const {
            match: {
              params: { id }
            }
          } = this.props;
          const url = `/api/v1/destroy/${id}`;
          const token = document.querySelector('meta[name="csrf-token"]').content;
      
          fetch(url, {
            method: "DELETE",
            headers: {
              "X-CSRF-Token": token,
              "Content-Type": "application/json"
            }
          })
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(() => this.props.history.push("/recipes"))
            .catch(error => console.log(error.message));
        }
      
        render() {
          const { recipe } = this.state;
          let ingredientList = "No ingredients available";
      ... 
      

      In the deleteRecipe method, you get the id of the recipe to be deleted, then build your url and grab the CSRF token. Next, you make a DELETE request to the Recipes controller to delete the recipe. If the recipe is successfully deleted, the application redirects the user to the recipes page.

      To run the code in the deleteRecipe method whenever the delete button is clicked, pass it as the click event handler to the button. Add an onClick event to the delete button in the render method:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      ...
      return (
        <div className="">
          <div className="hero position-relative d-flex align-items-center justify-content-center">
            <img
              src={recipe.image}
              alt={`${recipe.name} image`}
              className="img-fluid position-absolute"
            />
            <div className="overlay bg-dark position-absolute" />
            <h1 className="display-4 position-relative text-white">
              {recipe.name}
            </h1>
          </div>
          <div className="container py-5">
            <div className="row">
              <div className="col-sm-12 col-lg-3">
                <ul className="list-group">
                  <h5 className="mb-2">Ingredients</h5>
                  {ingredientList}
                </ul>
              </div>
              <div className="col-sm-12 col-lg-7">
                <h5 className="mb-2">Preparation Instructions</h5>
                <div
                  dangerouslySetInnerHTML={{
                    __html: `${recipeInstruction}`
                  }}
                />
              </div>
              <div className="col-sm-12 col-lg-2">
                <button type="button" className="btn btn-danger" onClick={this.deleteRecipe}>
                  Delete Recipe
                </button>
              </div>
            </div>
            <Link to="/recipes" className="btn btn-link">
              Back to recipes
            </Link>
          </div>
        </div>
      );
      ...
      

      At this point in the tutorial, your complete Recipe.jsx file will look like this:

      ~/rails_react_recipe/app/javascript/components/Recipe.jsx

      import React from "react";
      import { Link } from "react-router-dom";
      
      class Recipe extends React.Component {
        constructor(props) {
          super(props);
          this.state = { recipe: { ingredients: "" } };
      
          this.addHtmlEntities = this.addHtmlEntities.bind(this);
          this.deleteRecipe = this.deleteRecipe.bind(this);
        }
      
        addHtmlEntities(str) {
          return String(str)
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">");
        }
      
        componentDidMount() {
          const {
            match: {
              params: { id }
            }
          } = this.props;
          const url = `/api/v1/show/${id}`;
          fetch(url)
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(response => this.setState({ recipe: response }))
            .catch(() => this.props.history.push("/recipes"));
        }
      
        deleteRecipe() {
          const {
            match: {
              params: { id }
            }
          } = this.props;
          const url = `/api/v1/destroy/${id}`;
          const token = document.querySelector('meta[name="csrf-token"]').content;
          fetch(url, {
            method: "DELETE",
            headers: {
              "X-CSRF-Token": token,
              "Content-Type": "application/json"
            }
          })
            .then(response => {
              if (response.ok) {
                return response.json();
              }
              throw new Error("Network response was not ok.");
            })
            .then(() => this.props.history.push("/recipes"))
            .catch(error => console.log(error.message));
        }
      
        render() {
          const { recipe } = this.state;
          let ingredientList = "No ingredients available";
          if (recipe.ingredients.length > 0) {
            ingredientList = recipe.ingredients
              .split(",")
              .map((ingredient, index) => (
                <li key={index} className="list-group-item">
                  {ingredient}
                </li>
              ));
          }
      
          const recipeInstruction = this.addHtmlEntities(recipe.instruction);
      
          return (
            <div className="">
              <div className="hero position-relative d-flex align-items-center justify-content-center">
                <img
                  src={recipe.image}
                  alt={`${recipe.name} image`}
                  className="img-fluid position-absolute"
                />
                <div className="overlay bg-dark position-absolute" />
                <h1 className="display-4 position-relative text-white">
                  {recipe.name}
                </h1>
              </div>
              <div className="container py-5">
                <div className="row">
                  <div className="col-sm-12 col-lg-3">
                    <ul className="list-group">
                      <h5 className="mb-2">Ingredients</h5>
                      {ingredientList}
                    </ul>
                  </div>
                  <div className="col-sm-12 col-lg-7">
                    <h5 className="mb-2">Preparation Instructions</h5>
                    <div
                      dangerouslySetInnerHTML={{
                        __html: `${recipeInstruction}`
                      }}
                    />
                  </div>
                  <div className="col-sm-12 col-lg-2">
                    <button type="button" className="btn btn-danger" onClick={this.deleteRecipe}>
                      Delete Recipe
                    </button>
                  </div>
                </div>
                <Link to="/recipes" className="btn btn-link">
                  Back to recipes
                </Link>
              </div>
            </div>
          );
        }
      }
      
      export default Recipe;
      

      Save and exit the file.

      Restart the application server and navigate to the homepage. Click the View Recipes button to view all existing recipes, view any individual recipe, and click the Delete Recipe button on the page to delete the article. You will be redirected to the recipes page, and the deleted recipe will no longer exists.

      With the delete button working, you now have a fully functional recipe application!

      Conclusion

      In this tutorial, you created a food recipe application with Ruby on Rails and a React frontend, using PostgreSQL as your database and Bootstrap for styling. If you'd like to run through more Ruby on Rails content, take a look at our Securing Communications in a Three-tier Rails Application Using SSH Tunnels tutorial, or head to our How To Code in Ruby series to refresh your Ruby skills. To dive deeper into React, try out our How To Display Data from the DigitalOcean API with React article.



      Source link

      How To Build a Customer List Management App with React and TypeScript


      The author selected the Tech Education Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      TypeScript has brought a lot of improvement into how JavaScript developers structure and write code for apps, especially web applications. Defined as a superset of JavaScript, TypeScript behaves identically to JavaScript but with extra features designed to help developers build larger and more complex programs with fewer or no bugs. TypeScript is increasingly gaining popularity; adopted by major companies like Google for the Angular web framework. The Nest.js back-end framework was also built with TypeScript.

      One of the ways to improve productivity as a developer is the ability to implement new features as quickly as possible without any concern over breaking the existing app in production. To achieve this, writing statically typed code is a style adopted by many seasoned developers. Statically typed programming languages like TypeScript enforce an association for every variable with a data type; such as a string, integer, boolean, and so on. One of the major benefits of using a statically typed programming language is that type checking is completed at compile time, therefore developers can see errors in their code at a very early stage.

      React is an open-source JavaScript library, which developers use to create high-end user interfaces for scalable web applications. The great performance and dynamic user interfaces built with React for single-page applications make it a popular choice among developers.

      In this tutorial, you will create a customer list management application with a separate REST API backend and a frontend built with React and TypeScript. You will build the backend using a fake REST API named json-server. You’ll use it to quickly set up a CRUD (Create, Read, Update, and Delete) backend. Consequently you can focus on handling the front-end logic of an application using React and TypeScript.

      Prerequisites

      To complete this tutorial, you will need:

      Step 1 — Installing TypeScript and Creating the React Application

      In this step, you will install the TypeScript package globally on your machine by using the Node Package Manager (npm). After that, you will also install React and its dependencies, and check that your React app is working by running the development server.

      To begin, open a terminal and run the following command to install TypeScript:

      • npm install -g typescript

      Once the installation process is complete, execute the following command to check your installation of TypeScript:

      You will see the current version installed on your machine:

      Output

      Version 3.4.5

      Next, you will install the React application by using the create-react-app tool to set up the application with a single command. You'll use the npx command, which is a package runner tool that comes with npm 5.2+. The create-react-app tool has built-in support for working with TypeScript without any extra configuration required. Run the following command to create and install a new React application named typescript-react-app:

      • npx create-react-app typescript-react-app --typescript

      The preceding command will create a new React application with the name typescript-react-app. The --typescript flag will set the default filetype for React components to .tsx.

      Before you complete this section, the application will require moving from one port to another. To do that, you will need to install a routing library for your React application named React Router and its corresponding TypeScript definitions. You will use yarn to install the library and other packages for this project. This is because yarn is faster, especially for installing dependencies for a React application. Move into the newly created project folder and then install React Router with the following command:

      • cd typescript-react-app
      • yarn add react-router-dom

      You now have the React Router package, which will provide the routing functionality within your project. Next, run the following command to install the TypeScript definitions for React Router:

      • yarn add @types/react-router-dom

      Now you'll install axios, which is a promised-based HTTP client for browsers, to help with the process of performing HTTP requests from the different components that you will create within the application:

      Once the installation process is complete, start the development server with:

      Your application will be running on http://localhost:3000.

      React application homepage

      You have successfully installed TypeScript, created a new React application, and installed React Router in order to help with navigating from one page of the application to another. In the next section, you will set up the back-end server for the application.

      Step 2 — Creating a JSON Server

      In this step, you'll create a mock server that your React application can quickly connect with, as well as use its resources. It is important to note that this back-end service is not suitable for an application in production. You can use Nest.js, Express, or any other back-end technology to build a RESTful API in production. json-server is a useful tool whenever you need to create a prototype and mock a back-end server.

      You can use either npm or yarn to install json-server on your machine. This will make it available from any directory of your project whenever you might need to make use of it. Open a new terminal window and run this command to install json-server while you are still within the project directory:

      • yarn global add json-server

      Next, you will create a JSON file that will contain the data that will be exposed by the REST API. For the objects specified in this file (which you'll create), a CRUD endpoint will be generated automatically. To begin, create a new folder named server and then move into it:

      Now, use nano to create and open a new file named db.json:

      Add the following content to the file:

      /server/db.json

      {
          "customers": [
              {
                  "id": 1,
                  "first_name": "Customer_1",
                  "last_name": "Customer_11",
                  "email": "customer1@mail.com",
                  "phone": "00000000000",
                  "address": "Customer_1 Address",
                  "description": "Customer_1 description"
              },
              {
                  "id": 2,
                  "first_name": "Customer_2",
                  "last_name": "Customer_2",
                  "email": "customer2@mail.com",
                  "phone": "00000000000",
                  "address": "Customer_2 Adress",
                  "description": "Customer_2 Description"
              }
          ]
      }
      

      The JSON structure consists of a customer object, which has two datasets assigned. Each customer consists of seven properties: id, description, first_name, last_name, email, phone, and address.

      Save and exit the file.

      By default, the json-server runs on port 3000—this is the same port on which your React application runs. To avoid conflict, you can change the default port for the json-server. To do that, move to the root directory of the application:

      • cd ~/typescript-react-app

      Open the application with your preferred text editor and create a new file named json-server.json:

      Now insert the following to update the port number:

      /json-server.json

      {
          "port": 5000
      }
      

      This will act as the configuration file for the json-server and it will ensure that the server runs on the port specified in it at all times.

      Save and exit the file.

      To run the server, use the following command:

      • json-server --watch server/db.json

      This will start the json-server on port 5000. If you navigate to http://localhost:5000/customers in your browser, you will see the server showing your customer list.

      Customer list shown by json-server

      To streamline the process of running the json-server, you can update package.json with a new property named server to the scripts object as shown here:

      /package.json

      {
      ...
        "scripts": {
          "start": "react-scripts start",
          "build": "react-scripts build",
          "test": "react-scripts test",
          "eject": "react-scripts eject",
          "server": "json-server --watch server/db.json"
        },
      ...
      }
      

      Save and exit the file.

      Now anytime you wish to start the json-server, all you have to do is run yarn server from the terminal.

      You've created a simple REST API that you will use as the back-end server for this application. You also created a customer JSON object that will be used as the default data for the REST API. Lastly, you configured an alternative port for the back-end server powered by json-server. Next, you will build reusable components for your application.

      Step 3 — Creating Reusable Components

      In this section, you will create the required React components for the application. This will include components to create, display, and edit the details of a particular customer in the database respectively. You'll also build some of the TypeScript interfaces for your application.

      To begin, move back to the terminal where you have the React application running and stop the development server with CTRL + C. Next, navigate to the ./src/ folder:

      Then, create a new folder named components inside of it and move into the new folder:

      • mkdir components
      • cd components

      Within the newly created folder, create a customer folder and then move into it:

      • mkdir customer
      • cd customer

      Now create two new files named Create.tsx and Edit.tsx:

      • touch Create.tsx Edit.tsx

      These files are React reusable components that will render the forms and hold all the business logic for creating and editing the details of a customer respectively.

      Open the Create.tsx file in your text editor and add the following code:

      /src/components/customer/Create.tsx

      import * as React from 'react';
      import axios from 'axios';
      import { RouteComponentProps, withRouter } from 'react-router-dom';
      
      export interface IValues {
          first_name: string,
          last_name: string,
          email: string,
          phone: string,
          address: string,
          description: string,
      }
      export interface IFormState {
          [key: string]: any;
          values: IValues[];
          submitSuccess: boolean;
          loading: boolean;
      }
      
      

      Here you've imported React, axios, and other required components necessary for routing from the React Router package. After that you created two new interfaces named IValues and IFormState. TypeScript interfaces help to define the specific type of values that should be passed to an object and enforce consistency throughout an application. This ensures that bugs are less likely to appear in your program.

      Next, you will build a Create component that extends React.Component. Add the following code to the Create.tsx file immediately after the IFormState interface:

      /src/components/customer/Create.tsx

      ...
      class Create extends React.Component<RouteComponentProps, IFormState> {
          constructor(props: RouteComponentProps) {
              super(props);
              this.state = {
                  first_name: '',
                  last_name: '',
                  email: '',
                  phone: '',
                  address: '',
                  description: '',
                  values: [],
                  loading: false,
                  submitSuccess: false,
              }
          }
      }
      export default withRouter(Create)
      

      Here you've defined a React component in Typescript. In this case, the Create class component accepts props (short for “properties”) of type RouteComponentProps and uses a state of type IFormState. Then, inside the constructor, you initialized the state object and defined all the variables that will represent the rendered values for a customer.

      Next, add these methods within the Create class component, just after the constructor. You'll use these methods to process customer forms and handle all changes in the input fields:

      /src/components/customer/Create.tsx

      ...
                values: [],
                loading: false,
                submitSuccess: false,
            }
        }
      
        private processFormSubmission = (e: React.FormEvent<HTMLFormElement>): void => {
                e.preventDefault();
                this.setState({ loading: true });
                const formData = {
                    first_name: this.state.first_name,
                    last_name: this.state.last_name,
                    email: this.state.email,
                    phone: this.state.phone,
                    address: this.state.address,
                    description: this.state.description,
                }
                this.setState({ submitSuccess: true, values: [...this.state.values, formData], loading: false });
                axios.post(`http://localhost:5000/customers`, formData).then(data => [
                    setTimeout(() => {
                        this.props.history.push('/');
                    }, 1500)
                ]);
            }
      
            private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {
                e.preventDefault();
                this.setState({
                    [e.currentTarget.name]: e.currentTarget.value,
            })
        }
      
      ...
      export default withRouter(Create)
      ...
      

      The processFormSubmission() method receives the details of the customer from the application state and posts it to the database using axios. The handleInputChanges() uses React.FormEvent to obtain the values of all input fields and calls this.setState() to update the state of the application.

      Next, add the render() method within the Create class component immediately after the handleInputchanges() method. This render() method will display the form to create a new customer in the application:

      /src/components/customer/Create.tsx

      ...
        public render() {
            const { submitSuccess, loading } = this.state;
            return (
                <div>
                    <div className={"col-md-12 form-wrapper"}>
                        <h2> Create Post </h2>
                        {!submitSuccess && (
                            <div className="alert alert-info" role="alert">
                                Fill the form below to create a new post
                        </div>
                        )}
                        {submitSuccess && (
                            <div className="alert alert-info" role="alert">
                                The form was successfully submitted!
                                </div>
                        )}
                        <form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
                            <div className="form-group col-md-12">
                                <label htmlFor="first_name"> First Name </label>
                                <input type="text" id="first_name" onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="last_name"> Last Name </label>
                                <input type="text" id="last_name" onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="email"> Email </label>
                                <input type="email" id="email" onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="phone"> Phone </label>
                                <input type="text" id="phone" onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="address"> Address </label>
                                <input type="text" id="address" onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="description"> Description </label>
                                <input type="text" id="description" onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
                            </div>
                            <div className="form-group col-md-4 pull-right">
                                <button className="btn btn-success" type="submit">
                                    Create Customer
                                </button>
                                {loading &&
                                    <span className="fa fa-circle-o-notch fa-spin" />
                                }
                            </div>
                        </form>
                    </div>
                </div>
            )
        }
      ...
      

      Here, you created a form with the input fields to hold the values of the first_name, last_name, email, phone, address, and description of a customer. Each of the input fields have a method handleInputChanges() that runs on every keystroke, updating the React state with the value it obtains from the input field. Furthermore, depending on the state of the application, a boolean variable named submitSuccess will control the message that the application will display before and after creating a new customer.

      You can see the complete code for this file in this GitHub repository.

      Save and exit Create.tsx.

      Now that you have added the appropriate logic to the Create component file for the application, you'll proceed to add contents for the Edit component file.

      Open your Edit.tsx file within the customer folder, and start by adding the following content to import React, axios, and also define TypeScript interfaces:

      /src/components/customer/Edit.tsx

      import * as React from 'react';
      import { RouteComponentProps, withRouter } from 'react-router-dom';
      import axios from 'axios';
      
      export interface IValues {
          [key: string]: any;
      }
      export interface IFormState {
          id: number,
          customer: any;
          values: IValues[];
          submitSuccess: boolean;
          loading: boolean;
      }
      

      Similarly to the Create component, you import the required modules and create IValues and IFormState interfaces respectively. The IValues interface defines the data type for the input fields' values, while you'll use IFormState to declare the expected type for the state object of the application.

      Next, create the EditCustomer class component directly after the IFormState interface block as shown here:

      /src/components/customer/Edit.tsx

      ...
      class EditCustomer extends React.Component<RouteComponentProps<any>, IFormState> {
          constructor(props: RouteComponentProps) {
              super(props);
              this.state = {
                  id: this.props.match.params.id,
                  customer: {},
                  values: [],
                  loading: false,
                  submitSuccess: false,
              }
          }
      }
      export default withRouter(EditCustomer)
      

      This component takes the RouteComponentProps<any> and an interface of IFormState as a parameter. You use the addition of <any> to the RouteComponentProps because whenever React Router parses path parameters, it doesn’t do any type conversion to ascertain whether the type of the data is number or string. Since you're expecting a parameter for uniqueId of a customer, it is safer to use any.

      Now add the following methods within the component:

      /src/components/customer/Edit.tsx

      ...
          public componentDidMount(): void {
              axios.get(`http://localhost:5000/customers/${this.state.id}`).then(data => {
                  this.setState({ customer: data.data });
              })
          }
      
          private processFormSubmission = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
              e.preventDefault();
              this.setState({ loading: true });
              axios.patch(`http://localhost:5000/customers/${this.state.id}`, this.state.values).then(data => {
                  this.setState({ submitSuccess: true, loading: false })
                  setTimeout(() => {
                      this.props.history.push('/');
                  }, 1500)
              })
          }
      
          private setValues = (values: IValues) => {
              this.setState({ values: { ...this.state.values, ...values } });
          }
          private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {
              e.preventDefault();
              this.setValues({ [e.currentTarget.id]: e.currentTarget.value })
          }
      ...
      }
      
      export default withRouter(EditCustomer)
      

      First, you add a componentDidMount() method, which is a lifecycle method that is being called when the component is created. The method takes the id obtained from the route parameter to identify a particular customer as a parameter, uses it to retrieve their details from the database and then populates the form with it. Furthermore, you add methods to process form submission and handle changes made to the values of the input fields.

      Lastly, add the render() method for the Edit component:

      /src/components/customer/Edit.tsx

      ...
          public render() {
              const { submitSuccess, loading } = this.state;
              return (
                  <div className="App">
                      {this.state.customer &&
                          <div>
                              < h1 > Customer List Management App</h1>
                              <p> Built with React.js and TypeScript </p>
      
                              <div>
                                  <div className={"col-md-12 form-wrapper"}>
                                      <h2> Edit Customer </h2>
                                      {submitSuccess && (
                                          <div className="alert alert-info" role="alert">
                                              Customer's details has been edited successfully </div>
                                      )}
                                      <form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="first_name"> First Name </label>
                                              <input type="text" id="first_name" defaultValue={this.state.customer.first_name} onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="last_name"> Last Name </label>
                                              <input type="text" id="last_name" defaultValue={this.state.customer.last_name} onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="email"> Email </label>
                                              <input type="email" id="email" defaultValue={this.state.customer.email} onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="phone"> Phone </label>
                                              <input type="text" id="phone" defaultValue={this.state.customer.phone} onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="address"> Address </label>
                                              <input type="text" id="address" defaultValue={this.state.customer.address} onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="description"> Description </label>
                                              <input type="text" id="description" defaultValue={this.state.customer.description} onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
                                          </div>
                                          <div className="form-group col-md-4 pull-right">
                                              <button className="btn btn-success" type="submit">
                                                  Edit Customer </button>
                                              {loading &&
                                                  <span className="fa fa-circle-o-notch fa-spin" />
                                              }
                                          </div>
                                      </form>
                                  </div>
                              </div>
                          </div>
                      }
                  </div>
              )
          }
      ...    
      

      Here, you created a form to edit the details of a particular customer, and then populated the input fields within that form with the customer's details that your application's state obtained. Similarly to the Create component, changes made to all the input fields will be handled by the handleInputChanges() method.

      You can see the complete code for this file in this GitHub repository.

      Save and exit Edit.tsx.

      To view the complete list of customers created within the application, you’ll create a new component within the ./src/components folder and name it Home.tsx:

      • cd ./src/components
      • nano Home.tsx

      Add the following content:

      /src/components/Home.tsx

      import * as React from 'react';
      import { Link, RouteComponentProps } from 'react-router-dom';
      import axios from 'axios';
      
      interface IState {
          customers: any[];
      }
      
      export default class Home extends React.Component<RouteComponentProps, IState> {
          constructor(props: RouteComponentProps) {
              super(props);
              this.state = { customers: [] }
          }
          public componentDidMount(): void {
              axios.get(`http://localhost:5000/customers`).then(data => {
                  this.setState({ customers: data.data })
              })
          }
          public deleteCustomer(id: number) {
              axios.delete(`http://localhost:5000/customers/${id}`).then(data => {
                  const index = this.state.customers.findIndex(customer => customer.id === id);
                  this.state.customers.splice(index, 1);
                  this.props.history.push('/');
              })
          }
      }
      

      Here, you've imported React, axios, and other required components from React Router. You created two new methods within the Home component:

      • componentDidMount(): The application invokes this method immediately after a component is mounted. Its responsibility here is to retrieve the list of customers and update the home page with it.
      • deleteCustomer(): This method will accept an id as a parameter and will delete the details of the customer identified with that id from the database.

      Now add the render() method to display the table that holds the list of customers for the Home component:

      /src/components/Home.tsx

      ...
      public render() {
              const customers = this.state.customers;
              return (
                  <div>
                      {customers.length === 0 && (
                          <div className="text-center">
                              <h2>No customer found at the moment</h2>
                          </div>
                      )}
                      <div className="container">
                          <div className="row">
                              <table className="table table-bordered">
                                  <thead className="thead-light">
                                      <tr>
                                          <th scope="col">Firstname</th>
                                          <th scope="col">Lastname</th>
                                          <th scope="col">Email</th>
                                          <th scope="col">Phone</th>
                                          <th scope="col">Address</th>
                                          <th scope="col">Description</th>
                                          <th scope="col">Actions</th>
                                      </tr>
                                  </thead>
                                  <tbody>
                                      {customers && customers.map(customer =>
                                          <tr key={customer.id}>
                                              <td>{customer.first_name}</td>
                                              <td>{customer.last_name}</td>
                                              <td>{customer.email}</td>
                                              <td>{customer.phone}</td>
                                              <td>{customer.address}</td>
                                              <td>{customer.description}</td>
                                              <td>
                                                  <div className="d-flex justify-content-between align-items-center">
                                                      <div className="btn-group" style={{ marginBottom: "20px" }}>
                                                          <Link to={`edit/${customer.id}`} className="btn btn-sm btn-outline-secondary">Edit Customer </Link>
                                                          <button className="btn btn-sm btn-outline-secondary" onClick={() => this.deleteCustomer(customer.id)}>Delete Customer</button>
                                                      </div>
                                                  </div>
                                              </td>
                                          </tr>
                                      )}
                                  </tbody>
                              </table>
                          </div>
                      </div>
                  </div>
              )
          }
      ...
      

      In this code block, you retrieve the lists of customers from the application's state as an array, iterate over it, and display it within an HTML table. You also add the customer.id parameter, which the method uses to identify and delete the details of a particular customer from the list.

      Save and exit Home.tsx.

      You've adopted a statically typed principle for all the components created with this application by defining types for the components and props through the use of interfaces. This is one of the best approaches to using TypeScript for a React application.

      With this, you've finished creating all the required reusable components for the application. You can now update the app component with links to all the components that you have created so far.

      Step 4 — Setting Up Routing and Updating the Entry Point of the Application

      In this step, you will import the necessary components from the React Router package and configure the App component to render different components depending on the route that is loaded. This will allow you to navigate through different pages of the application. Once a user visits a route, for example /create, React Router will use the path specified to render the contents and logic within the appropriate component defined to handle such route.

      Navigate to ./src/App.tsx:

      Then replace its content with the following:

      /src/App.tsx

      import * as React from 'react';
      import './App.css';
      import { Switch, Route, withRouter, RouteComponentProps, Link } from 'react-router-dom';
      import Home from './components/Home';
      import Create from './components/customer/Create';
      import EditCustomer from './components/customer/Edit';
      
      class App extends React.Component<RouteComponentProps<any>> {
        public render() {
          return (
            <div>
              <nav>
                <ul>
                  <li>
                    <Link to={'/'}> Home </Link>
                  </li>
                  <li>
                    <Link to={'/create'}> Create Customer </Link>
                  </li>
                </ul>
              </nav>
              <Switch>
                <Route path={'/'} exact component={Home} />
                <Route path={'/create'} exact component={Create} />
                <Route path={'/edit/:id'} exact component={EditCustomer} />
              </Switch>
            </div>
          );
        }
      }
      export default withRouter(App);
      

      You imported all the necessary components from the React Router package and you also imported the reusable components for creating, editing, and viewing customers' details.

      Save and exit App.tsx.

      The ./src/index.tsx file is the entry point for this application and renders the application. Open this file and import React Router into it, then wrap the App component inside a BrowserRouter:

      /src/index.tsx

      import React from 'react';
      import ReactDOM from 'react-dom';
      import './index.css';
      import App from './App';
      import { BrowserRouter } from 'react-router-dom'; 
      import * as serviceWorker from './serviceWorker';
      ReactDOM.render(
          <BrowserRouter>
              <App />
          </BrowserRouter>
          , document.getElementById('root')
      );
      serviceWorker.unregister();
      

      React Router uses the BrowserRouter component to make your application aware of the navigation, such as history and current path.

      Once you've finished editing Index.tsx, save and exit.

      Lastly, you will use Bootstrap to add some style to your application. Bootstrap is a popular HTML, CSS, and JavaScript framework for developing responsive, mobile-first projects on the web. It allows developers to build an appealing user interface without having to write too much CSS. It comes with a responsive grid system that gives a web page a finished look that works on all devices.

      To include Bootstrap and styling for your application, replace the contents of ./src/App.css with the following:

      /src/App.css

      @import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
      
      .form-wrapper {
        width: 500px;
        margin: 0 auto;
      }
      .App {
        text-align: center;
        margin-top: 30px;
      }
      nav {
        width: 300px;
        margin: 0 auto;
        background: #282c34;
        height: 70px;
        line-height: 70px;
      }
      nav ul li {
        display: inline;
        list-style-type: none;
        text-align: center;
        padding: 30px;
      }
      nav ul li a {
        margin: 50px 0;
        font-weight: bold;
        color: white;
        text-decoration: none;
      }
      nav ul li a:hover {
        color: white;
        text-decoration: none;
      }
      table {
        margin-top: 50px;
      }
      .App-link {
        color: #61dafb;
      }
      @keyframes App-logo-spin {
        from {
          transform: rotate(0deg);
        }
        to {
          transform: rotate(360deg);
        }
      }
      

      You have used Bootstrap here to enhance the look and feel of the application by giving it a default layout, styles, and color. You have also added some custom styles, particularly to the navigation bar.

      Save and exit App.css.

      In this section, you have configured React Router to render the appropriate component depending on the route visited by the user and also added some styling to make the application more attractive to users. Next, you will test all the functionality implemented for the application.

      Step 5 — Running Your Application

      Now that you have set up the frontend of this application with React and TypeScript by creating several reusable components, and also built a REST API with the json-server, you can run your app.

      Navigate back to the project’s root folder:

      • cd ~/typescript-react-app

      Next run the following command to start your app:

      Note: Make sure your server is still running in the other terminal window. Otherwise, start it with: yarn server.

      Navigate to http://localhost:3000 to view the application from your browser. Then proceed to click on the Create button and fill in the details of a customer.

      Create customer page

      After entering the appropriate values in the input fields, click on the Create Customer button to submit the form. The application will redirect you back to your homepage once you're done creating a new customer.

      View customers page

      Click the Edit Customer button for any of the rows and you will be directed to the page that hosts the editing functionality for the corresponding customer on that row.

      Edit customer page

      Edit the details of the customer and then click on Edit Customer to update the customer’s details.

      You've run your application to ensure all the components are working. Using the different pages of your application, you've created and edited a customer entry.

      Conclusion

      In this tutorial you built a customer list management app with React and TypeScript. The process in this tutorial is a deviation from using JavaScript as the conventional way of structuring and building applications with React. You've leveraged the benefits of using TypeScript to complete this front-end focused tutorial.

      To continue to develop this project, you can move your mock back-end server to a production-ready back-end technology like Express or Nest.js. Furthermore, you can extend what you have built in this tutorial by adding more features such as authentication and authorization with different tools like the Passport.js authentication library.

      You can find the complete source code for the project on GitHub.



      Source link

      How To Display Data from the DigitalOcean API with React


      The author selected the Wikimedia Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      Over the last few years, open-source web frameworks have greatly simplified the process of coding an application. React, for example, has only added to the popularity of JavaScript by making the language more accessible to new developers and increasing the productivity of seasoned developers. Created by Facebook, React allows developers to quickly create high-end user interfaces for highly-scalable web-applications by supporting such features as declarative views, state management, and client-side rendering, each of which can greatly reduce the complexity of building an app in JavaScript.

      You can leverage frameworks like React to load and display data from the DigitalOcean API, through which you can manage your Droplets and other products within the DigitalOcean cloud using HTTP requests. Although one can fetch data from an API with many other JavaScript frameworks, React provides useful benefits like lifecycles and local state management that make it particularly well-suited for the job. With React, the data retrieved from the API is added to the local state when the application starts and can go through various lifecycles as components mount and dismount. At any point, you can retrieve the data from your local state and display it accordingly.

      In this tutorial, you will create a simple React application that interacts with the DigitalOcean API v2 to make calls and retrieve information about your Droplets. Your app will display a list containing your current Droplets and their details, like name, region, and technical specifications, and you will use the front-end framework Bootstrap to style your application.

      Once you have finished this tutorial, you will have a basic interface displaying a list of your DigitalOcean Droplets, styled to look like the following:

      The final version of your React Application

      Prerequisites

      Before you begin this guide, you’ll need a DigitalOcean account and at least one Droplet set up, in addition to the following:

      Step 1 — Creating a Basic React Application

      In this first step, you’ll create a basic React application using the Create React App package from npm. This package automatically installs and configures the essential dependencies needed to run React, like the module builder Webpack and the JavaScript compiler Babel. After installing, you’ll run the Create React App package using the package runner npx, which comes pre-installed with Node.js.

      To install Create React App and create the first version of your application, run the following command, replacing my-app with the name you want to give to your application:

      • npx create-react-app my-app

      After the installation is complete, move into the new project directory and start running the application using these commands:

      The preceding command starts a local development server provided by Create React App, which disables the command line prompt in your terminal. To proceed with the tutorial, open up a new terminal window and navigate back to the project directory before proceeding to the next step.

      You now have the first version of your React application running in development mode, which you can view by opening http://localhost:3000 in a web browser. At this point, your app will only display the welcome screen from Create React App:

      The first version of your React application

      Now that you have installed and created the first version of your React application, you can add a table component to your app that will eventually hold the data from the DigitalOcean API.

      Step 2 — Creating a Component to Show the Droplet Data

      In this step, you will create the first component that displays information about your Droplets. This component will be a table that lists all of your Droplets and their corresponding details.

      The DigitalOcean API documentation states that you can retrieve a list containing all of your Droplets by sending a request to the following endpoint using cURL: https://api.digitalocean.com/v2/droplets. Using the output from this request, you can create a table component containing id, name, region, memory, vcpus, and disk for each Droplet. Later on in this tutorial, you'll insert the data retrieved from the API into the table component.

      To define a clear structure for your application, create a new directory called components inside the src directory where you'll store all the code you write. Create a new file called Table.js inside the src/components directory and open it with nano or a text editor of your choice:

      • mkdir src/components
      • nano src/components/Table.js

      Define the table component by adding the following code to the file:

      src/components/Table.js

      import React from 'react';
      
      const Table = () => {
        return (
          <table>
            <thead>
              <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Region</th>
                <th>Memory</th>
                <th>CPUs</th>
                <th>Disk Size</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
              </tr>
            </tbody>
          </table>
        );
      }
      
      export default Table
      

      The code block above imports the React framework and defines a new component called Table, which consists of a table with a heading and a body.

      When you have added these lines of code, save and exit the file. With the nano text editor, you can do this by pressing CTRL+X, typing y, and pressing ENTER.

      Now that you have created the table component, it is time to include this component in your application. You'll do this by importing the component into the entry point of the application, which is in the file src/App.js. Open this file with the following command:

      Next, remove the boilerplate code that displays the Create React App welcome message in src/App.js, which is highlighted in the following code block.

      src/App.js

      import React, { Component } from 'react';
      import logo from './logo.svg';
      import './App.css';
      
      class App extends Component {
        render() {
          return (
            <div className="App">
              <header className="App-header">
                <img src={logo} className="App-logo" alt="logo" />
                <p>
                  Edit <code>src/App.js</code> and save to reload.
                </p>
                <a
                  className="App-link"
                  href="https://reactjs.org"
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  Learn React
                </a>
              </header>
            </div>
          );
        }
      }
      
      export default App;
      

      After removing the lines that displayed the welcome message, include the table component inside this same file by adding the following highlighted lines:

      src/App.js

      import React, { Component } from 'react';
      import Table from './components/Table.js';
      
      class App extends Component {
        render() {
          return (
            <div className="App">
              <Table />
            </div>
          );
        }
      }
      
      export default App;
      

      If you access http://localhost:3000 in your web browser again, your application will now display a basic table with table heads:

      The React application with a basic table

      In this step, you have created a table component and included this component into the entry point of your application. Next, you will set up a connection to the DigitalOcean API, which you'll use to retrieve the data that this table will display.

      Step 3 — Securing Your API Credentials

      Setting up a connection to the DigitalOcean API consists of several actions, starting with safely storing your Personal Access Token as an environment variable. This can be done by using dotenv, a package that allows you to store sensitive information in a .env file that your application can later access from the environment.

      Use npm to install the dotenv package:

      After installing dotenv, create an environment file called .env in the root directory of your application by executing this command:

      Add the following into .env, which contains your Personal Access Token and the URL for the DigitalOcean API :

      .env

      DO_API_URL=https://api.digitalocean.com/v2
      DO_ACCESS_TOKEN=YOUR_API_KEY
      

      To ensure this sensitive data doesn't get committed to a repository, add it to your .gitignore file with the following command:

      • echo ".env" >> .gitignore

      You have now created a safe and simple configuration file for your environment variables, which will provide your application with the information it needs to send requests to the DigitalOcean API. To ensure your API credentials aren't visible on the client side, you will next set up a proxy server to forward requests and responses between your application server and the DigitalOcean API.

      Install the middleware http-proxy-middleware by executing the following command:

      • npm install http-proxy-middleware

      After installing this, the next step is to set up your proxy. Create the setupProxy.js file in the src directory:

      Inside this file, add the following code to set up the proxy server:

      src/setupProxy.js

      const proxy = require('http-proxy-middleware')
      
      module.exports = function(app) {
      
        require('dotenv').config()
      
        const apiUrl = process.env.DO_API_URL
        const apiToken = process.env.DO_ACCESS_TOKEN
        const headers  = {
          "Content-Type": "application/json",
          "Authorization": "Bearer " + apiToken
        }
      
        // define http-proxy-middleware
        let DOProxy = proxy({
          target: apiUrl,
          changeOrigin: true,
        pathRewrite: {
          '^/api/' : '/'
        },
          headers: headers,
        })
      
        // define the route and map the proxy
        app.use('/api', DOProxy)
      
      };
      

      In the preceding code block, const apiURL = sets the url for the DigitalOcean API as the endpoint, and const apiToken = loads your Personal Access Token into the proxy server. The option pathRewrite mounts the proxy server to /api rather than / so that it does not interfere with the application server but still matches the DigitalOcean API.

      You've now successfully created a proxy server that will send all API requests made from your React application to the DigitalOcean API. This proxy server will make sure your Personal Access Token, which is safely stored as an environment variable, isn't exposed on the client side. Next, you will create the actual requests to retrieve your Droplet data for your application.

      Step 4 — Making API Calls to DigitalOcean

      Now that your display component is ready and the connection details to DigitalOcean are stored and secured through a proxy server, you can start retrieving data from the DigitalOcean API. First, add the following highlighted lines of code to src/App.js just before and after you declare the class App:

      src/App.js

      import React, { Component } from 'react';
      ...
      class App extends Component {
        constructor(props) {
          super(props);
          this.state = {
            droplets: []
          }
        }
      
          render() {
      ...
      

      These lines of code call a constructor method in your class component, which in React initializes the local state by providing this.state with an object or objects. In this case, the objects are your Droplets. From the code block above, you can see that the array containing your Droplets is empty, making it possible to fill it with the results from the API call.

      In order to display your current Droplets, you'll need to fetch this information from the DigitalOcean API. Using the JavaScript function Fetch, you'll send a request to the DigitalOcean API and update the state for droplets with the data you retrieve. You can do this using the componentDidMount method by adding the following lines of code after the constructor:

      src/App.js

      class App extends Component {
        constructor(props) {
          super(props);
          this.state = {
            droplets: []
          }
        }
      
        componentDidMount() {
          fetch('http://localhost:3000/api/droplets')
          .then(res => res.json())
          .then(json => json.droplets)
          .then(droplets => this.setState({ 'droplets': droplets }))
        }
      ...
      

      With your Droplet data stored into the state, it's time to retrieve it within the render function of your application and to send this data as a prop to the table component. Add the following highlighted statement to the table component in App.js:

      src/App.js

      ...
      class App extends Component {
        render() {
          return (
            <div className="App">
              <Table droplets={ this.state.droplets } />
            </div>
          );
        }
      }
      ...
      

      You have now created the functionality to retrieve data from the API, but you still need to make this data accessible via a web browser. In the next step, you will accomplish this by displaying your Droplet data in your table component.

      Step 5 — Displaying Droplet Data in Your Table Component

      Now that you have transferred the Droplet data to the table component, you can iterate this data over rows in the table. But since the application makes the request to the API after App.js is mounted, the property value for droplets will be empty at first. Therefore, you also need to add code to make sure droplets isn't empty before you try to display the data. To do this, add the following highlighted lines to the tbody section of Table.js:

      src/components/Table.js

      const Table = ({ droplets }) => {
        return (
          <table>
            <thead>
              <tr>
                <th>Id</th>
                <th>Name</th>
                <th>Region</th>
                <th>Memory</th>
                <th>CPUs</th>
                <th>Disk Size</th>
              </tr>
            </thead>
            <tbody>
              { (droplets.length > 0) ? droplets.map( (droplet, index) => {
                 return (
                  <tr key={ index }>
                    <td>{ droplet.id }</td>
                    <td>{ droplet.name }</td>
                    <td>{ droplet.region.slug}</td>
                    <td>{ droplet.memory }</td>
                    <td>{ droplet.vcpus }</td>
                    <td>{ droplet.disk }</td>
                  </tr>
                )
               }) : <tr><td colSpan="5">Loading...</td></tr> }
            </tbody>
          </table>
        );
      }
      

      With the addition of the preceding code, your application will display a Loading... placeholder message when no Droplet data is present. When the DigitalOcean API does return Droplet data, your application will iterate it over table rows containing columns for each data type and will display the result to your web browser:

      The React Application with Droplet data

      Note: If your web browser displays an error at http://localhost:3000, press CTRL+C in the terminal that is running your development server to stop your application. Run the following command to restart your application:

      In this step, you have modified the table component of your application to display your Droplet data in a web browser and added a placeholder message for when there are no Droplets found. Next, you will use a front-end web framework to style your data to make it more visually appealing and easier to read.

      Step 6 — Styling Your Table Component Using Bootstrap

      Your table is now populated with data, but the information is not displayed in the most appealing manner. To fix this, you can style your application by adding Bootstrap to your project. Bootstrap is an open-source styling and component library that lets you add responsive styling to a project with CSS templates.

      Install Bootstrap with npm using the following command:

      After Bootstrap has finished installing, import its CSS file into your project by adding the following highlighted line to src/App.js:

      src/App.js

      import React, { Component } from 'react';
      import Table from './components/Table.js';
      import 'bootstrap/dist/css/bootstrap.min.css';
      
      class App extends Component {
      ...
      

      Now that you have imported the CSS, apply the Bootstrap styling to your table component by adding the class table to the <table> tag in src/components/Table.js.

      src/components/Table.js

      import React from 'react';
      
      const Table = ({ droplets }) => {
        return (
          <table className="table">
            <thead>
      ...
      

      Next, finish styling your application by placing a header above your table with a title and the DigitalOcean logo. Click on Download Logos in the Brand Assets section of DigitalOcean's Press page to download a set of logos, pick your favorite from the SVG directory (this tutorial uses DO_Logo_icon_blue.svg), and add it to your project by copying the logo file into a new directory called assets within the src directory of your project. After uploading the logo, import it into the header by adding the highlighted lines to src/App.js:

      src/App.js

      import React, { Component } from 'react';
      import Table from './components/Table.js';
      import 'bootstrap/dist/css/bootstrap.min.css';
      import logo from './assets/DO_Logo_icon_blue.svg';
      
      class App extends Component {
      ...
        render() {
          return (
            <div className="App">
              <nav class="navbar navbar-light bg-light">
                <a class="navbar-brand" href="./">
                  <img src={logo} alt="logo" width="40" /> My Droplets
                </a>
              </nav>
              <Table droplets={ this.state.droplets } />
            </div>
          );
        }
      }
      
      export default App;
      

      In the preceding code block, the classes within the nav tag add a particular styling from Bootstrap to your header.

      Now that you have imported Bootstrap and applied its styling to your application, your data will show up in your web browser with an organized and legible display:

      The final version of your React Application

      Conclusion

      In this article, you've created a basic React application that fetches data from the DigitalOcean API through a secured proxy server and displays it with Bootstrap styling. Now that you are familiar with the React framework, you can apply the concepts you learned here to more complicated applications, such as the one found in How To Build a Modern Web Application to Manage Customer Information with Django and React on Ubuntu 18.04. If you want to find out what other actions are possible with the DigitalOcean API, have a look at the API documentation on DigitalOcean's website.



      Source link