Sending data from React to Rails and vice versa.
Tags: react, programming, rails, spoonacularapi
Published: 2021-01-25 05:57:29

During my final project at Flatiron School, I decided to build a foody app called Hipstew, which allows user to search for recipes based on given ingredients and they can create a list of their favorite recipes as well! And I've been wanting to work with a larger database for a while because I feel like I did not have a chance to dive deep into handling data in general. That is why, in my final project, I decided to cooperate [Spoonaclar API](https://spoonacular.com/) into my app. Honestly, I was freaking out at the beginning of building this app: I had no idea how to send user's input from React to Rails nor how do I use that input to perform a GET request to my chosen API for data and send it back to React. But after some research and lectures, I finally figured it out and today, I made this tutorial to walk you through it step by step. A general flow of how React communicates with Rails:

![data flow](https://dev-to-uploads.s3.amazonaws.com/i/ljpt8vie5do16hwqn5yc.png)

Okay, let's dive into it:

##**Create React app:**
There are several ways to generate a React app, there is no correct way to do it, but I usually use a project generator tool called [create-react-app](https://github.com/facebook/create-react-app), developed by Facebook. To install:
```ruby
npm install -g create-react-app
```

Generate our app:
```ruby
create-react-app hipstew_frontend
```

In my frontend, I created a components folder to store all my future components. For this example, I created a SearchBar.js class component which has a controlled form to keep track of user's input and a submit function inherited from App.js:

```react
import React from 'react'

import Button from 'react-bootstrap/Button'
import Form from 'react-bootstrap/Form'
import InputGroup from 'react-bootstrap/InputGroup'

export default class SearchBar extends React.Component {

    state={
        ingredient: ''
    }

    handleChange = (e) => this.setState({ ingredient: e.target.value})

    render(){
        return(
            <div>
                <InputGroup className="mb-3 search-bar">
                    <Form.Control
                        className="mb-2"
                        id="inlineFormInputName2"
                        placeholder="Ingredient name (beef, tomato, etc)"
                        value={this.state.ingredient}
                        onChange={this.handleChange}
                    />
                </InputGroup>

                <InputGroup.Append>                          
                    <Button 
                        variant='primary' 
                        type="submit" 
                        className="mb-2" 
                        onClick={(e) => {
                            this.props.handleSubmit(e, this.state.ingredient)
                            this.setState({ ingredient: '' })
                        }}>
                        Submit
                    </Button>
                </InputGroup.Append>
            </div>
        )
    }
}
```
_Note: I used some React Bootstrap here but it's optional! You can always user `<form>` and `<button>` instead of `<InputGroup>` and `<Button>`_

App.js component:

```react
import React from 'react'
import SearchBar from './SearchBar'
import RecipeList from './RecipeList'

export default class App extends React.Component {

    state={
        ingredients: '',
        recipe: ''
    }

    handleSubmit = (e, ingredients) => { 
        e.preventDefault()
        this.setState({ ingredients, recipe: 'result' })
    }

    render(){
        return(
            <div>
                <SearchBar  handleSubmit={this.handleSubmit} />

                { this.state.recipe === 'result' ? <RecipeList ingredients={this.state.ingredients} /> : null }
            </div>
        )
    }
}
```

In my App component, I used a recipe state to conditionally render RecipeList component. This component will only be rendered if a user submitted information in search bar.

RecipeList component:

```react
import React from 'react'

export default class RecipeList extends React.Component {

    state={
        recipes: [],
        error: null
    }

    componentDidMount(){

        fetch(`http://localhost:3000/getrecipe?ingredients=${this.props.ingredients}`)
        .then(resp => resp.json())
        .then(data => {
            // handling errors if any.
            if (data.error){
                this.setState({ error: data.error })
            } else {
                this.setState({ recipes: data })
            }
        })

    }

    render(){
        return(
            // render recipe based on data got back from rails.
        )
    }
}
```
This is where we actually send user's input to our Rails backend! I did a fetch request to a custom endpoint: '/getrecipe', but how do we send out user's input as params? Pretty similar to API endpoints, we can add a '?' + params name=data to send data to backend. For this case: `/getrecipe?ingredients=${this.props.ingredients}`. I also use `componentDidMount` lifecycle component to make sure RecipeList receives search's result before it's rendered (read more about lifecycle [here](https://reactjs.org/docs/react-component.html)). And that's the basic set up for our frontend. Let's also prepare our Rails app!

##**Create Rails app:**
```ruby
rails new hipstew_backend --api -T --database=postgresql
```
In this example, I use Postgres instead of SQLite, but this part is optional. If you do want to use Postgres, make sure you downloaded it [here](https://www.postgresql.org/) and have it running during this progress.

In our backend set up, beside my other models' controllers, I generated an extra controller dedicated to making requests to Spoonacular API, I named it spoonacular_api_controller but you can call it anything you want, make sure to use snake case for it :
```ruby
rails g controller spoonacular_api_controller --no-test-framework
```
This would give us a barebone controller, nothing special yet. Let's add a function in our controller that performs a GET request to our API:

```ruby
require './lib/key.rb'

class SpoonacularApiController < ApplicationController
  BASE_URL='https://api.spoonacular.com'

  def get_recipe
    ingredientString = params["ingredients"].split(', ').map do |ing|
      if ing.include?(' ') 
        ing[' '] = '-' 
      end 
      ing + '%2C'
    end.join()

    url = "#{BASE_URL}/recipes/findByIngredients?apiKey=#{API_KEY}&ingredients=#{ingredientString}&number=54"
    
    response = HTTP.get(url)
    data = response.parse

    if data.length === 0
      render json: {error: "There is no recipe for #{params["ingredients"]}. Please try another search term."}
    else
      render json: data
    end

  end

end
```

And add a custom route in our config/routes.rb:
```ruby
get "/getrecipe", to: 'spoonacular_api#get_recipe'
```

This indicates whenever we fetch to '/getrecipe' endpoint, 'get_recipe' will be invoked! 

At this point, if we put a `byebug` inside `get_recipe` and type `params["ingredients"]`, we should get back our user's input from React app! I added ingredientString to make sure all ingredients are in camel-case.

_Additional Note: make sure you store your API key in a separate file and include that file in .gitignore to keep your API key a secret. I stored mine in lib/key.rb!_

Here is a my app in action using the example above:

<img src="https://media.giphy.com/media/HGbzFyU0iWZOxXi0ae/giphy.gif" alt="hipstew-demo" >

Thank you for reading, feel free to comment below for further discussion. Stay tune for more :)!!