How to Read a Api With Ruby on Rails

React + Carmine on Rails = 🔥

React has taken the frontend development earth by tempest. Information technology's an fantabulous JavaScript library for building user interfaces. And it's dandy in combination with Ruby on Track. Y'all can use Runway on the back end with React on the front end in various ways.

In this hands-on tutorial, nosotros're going to build a React app that works with a Rails five.1 API.

You can watch a video version of this tutorial hither.

Watch a video version of this tutorial

To follow this tutorial, yous demand to exist comfortable with Rails and know the basics of React.

If you don't use Rails, you lot can also build the API in the language or framework of your pick, and just use this tutorial for the React office.

The tutorial covers stateless functional components, class-based components, using Create React App, use of axios for making API calls, immutability-helper and more.

What We're Going to Build

We're going to build an idea board every bit a single page app (SPA), which displays ideas in the class of foursquare tiles.

You can add new ideas, edit them and delete them. Ideas get auto-saved when the user focuses out of the editing form.

A demo of the Idea board app

At the stop of this tutorial, we'll have a functional CRUD app, to which we can add together some enhancements, such every bit animations, sorting and search in a future tutorial.

Y'all tin can see the total code for the app on GitHub:

Ideaboard Rails API

Ideaboard React frontend

Setting up the Rails API

Allow's go started past edifice the Runway API. We'll utilize the in-built feature of Rails for building API-only apps.

Make sure you lot take version 5.ane or higher of the Rails gem installed.

          jewel            install            rails -v            5.i.3                  

At the fourth dimension of writing this tutorial, 5.i.3 is the latest stable release, so that's what nosotros'll use.

And then generate a new Runway API app with the --api flag.

          rails new --api ideaboard-api            cd            ideaboard-api                  

Next, let'due south create the data model. We only need 1 information model for ideas with ii fields — a title and a body, both of type string.

Let'south generate and run the migration:

          runway generate model Idea title:string body:string  rails db:migrate                  

At present that we've created an ideas table in our database, let'south seed it with some records and then that we have some ideas to brandish.

In the db/seeds.rb file, add the following lawmaking:

          ideas            =            Thought            .create(            [            {            title:            "A new block recipe"            ,            trunk:            "Made of chocolate"            }            ,            {            title:            "A twitter client thought"            ,            body:            "Only for replying to mentions and DMs"            }            ,            {            title:            "A novel set up in Italy"            ,            trunk:            "A mafia crime drama starring Berlusconi"            }            ,            {            championship:            "Bill of fare game design"            ,            body:            "Like Uno but involves drinking"            }            ]            )                  

Experience free to add your own ideas.

Then run:

          rails db:seed                  

Side by side, permit's create an IdeasController with an index action in app/controllers/api/v1/ideas_controller.rb:

                      module            Api            :            :            V1            class            IdeasController            <            ApplicationController            def                          alphabetize                        @ideas            =            Thought            .all       render json:            @ideas            terminate            end            stop                  

Note that the controller is under app/controllers/api/v1 considering we're versioning our API. This is a good practice to avoid breaking changes and provide some backwards compatibility with our API.

Then add ideas every bit a resource in config/routes.rb:

                      Rails            .application.routes.draw            practise            namespace            :api            do            namespace            :v1            do            resources            :ideas            end            finish            cease                  

Alright, now permit's exam our first API endpoint!

Kickoff, let's start the Rails API server on port 3001:

          rails south -p            3001                  

Then, let's test our endpoint for getting all ideas with coil:

                      curl            -G http://localhost:3001/api/v1/ideas                  

And that prints all our ideas in JSON format:

                      [            {            "id":18,"title"            :            "Card game design","body"            :            "Like Uno but involves drinking","created_at"            :            "2017-09-05T15:42:36.217Z","updated_at"            :            "2017-09-05T15:42:36.217Z"            },{            "id":17,"championship"            :            "A novel fix in Italia","torso"            :            "A mafia criminal offence drama starring Berlusconi","created_at"            :            "2017-09-05T15:42:36.213Z","updated_at"            :            "2017-09-05T15:42:36.213Z"            },{            "id":16,"title"            :            "A twitter customer idea","body"            :            "Only for replying to mentions and DMs","created_at"            :            "2017-09-05T15:42:36.209Z","updated_at"            :            "2017-09-05T15:42:36.209Z"            },{            "id":xv,"championship"            :            "A new cake recipe","torso"            :            "Made of chocolate","created_at"            :            "2017-09-05T15:42:36.205Z","updated_at"            :            "2017-09-05T15:42:36.205Z"            }            ]                  

We can too exam the endpoint in a browser by going to http://localhost:3001/api/v1/ideas.

Testing our API endpoint in a browser

Setting upwards Our Front-end App Using Create React App

Now that we have a basic API, let'southward set upward our front-end React app using Create React App. Create React App is a projection by Facebook that helps y'all go started with a React app quickly without any configuration.

First, make sure y'all have Node.js and npm installed. You can download the installer from the Node.js website. And then install Create React App by running:

                      npm            install            -g create-react-app                  

Then, make certain you lot're outside the Rails directory and run the following command:

          create-react-app ideaboard                  

That volition generate a React app called ideaboard, which nosotros'll now utilise to talk to our Rails API.

Let's run the React app:

                      cd            ideaboard            npm            showtime                  

This will open it on http://localhost:3000.

Homepage of a new app generated by Create React App

The app has a default folio with a React component called App that displays the React logo and a welcome message.

The content on the page is rendered through a React component in the src/App.js file:

                      import                          React              ,              {              Component              }                        from            'react'            import            logo            from            './logo.svg'            import            './App.css'            class            App            extends            Component            {            render            (            )            {            return            (                                          <div              className                              =                "App"                            >                                                                              <div              className                              =                "App-header"                            >                                                                              <img              src                              =                {logo}                            className                              =                "App-logo"                            alt                              =                "logo"                            />                                                                              <h2              >                        Welcome to React                                          </h2              >                                                                              </div              >                                                                              <p              className                              =                "App-intro"                            >                                      To go started, edit                                                      <code              >                        src/App.js                                          </code              >                                      and save to reload.                                                      </p              >                                                                              </div              >                        )            ;            }            }            export            default            App                  

Our First React Component

Our next stride is to edit this file to utilize the API we but created and list all the ideas on the page.

Let'due south kickoff off by replacing the Welcome message with an h1 tag with the championship of our app 'Thought Board'.

Let'due south also add a new component called IdeasContainer. We need to import it and add it to the render function:

                      import                          React              ,              {              Component              }                        from            'react'            import            './App.css'            import                          IdeasContainer                        from            './components/IdeasContainer'            class            App            extends            Component            {            return            (            )            {            return            (                                          <div              className                              =                "App"                            >                                                                              <div              className                              =                "App-header"                            >                                                                              <h1              >                        Idea Lath                                          </h1              >                                                                              </div              >                                                                              <                IdeasContainer                            />                                                                              </div              >                        )            ;            }            }            export            default            App                  

Let's create this IdeasContainer component in a new file in src/IdeasContainer.js under a src/components directory.

                      import                          React              ,              {              Component              }                        from            'react'            course            IdeasContainer            extends            Component            {            return            (            )            {            return            (                                          <div              >                                      Ideas                                                      </div              >                        )            }            }            export            default            IdeasContainer                  

Let's likewise modify the styles in App.css to have a white header and black text, and besides remove styles we don't need:

                                    .App-header                        {            text-align            :            middle;            meridian            :            150            px            ;            padding            :            twenty            px            ;            }                          .App-intro                        {            font-size            :            large;            }                  

Skeleton Idea board app

This component needs to talk to our Rails API endpoint for getting all ideas and display them.

Fetching API Data with axios

We'll make an Ajax call to the API in the componentDidMount() lifecycle method of the IdeasContainer component and shop the ideas in the component state.

Let's start past initializing the state in the constructor with ideas equally an empty assortment:

                      constructor            (            props            )            {            super            (props)            this            .            country            =            {            ideas:            [            ]            }            }                  

And then nosotros'll update the state in componentDidMount().

Let's apply the axios library for making the API calls. Y'all can besides employ fetch or jQuery if you prefer those.

Install axios with npm:

                      npm            install            axios --save                  

And so import it in IdeasContainer:

                      import            axios            from            'axios'                  

And use it in componentDidMount():

                      componentDidMount            (            )            {            axios.            get            (            'http://localhost:3001/api/v1/ideas.json'            )            .            then            (            response            =>            {            console            .            log            (response)            this            .            setState            (            {ideas:            response.            data            }            )            }            )            .            catch            (            error            =>            console            .            log            (error)            )            }                  

At present if we refresh the folio … information technology won't piece of work!

No Access-Control-Allow-Origin header present

We'll go a "No Access-Control-Allow-Origin header present" error, considering our API is on a different port and we haven't enabled Cantankerous Origin Resource Sharing (CORS).

Enabling Cantankerous Origin Resource Sharing (CORS)

And then allow's first enable CORS using the rack-cors gem in our Rails app.

Add the jewel to the Gemfile:

          gem            'rack-cors'            ,            :require            =            >            'rack/cors'                  

Install information technology:

          bundle            install                  

And then add the middleware configuration to config/application.rb file:

          config.middleware.insert_before            0            ,            Rack            :            :            Cors            exercise            permit            practise            origins            'http://localhost:3000'            resource            '*'            ,            :headers            =            >            :any            ,            :methods            =            >            [            :get            ,            :post            ,            :put            ,            :delete            ,            :options            ]            end            end                  

We restrict the origins to our front-terminate app at http://localhost:3000 and allow admission to the standard Remainder API endpoint methods for all resources.

Now we demand to restart the Rails server, and if we refresh the browser, we'll no longer get the CORS error.

The folio volition load fine and we can see the response data logged in the console.

Ideas JSON response from API logged to the console

So now that nosotros know nosotros're able to fetch ideas from our API, let's apply them in our React component.

We tin can change the return function to iterate through the list ideas from the country and display each of them:

                      return            (            )            {            return            (                                          <div              >                                                {            this            .            state            .            ideas            .            map            (            (            idea            )            =>            {            return            (                                          <div              className                              =                "tile"                            key                              =                {idea.                id                }                            >                                                                              <h4              >                        {idea.            title            }                                          </h4              >                                                                              <p              >                        {idea.            body            }                                          </p              >                                                                              </div              >                        )            }            )            }                                                                  </div              >                        )            ;            }                  

That will display all the ideas on the page now.

List of ideas displayed by component

Note the primal attribute on the tile div.

Nosotros need to include information technology when creating lists of elements. Keys help React place which items have inverse, are added, or are removed.

Now let's add some styling in App.css to make each idea await like a tile:

                                    .tile                        {            pinnacle            :            150            px            ;            width            :            150            px            ;            margin            :            x            px            ;            background            :            lightyellow            ;            bladder            :            left;            font-size            :            11            px            ;            text-align            :            left;            }                  

We set the height, width, background colour and make the tiles bladder left.

Styled idea tiles

Stateless functional components

Before we go on, let'southward refactor our code and then far and move the JSX for the idea tiles into a separate component called Thought.

                      import                          React                        from            'react'            const                          Thought                        =            (                          {idea}                        )            =>                                          <div              className                              =                "tile"                            key                              =                {idea.                id                }                            >                                                                              <h4              >                        {idea.            title            }                                          </h4              >                                                                              <p              >                        {idea.            torso            }                                          </p              >                                                                              </div              >                        consign            default            Idea                  

This is a stateless functional component (or as some call it, a "dumb" component), which means that it doesn't handle any land. It'southward a pure function that accepts some information and returns JSX.

Then inside the map function in IdeasContainer, we tin return the new Idea component:

                      {            this            .            land            .            ideas            .            map            (            (            idea            )            =>            {            return            (                                          <                Idea                            thought                              =                {idea}                            key                              =                {idea.                id                }                            />                        )            }            )            }                  

Don't forget to import Thought as well:

                      import                          Thought                        from            './Idea'                  

Great, so that'southward the first office of our app complete. Nosotros have an API with an endpoint for getting ideas and a React app for displaying them as tiles on a board!

Adding a new tape

Adjacent, we'll add together a way to create new ideas.

Let's start by calculation a button to add a new thought.

Inside the render function in IdeasContainer, add:

                                                    <push              className                              =                "newIdeaButton"                            >                                      New Idea                                                      </button              >                              

And permit's add together some styling for information technology in App.css:

                                    .newIdeaButton                        {            background            :            darkblue            ;            color            :            white            ;            border            :            none;            font-size            :            18            px            ;            cursor            :            pointer;            margin-right            :            10            px            ;            margin-left            :            x            px            ;            padding            :            10            px            ;            }                  

New Idea button

Now when nosotros click the button, nosotros want another tile to appear with a form to edit the idea.

One time we edit the form, nosotros want to submit it to our API to create a new idea.

API endpoint for creating a new idea

So permit's start of by first making an API endpoint for creating new ideas in IdeasController:

                      def                          create                        @thought            =            Thought            .create(idea_params)            render json:            @idea            end            private            def                          idea_params                        params.            require            (            :idea            )            .permit(            :title            ,            :body            )            cease                  

Since Track uses strong parameters, we define the private method idea_params to whitelist the params we need — title and body.

At present nosotros take an API endpoint to which we tin post thought data and create new ideas.

Back in our React app, now permit's add a click handler called addNewIdea to the new idea button:

                                                    <push button              className                              =                "newIdeaButton"                            onClick                              =                {                this                .                addNewIdea                }                            >                                      New Thought                                                      </push              >                              

Let'southward define addNewIdea as a role that uses axios to make a POST call to our new idea endpoint with a bare idea. Permit'due south just log the response to the panel for now:

                      addNewIdea            =            (            )            =>            {            axios.            post            (            'http://localhost:3001/api/v1/ideas'            ,            {            thought:            {            title:            ''            ,            trunk:            ''            }            }            )            .            then            (            response            =>            {            console            .            log            (response)            }            )            .            catch            (            error            =>            console            .            log            (error)            )            }                  

Now if we try clicking on the new idea button in the browser, we'll see in the panel that the response contains a information object with our new idea with a bare championship and body.

Blank idea data logged to the console

When nosotros refresh the page, we can see an empty tile representing our new thought.

Blank idea tile

What we really desire to happen is that, when we click the new idea push button, an thought is created immediately, and a grade for editing that idea appears on the page.

This way, nosotros can employ the same form and logic for editing any idea later on on in the tutorial.

Before we do that, let'south first society the ideas on the folio in reverse chronological order so that the newest ideas appear at the height.

So let's change the definition of @ideas in IdeasController to club ideas in descending order of their created_at time:

                      module            Api            :            :            V1            class            IdeasController            <            ApplicationController            def                          index                        @ideas            =            Idea            .society(            "created_at DESC"            )            render json:            @ideas            end            stop            end                  

Alright, now the latest ideas are displayed beginning.

Display newest ideas first

Now, let's continue with defining addNewIdea.

Starting time, permit's use the response from our POST phone call to update the assortment of ideas in the country, then that when we add a new thought it appears on the page immediately.

We could just push the new idea to the array, since this is only an example app, only information technology'due south good practice to use immutable data.

So permit's utilize immutability-helper, which is a nice package for updating information without direct mutating it.

Install it with npm:

                      npm            install            immutability-helper --salve                  

Then import the update office in IdeasContainer:

                      import            update            from            'immutability-helper'                  

Now let'due south use information technology within addNewIdea to insert our new idea at the offset of the array of ideas:

                      addNewIdea            =            (            )            =>            {            axios.            post            (            'http://localhost:3001/api/v1/ideas'            ,            {            idea:            {            title:            ''            ,            torso:            ''            }            }            )            .            and so            (            response            =>            {            console            .            log            (response)            const            ideas            =            update            (            this            .            country            .            ideas            ,            {            $splice:            [            [            0            ,            0            ,            response.            data            ]            ]            }            )            this            .            setState            (            {ideas:            ideas}            )            }            )            .            take hold of            (            error            =>            panel            .            log            (error)            )            }                  

We brand a new copy of this.state.ideas and use the $splice command to insert the new thought (in response.data) at the 0th index of this array.

Then we use this new ideas assortment to update the land using setState.

At present if we try the app in the browser and click the new thought button, a new empty tile appears immediately.

Add a new idea

Now we tin can proceed with editing this idea.

First, we need a new state property editingIdeaId, which keeps track of which idea is being currently edited.

Past default, we're not editing whatever idea, so let's initialize editingIdeaId in the state with a nothing value:

                      this            .            state            =            {            ideas:            [            ]            ,            editingIdeaId:            zero            }                  

Now when we add together a new idea, in add-on to calculation it to state.ideas, we as well want to set its id every bit the value of state.editingIdeaId. And then permit'southward alter the setState call in addNewIdea to include also set up editingIdeaId:

                      this            .            setState            (            {            ideas:            ideas,            editingIdeaId:            response.            data            .            id            }            )                  

And so this indicates that we've simply added a new idea and we want to edit it immediately.

The complete addNewIdea part now looks similar this:

                      addNewIdea            =            (            )            =>            {            axios.            post            (            'http://localhost:3001/api/v1/ideas'            ,            {            thought:            {            title:            ''            ,            trunk:            ''            }            }            )            .            so            (            response            =>            {            const            ideas            =            update            (            this            .            country            .            ideas            ,            {            $splice:            [            [            0            ,            0            ,            response.            data            ]            ]            }            )            this            .            setState            (            {            ideas:            ideas,            editingIdeaId:            response.            data            .            id            }            )            }            )            .            catch            (            error            =>            panel            .            log            (error)            )            }                  

A Form component

Now we tin can use state.editingIdeaId in the return function, and so that instead of displaying but a normal idea tile, nosotros tin display a form.

Inside the map office, let's change the return value to a conditional argument, which renders an IdeaForm component if an idea'southward id matches state.editingIdeaId, otherwise rendering an Idea component:

                      {            this            .            country            .            ideas            .            map            (            (            idea            )            =>            {            if            (            this            .            land            .            editingIdeaId            ===            idea.            id            )            {            return            (                                          <                IdeaForm                            thought                              =                {idea}                            key                              =                {thought.                id                }                            />                        )            }            else            {            return            (                                          <                Idea                            idea                              =                {idea}                            key                              =                {idea.                id                }                            />                        )            }            }            )            }                  

Let'southward import the IdeaForm component in IdeasContainer:

                      import                          IdeaForm                        from            './IdeaForm'                  

And let's define it in IdeaForm.js. Nosotros'll offset with a simple form component, which renders a class with two input fields for the idea title and torso:

                      import                          React              ,              {              Component              }                        from            'react'            import            axios            from            'axios'            form            IdeaForm            extends            Component            {            constructor            (            props            )            {            super            (props)            this            .            state            =            {            }            }            return            (            )            {            render            (                                          <div              className                              =                "tile"                            >                                                                              <form              >                                                                              <input              className                              =                'input'                            type                              =                "text"                            name                              =                "championship"                            placeholder                              =                'Enter a Title'                            />                                                                              <textarea              className                              =                'input'                            name                              =                "body"                            placeholder                              =                'Describe your thought'                            >                                                      </textarea              >                                                                              </grade              >                                                                              </div              >                        )            ;            }            }            export            default            IdeaForm                  

Let'southward add a scrap of CSS in App.css to style the form:

                                    .input                        {            border            :            0            ;            background            :            none;            outline            :            none;            margin-top            :            ten            px            ;            width            :            140            px            ;            font-size            :            11            px            ;            }                          .input              :focus                        {            edge            :            solid            1            px            lightgrey            ;            }            textarea            {            resize            :            none;            tiptop            :            90            px            ;            font-size            :            11            px            ;            }                  

Now when nosotros click on the new idea button, a new tile appears with a form in it:

Styled form for editing new idea

Now let'southward make this form functional!

Nosotros demand to hook upwardly the form input fields to the state.

First, permit'south initialize the IdeaForm component land values from the idea prop that it receives from IdeasContainer:

                      form            IdeaForm            extends            Component            {            constructor            (            props            )            {            super            (props)            this            .            country            =            {            title:            this            .            props            .            idea            .            championship            ,            body:            this            .            props            .            idea            .            body            }            }                  

And then prepare the class field values to their respective state values and set up an onChange handler:

                                                    <form              >                                                                              <input              className                              =                'input'                            type                              =                "text"                            name                              =                "title"                            placeholder                              =                'Enter a Championship'                            value                              =                {                this                .                state                .                championship                }                            onChange                              =                {                this                .                handleInput                }                            />                                                                              <textarea              className                              =                'input'                            name                              =                "torso"                            placeholder                              =                'Describe your thought'                            value                              =                {                this                .                state                .                torso                }                            onChange                              =                {                this                .                handleInput                }                            >                                                                              </textarea              >                                                                              </form              >                              

Nosotros'll define handleInput such that, when we type in either of the input fields, the respective state value and so the value of the field gets updated:

                      handleInput            =            (            due east            )            =>            {            this            .            setState            (            {            [east.            target            .            proper noun            ]            :            e.            target            .            value            }            )            }                  

Tracking state changes in React Programmer Tools

Let'south see these state changes in activeness with the React Programmer Tools browser extension. You tin can become it for Chrome here and for Firefox here.

Once yous accept it installed, refresh the app page and open up the programmer console. You should see a new React tab.

When you click on information technology, you lot'll see our app components tree on the left and all the props and state associated with each component on the right.

React developer tools showing state updates

Now nosotros're updating the class fields, but we're still not saving the edited thought. So the next affair needed is that, when nosotros blur out of a form field, we want to submit the form and update the idea.

API endpoint for updating ideas

First, nosotros need to define an API endpoint for updating ideas. And so let'due south add an update activeness in IdeasController:

                      def                          update                        @idea            =            Idea            .find(params[            :id            ]            )            @idea            .update_attributes(idea_params)            return json:            @idea            finish                  

Back in IdeaForm.js, we'll ready an onBlur handler chosen handleBlur to the form:

                                                    <form              onBlur                              =                {                this                .                handleBlur                }                            >                                                      

Nosotros'll define handleBlur to make a PUT phone call to our API endpoint for updating ideas with idea data from the state. For now, let's just log the response to the console and come across if our call works:

                      handleBlur            =            (            )            =>            {            const            idea            =            {            title:            this            .            state            .            title            ,            body:            this            .            state            .            torso            }            axios.            put            (                          `              http://localhost:3001/api/v1/ideas/                              ${                this                .                props                .                idea                .                id                }                            `                        ,            {            thought:            idea            }            )            .            then            (            response            =>            {            panel            .            log            (response)            }            )            .            catch            (            error            =>            console            .            log            (error)            )            }                  

We as well need to import axios in this file to be able to use it:

                      import            axios            from            'axios'                  

Now if nosotros click on the new idea button, edit its title and blur out of that field, we'll come across our API response logged in the console, with the new edited thought data.

The same thing happens if we edit the torso and blur out of that field.

Checking edited idea data in the console

So our onBlur handler works and we can edit our new idea, but nosotros likewise need to send the edited idea data back up to IdeasContainer so that it can update its own state likewise.

Otherwise, state.ideas won't accept the updated value of the idea we only edited.

We'll use a method chosen updateIdea, which nosotros'll laissez passer as a prop from IdeasContainer to IdeaForm. We'll call updateIdea with the response data from our API telephone call:

                      handleBlur            =            (            )            =>            {            const            idea            =            {            title:            this            .            country            .            title            ,            body:            this            .            country            .            body            }            axios.            put            (                          `              http://localhost:3001/api/v1/ideas/                              ${                this                .                props                .                idea                .                id                }                            `                        ,            {            idea:            idea            }            )            .            then            (            response            =>            {            console            .            log            (response)            this            .            props            .            updateIdea            (response.            data            )            }            )            .            catch            (            fault            =>            console            .            log            (fault)            )            }                  

Now in IdeasContainer, permit's transport an updateIdea office as a prop to IdeaForm:

                                                    <                IdeaForm                            idea                              =                {idea}                            cardinal                              =                {idea.                id                }                            updateIdea                              =                {                this                .                updateIdea                }                            />                              

Let's define the office to do an immutable update of the idea in state.ideas:

                      updateIdea            =            (            idea            )            =>            {            const            ideaIndex            =            this            .            state            .            ideas            .            findIndex            (            x            =>            10.            id            ===            idea.            id            )            const            ideas            =            update            (            this            .            state            .            ideas            ,            {            [ideaIndex]            :            {            $set:            idea            }            }            )            this            .            setState            (            {ideas:            ideas}            )            }                  

First, we find the alphabetize of the edited idea in the array, and and so use the $prepare command to supercede the onetime value with the new i. Finally, we call setState to update state.ideas.

We can see this in activity in the browser with the React Developer Tools tab open.

IdeasContainer state updates

Displaying a success notification

Now we tin can add a new idea and edit information technology, only the user gets no visual feedback or confirmation when the idea is saved. So let'south add together a notification message to tell the user when an idea has been successfully saved.

Let's add together a span next to the new idea push button to display a notification from a value in state:

                                                    <span              className                              =                "notification"                            >                                                {            this            .            state            .            notification            }                                                                  </span              >                              

Let's initialize land.notification as an empty string:

                      constructor            (            props            )            {            super            (props)            this            .            state            =            {            ideas:            [            ]            ,            editingIdeaId:            nil            ,            notification:            ''            }            }                  

At present every fourth dimension an idea gets updated, we'll update land.notification with a success notification we desire to show to the user.

So in the setState phone call in updateIdea, in improver to updating ideas, allow's also update notification:

                      this            .            setState            (            {            ideas:            ideas,            notification:            'All changes saved'            }            )                  

Now when nosotros edit an idea and mistiness out of the input field, the thought gets saved and nosotros see the success notification.

Notification message on successful updates to an idea

We also want to reset the notification as soon every bit the user makes a modify that hasn't been saved even so.

So in the handleInput role of the IdeaForm component, let's call a function chosen resetNotification to reset the notification bulletin:

                      handleInput            =            (            e            )            =>            {            this            .            props            .            resetNotification            (            )            this            .            setState            (            {            [e.            target            .            name            ]            :            eastward.            target            .            value            }            )            }                  

At present, inside the render function of IdeasContainer, let's as well pass resetNotification as a prop to IdeaForm:

                                                    <                IdeaForm                            thought                              =                {idea}                            primal                              =                {thought.                id                }                            updateIdea                              =                {                this                .                updateIdea                }                            resetNotification                              =                {                this                .                resetNotification                }                            />                              

Let's define resetNotification as:

                      resetNotification            =            (            )            =>            {            this            .            setState            (            {notification:            ''            }            )            }                  

Now after a success notification appears, if nosotros edit the idea once again, the notification disappears.

Reset notification message on unsaved edits

Editing an existing thought

Next, let's add the ability to edit an existing idea. When we click on an idea tile, we want to change the tile and then that it replaces the Idea component with an IdeaForm component to edit that thought.

Then we can edit the idea and it will get saved on blur.

In gild to add this characteristic, nosotros need to add a click handler on our idea tiles.

So first we need to convert our Thought component from a functional component into a class component so we tin gear up define a click handler function handleClick for the title and body.

                      import                          React              ,              {              Component              }                        from            'react'            class            Thought            extends            Component            {            handleClick            =            (            )            =>            {            this            .            props            .            onClick            (            this            .            props            .            idea            .            id            )            }            render            (            )            {            return            (                                          <div              className                              =                "tile"                            >                                                                              <h4              onClick                              =                {                this                .                handleClick                }                            >                                                {            this            .            props            .            idea            .            title            }                                                                  </h4              >                                                                              <p              onClick                              =                {                this                .                handleClick                }                            >                                                {            this            .            props            .            thought            .            body            }                                                                  </p              >                                                                              </div              >                        )            }            }            export            default            Idea                  

Note that we accept to add this.props. to use the props value, considering different in the functional component, we are no longer destructuring the props object.

handleClick calls this.props.onClick with the idea id.

Now, inside the render office of IdeasContainer, let's also laissez passer onClick as a prop to Idea:

                      return            (                                          <                Idea                            idea                              =                {idea}                            fundamental                              =                {idea.                id                }                            onClick                              =                {                this                .                enableEditing                }                            />                        )                  

We'll define enableEditing to set the value of country.editingIdeaId to the clicked idea's id:

                      enableEditing            =            (            id            )            =>            {            this            .            setState            (            {editingIdeaId:            id}            )            }                  

Now when nosotros click on a tile, it instantly becomes editable!

Click on an idea tile to edit it

When nosotros click on a tile, once the form appears, allow's also gear up the cursor focus to the championship input field.

Nosotros can do that by adding a ref on the title input field in IdeaForm:

                                                    <input              className                              =                'input'                            type                              =                "text"                            name                              =                "title"                            placeholder                              =                'Enter a Title'                            value                              =                {                this                .                state                .                championship                }                            onChange                              =                {                this                .                handleInput                }                            ref                              =                {                this                .                props                .                titleRef                }                            />                              

We demand to pass the ref as a prop, because we desire to use it in the parent component IdeasContainer, where we tin can define the ref every bit a callback function:

                      <            IdeaForm            thought=            {thought}            key=            {thought.            id            }            updateIdea=            {            this            .            updateIdea            }            titleRef=            {            input            =>            this            .            title            =            input}            resetNotification=            {            this            .            resetNotification            }            /            >                  

Now we can use this ref in enableEditing to set up the focus in the title input field:

                      enableEditing            =            (            id            )            =>            {            this            .            setState            (            {editingIdeaId:            id}            ,            (            )            =>            {            this            .            championship            .            focus            (            )            }            )            }                  

Discover that we didn't telephone call this.title.focus() as a separate function later on calling setState. Instead, we passed it to setState inside a callback as a 2d argument.

We did this because setState doesn't always immediately update the component. Past passing our focus call in a callback, we make sure that it gets called only after the component has been updated.

Now if nosotros endeavor the app in a browser, when nosotros click on an idea tile, it becomes editable with a form and the cursor gets focused within its title input field.

Click to edit idea and set focus to input field

And then now we can add together and edit ideas.

Deleting an idea

Finally, nosotros desire to exist able to delete ideas.

When we hover over an thought tile, we want a delete button (in the grade of a red cross) to appear in the acme right corner. Clicking that cross should delete the idea and remove the tile from the board.

So let's start by calculation some markup and CSS to brandish the delete button on hover.

In the Thought component, add a span with a course deleteButton and the text 'x':

                                                    <div              className                              =                "tile"                            >                                                                              <span              className                              =                "deleteButton"                            >                                      x                                                      </span              >                                                      

Then let'south add together some CSS in App.css to hide this span past default and brand it visible when nosotros hover over a tile:

                                    .deleteButton                        {            visibility            :            hidden;            float            :            right;            margin            :            5            px            ;            font-size            :            14            px            ;            cursor            :            arrow;            colour            :            crimson            ;            }                          .tile              :hover              .deleteButton                        {            visibility            :            visible;            }                  

Delete button appears on hovering over a tile

Next, allow's add a click handler handleDelete to this delete button, which then deletes the idea:

                                                    <span              className                              =                "deleteButton"                            onClick                              =                {                this                .                handleDelete                }                            >                                      x                                                      </bridge              >                              

Like to handleClick, we'll define handleDelete equally an arrow function that calls another function this.props.onDelete with the tile's idea id:

                      handleDelete            =            (            )            =>            {            this            .            props            .            onDelete            (            this            .            props            .            idea            .            id            )            }                  

Let's laissez passer onDelete as a prop from IdeasContainer:

                                                    <                Idea                            idea                              =                {idea}                            primal                              =                {thought.                id                }                            onClick                              =                {                this                .                enableEditing                }                            onDelete                              =                {                this                .                deleteIdea                }                            />                              

Nosotros'll define deleteIdea in a moment, but first let'due south add together an API endpoint for deleting ideas in IdeasController:

                      def                          destroy                        @idea            =            Thought            .find(params[            :id            ]            )            if            @idea            .destroy     head            :no_content            ,            status:            :ok            else            render json:            @thought            .errors,            status:            :unprocessable_entity            cease            end                  

Now let'due south ascertain deleteIdea in IdeasContainer equally a function that makes a DELETE call to our API with the idea id and, on success, updates state.ideas:

                      deleteIdea            =            (            id            )            =>            {            axios.            delete            (                          `              http://localhost:3001/api/v1/ideas/                              ${id}                            `                        )            .            so            (            response            =>            {            const            ideaIndex            =            this            .            state            .            ideas            .            findIndex            (            10            =>            x.            id            ===            id)            const            ideas            =            update            (            this            .            state            .            ideas            ,            {            $splice:            [            [ideaIndex,            i            ]            ]            }            )            this            .            setState            (            {ideas:            ideas}            )            }            )            .            catch            (            fault            =>            console            .            log            (error)            )            }                  

Once again, we look up the alphabetize of the deleted idea, use update with the $splice command to create a new array of ideas, and so update state.ideas with that.

Now nosotros can endeavor information technology in the browser. When we hover over an idea tile, the ruby delete button appears. Clicking on it deletes the idea and removes the tile from the board.

Click the delete button to delete ideas

Hurray, we now have a functional app with all the basic CRUD functionality!

Wrap Up

In this tutorial, nosotros built a consummate CRUD app using a Runway 5.1 API and a forepart-end React app.

Our API has 3 endpoints, ane each for creating, updating and deleting ideas.

We used Create React App to make our React app. This made setup completely painless and easy. Nosotros could dive directly into building our app instead of configuring anything.

Nosotros used axios for making Ajax calls to the API and immutability-helper to brand information updates.

In a hereafter tutorial, we can look at how to deploy this app to a product server and too add together some animations and transitions to spice upwards the UI. For case, nosotros could fade in new idea tiles and fade out deleted tiles, fade in and out notification letters.

Y'all can scout a video version of this tutorial here.

You tin encounter the full code for the app on GitHub:

Ideaboard Rail API

Ideaboard React frontend

vondoussadrinnera.blogspot.com

Source: https://www.sitepoint.com/react-rails-5-1/

0 Response to "How to Read a Api With Ruby on Rails"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel