DEV Community

vinodchauhan7
vinodchauhan7

Posted on

MERN App using GraphQL via Apollo-Client,(React Hooks). (Part-3) FrontEnd

Alt Text

GitHub Link : MERN-app-using-graphql-apollo-client

So finally we are done with our express server in part-2 of this series. Now we will focus on client(frontend) of this MERN app.

MERN App using GraphQL via Apollo-Client,(React Hooks). (Part-1)

MERN App using GraphQL via Apollo-Client,(React Hooks). (Part-2)

To create our client we will choose 'create-react-app' library to create a react application for us.

To install 'create-react-app' : npm install -g create-react-app

In the Vehicle-Management System in which we have earlier created our backend 'server' folder, hit the following command in terminal.

npx create-react-app my-client
cd my-client
//Delete each and every file in 'src' folder except //'app.js','index.css','index.js'


//App.js
import React from "react";

function App() {
  return (
    <div className="App">
      <h1>List of Cars</h1>
    </div>
  );
}

export default App;

To run react-app hit : npm start

Above command will open react-app on browser using localhost:3000.

Before Implementing our frontend we need to install our dependencies of apollo-client & graphql.

npm install apollo-boost react-apollo graphql --save.

Right now we are in a good shape to start our client implementation.

apollo-boost

It provide us ApolloClient which help us to connect express-graphQL server which is running on 'localhost:4000/graphql'.

react-apollo

It provide us ApolloProvider which help react app to connect with data retrieved by ApolloClient.

//app.js
import React from "react";
import ApolloClient from "apollo-boost"; //connect with our server which is running at backend
import { ApolloProvider } from "react-apollo"; // Connect react with apollo.

//Using ApolloClient to connect with server
const client = new ApolloClient({
  uri: "http://localhost:4000/graphql"
});

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <h1>List of Cars</h1>
      </div>
    </ApolloProvider>
  );
}

export default App;

In above code, If you have worked on 'React-Redux' then you can simply understand how data is transferred from parent to it child devices. Those who don't know about the React-Redux, dont worry child-components will get this client data as props.

To understand about React-Redux : React-Redux Basic Understanding

Crud-Operation in React-Redux : Crud in React-Redux

a) Create 'CarList' component.

First of all create a 'components' folder inside 'src' folder. And inside 'components' folder create file 'CarList.js'

import React from "react";

const CarList = props => {
  return (
    <>
      <ul id="carList">
        <li>CarName</li>
      </ul>
    </>
  );
};

export default CarList;

Import this component in App.js file to show it in our app.

//App.js
import React from "react";
import ApolloClient from "apollo-boost"; //connect with our server which is running at backend
import { ApolloProvider } from "react-apollo"; // Connect react with apollo.
import CarList from "./components/CarList";

//Using ApolloClient to connect with server
const client = new ApolloClient({
  uri: "http://localhost:4000/graphql"
});

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <h1>List of Cars</h1>
        <CarList></CarList>
      </div>
    </ApolloProvider>
  );
}

export default App;

b) Create Graphql Query and embed result it in CarList component.

At first, create a folder name 'queries' inside src folder. Inside 'queries' folder create a new file 'queries.js'.
We will use 'gql' const from 'apollo-boost'.

//queries.js
import { gql } from "apollo-boost";

const getCarsQuery = gql`
  {
    cars {
      name
      id
    }
  }
`;

export default getCarsQuery;

In above file, We make a same query which we are creating in graphql tool on 'localhost:4000/graphql' server to get the list of cars with details.

Now connect 'getCarsQuery' in CarList component

//CarList
import React from "react";
import { graphql } from "react-apollo";
import getCarsQuery from "./../queries/queries";

const CarList = props => {
  console.log(props); //check in the browser to see this values.
  return (
    <>
      <ul id="carList">
        <li>CarName</li>
      </ul>
    </>
  );
};

export default graphql(getCarsQuery)(CarList); //HOC

Look at the bottom of the file, we are passing our query in graphql function to process it and get data from server.

If you guys dont know how this thing work or What it is called? Then look for my article on this High Order Components in React

Right now our console is giving errors and the main reason is we need cors() implementation on our server which is running in other terminal.

In our server code, open app.js file.

//Add two lines 

const cors = require("cors");

/**
 * Cors added as middleware
 */
app.use(cors());

It will restart again and now see in frontend app console to see data.

Alt Text

props.data.loading is true when server hitting the mongodb and it become 'false' when data is loaded'.

c) Iteration of cars datalist

import React from "react";
import { graphql } from "react-apollo";
import getCarsQuery from "./../queries/queries";

const CarList = props => {
  console.log(props);

  const displayCars = () => {
    var data = props.data;
    if (data.loading) {
      return <div>Loading Cars...</div>;
    } else {
      return data.cars.map(car => {
        return <li key={car.id}>{car.name}</li>;
      });
    }
  };

  return (
    <>
      <ul id="carList">{displayCars()}</ul>
    </>
  );
};

export default graphql(getCarsQuery)(CarList);

d) Add new Car

To add a new Car create 'AddCar' component in components folder.

import React from "react";
import { getOwnersQuery } from "./../queries/queries";
import { graphql } from "react-apollo";

const AddCar = props => {
  const getOwners = () => {
    var data = props.data;
    if (data.loading) {
      return <option disabled>Owner loading...</option>;
    } else {
      return data.owners.map(owner => {
        return (
          <option key={owner.id} value={owner.id}>
            {owner.name}
          </option>
        );
      });
    } //esle ends here
  };

  return (
    <>
      <form>
        <div className="field">
          <label>Car Name</label>
          <input type="text" name="carName"></input>
        </div>
        <div className="field">
          <label>Model</label>
          <input type="number" name="model"></input>
        </div>
        <div className="field">
          <label>Company:</label>
          <input type="text" name="company"></input>
        </div>
        <div className="field">
          <label>Owner:</label>
          <select>
            <option>Select Owner</option>
            {getOwners(props)}
          </select>
        </div>
        <button>AddCar</button>
      </form>
    </>
  );
};

export default graphql(getOwnersQuery)(AddCar);

Create getOwnersQuery in queries.js file

import { gql } from "apollo-boost";

const getCarsQuery = gql`
  {
    cars {
      name
      id
    }
  }
`;

const getOwnersQuery = gql`
  {
    owners {
      name
      id
    }
  }
`;

export { getCarsQuery, getOwnersQuery };

** Now add entry of AddCar.js file in app.js**

function App() {
  return (
    <ApolloProvider client={client}>
      <div className="App">
        <h1>List of Cars</h1>
        <CarList></CarList>
        <AddCar></AddCar>
      </div>
    </ApolloProvider>
  );
}

Alt Text

e) Make CustomHook to handle Form Data

Create a folder 'hooks' inside the 'src' folder.

// handleFormHook.js
import React from "react";

const HandleFormHook = callback => {
  const [inputs, setInputs] = React.useState({});

  const handleSubmit = event => {
    if (event) {
      event.preventDefault();
      console.log(inputs);
    }
    callback();
  };
  const handleInputChange = event => {
    event.persist();
    setInputs(inputs => ({
      ...inputs,
      [event.target.name]: event.target.value
    }));
  };
  return {
    handleSubmit,
    handleInputChange,
    inputs
  };
};

export default HandleFormHook;

Now Update AddCar.js component to use this Custom Hook.

//AddCar.js
import React from "react";
import { getOwnersQuery } from "./../queries/queries";
import { graphql } from "react-apollo";
import HandleFormHook from "./../hooks/handleFormHook";

const AddCar = props => {
  const getFormData = () => {
    console.log(`${inputs}`);
  };

  const { inputs, handleInputChange, handleSubmit } = HandleFormHook(
    getFormData
  );

  const getOwners = () => {
    var data = props.data;
    if (data.loading) {
      return <option disabled>Owner loading...</option>;
    } else {
      return data.owners.map(owner => {
        return (
          <option key={owner.id} value={owner.id}>
            {owner.name}
          </option>
        );
      });
    } //esle ends here
  };

  return (
    <>
      <form onSubmit={handleSubmit}>
        <div className="field">
          <label>Car Name</label>
          <input
            type="text"
            name="carName"
            onChange={handleInputChange}
            value={inputs.carName}
          ></input>
        </div>
        <div className="field">
          <label>Model</label>
          <input
            type="number"
            name="model"
            onChange={handleInputChange}
            value={inputs.model}
          ></input>
        </div>
        <div className="field">
          <label>Company:</label>
          <input
            type="text"
            name="company"
            onChange={handleInputChange}
            value={inputs.company}
          ></input>
        </div>
        <div className="field">
          <label>Owner:</label>
          <select
            name="owner"
            onChange={handleInputChange}
            value={inputs.owner}
          >
            <option>Select Owner</option>
            {getOwners(props)}
          </select>
        </div>
        <button>AddCar</button>
      </form>
    </>
  );
};

export default graphql(getOwnersQuery)(AddCar);

getFormData() will get all the values of the form data.Check console for result.

To understand about hooks : Hooks (Make Custom Hooks)

Advanced Hooks demo

f) Hit Mutation code.

Install > npm install recompose --save

Add Mutation query in queries.js file

//queries.js

//mutation function getting arguments from calling function and passing to //addCar.
const AddCarMutation = gql`
  mutation($name: String!, $model: Int!, $company: String!, $ownerId: ID!) {
    addCar(name: $name, model: $model, company: $company, ownerId: $ownerId) {
      name
      id
    }
  }
`;

export { getCarsQuery, getOwnersQuery, AddCarMutation };

Now change the AddCar.js file to hit the addCar mutation on server and add updated list.

import React from "react";
import { compose } from "recompose";
import {
  getOwnersQuery,
  AddCarMutation,
  getCarsQuery
} from "./../queries/queries";
import { graphql } from "react-apollo";
import HandleFormHook from "./../hooks/handleFormHook";

const AddCar = props => {
  const getFormData = () => {
    console.log(`${inputs}`);
//Hitting AddCarMutation with arguments.
    props.AddCarMutation({
      variables: {
        name: inputs.carName,
        model: parseInt(inputs.model),
        company: inputs.company,
        ownerId: inputs.owner
      },
      refetchQueries: [{ query: getCarsQuery }] // to update carsQuery on CarList.js
    });
  };

  const { inputs, handleInputChange, handleSubmit } = HandleFormHook(
    getFormData
  );

  const getOwners = () => {
    var data = props.getOwnersQuery;
    if (data.loading) {
      return <option disabled>Owner loading...</option>;
    } else {
      return data.owners.map(owner => {
        return (
          <option key={owner.id} value={owner.id}>
            {owner.name}
          </option>
        );
      });
    } //esle ends here
  };

  return (
    <>
      <form onSubmit={handleSubmit}>
        <div className="field">
          <label>Car Name</label>
          <input
            type="text"
            name="carName"
            onChange={handleInputChange}
            value={inputs.carName}
          ></input>
        </div>
        <div className="field">
          <label>Model</label>
          <input
            type="number"
            name="model"
            onChange={handleInputChange}
            value={inputs.model}
          ></input>
        </div>
        <div className="field">
          <label>Company:</label>
          <input
            type="text"
            name="company"
            onChange={handleInputChange}
            value={inputs.company}
          ></input>
        </div>
        <div className="field">
          <label>Owner:</label>
          <select
            name="owner"
            onChange={handleInputChange}
            value={inputs.owner}
          >
            <option>Select Owner</option>
            {getOwners(props)}
          </select>
        </div>
        <button>AddCar</button>
      </form>
    </>
  );
};

//For hitting two queries we need compose library.
export default compose(
  graphql(getOwnersQuery, { name: "getOwnersQuery" }),
  graphql(AddCarMutation, { name: "AddCarMutation" })
)(AddCar);

In below screenshot, you can see I add a new car 'Ciaz'.
Alt Text

g) Now add CarDetails.js component to show car details.

Add new Query in queries.js file

const getCarQuery = gql`
  query($id: ID!) {
    car(id: $id) {
      id
      name
      model
      company
      owner {
        id
        name
        age
        cars {
          name
          company
          id
        }
      }
    }
  }
`;

export { getCarsQuery, getOwnersQuery, AddCarMutation, getCarQuery };

Now add CarDetails.js

import React from "react";
import { graphql } from "react-apollo";
import { getCarQuery } from "./../queries/queries";

const CarDetails = props => {
  console.log(props.carId.Id);

  const getCarDetails = () => {
    const { car } = props.data;
    console.log(car);
    if (car) {
      return (
        <div>
          <h2>{car.name}</h2>
          <p>model : {car.model}</p>
          <p>company : {car.company}</p>
          <p>owner : {car.owner.name}</p>
          <p>All cars by this owner :</p>
          <ul>
            {car.owner.cars.map(item => {
              return <li key={item.id}>{item.name}</li>;
            })}
          </ul>
        </div>
      );
    } else {
      return <div>No Car Selected</div>;
    }
  };

  return <div id="carDetails">{getCarDetails()}</div>;
};


//Passing carId in getCarQuery 
export default graphql(getCarQuery, {
  options: props => {
    return {
      variables: {
        id: props.carId.Id
      }
    };
  }
})(CarDetails);

Finally update the CarList.js from where we can select the cars.

//CarList.js
import React from "react";
import { graphql } from "react-apollo";
import { getCarsQuery } from "./../queries/queries";
import CarDetails from "./CarDetails";

const CarList = props => {
  console.log(props);
  const [Id, setCar] = React.useState(0);

  const displayCars = () => {
    var data = props.data;
    if (data.loading) {
      return <div>Loading Cars...</div>;
    } else {
      return data.cars.map(car => {
        return (
          <li key={car.id} onClick={e => setCar({ Id: car.id })}>
            {car.name}
          </li>
        );
      });
    }
  };

  return (
    <>
      <ul id="carList">{displayCars()}</ul>
      <CarDetails carId={Id}></CarDetails>
    </>
  );
};

export default graphql(getCarsQuery)(CarList);

Click on any Car and you will get details of that car and its owner.

Final Output

Alt Text

Sorry guys for not adding any Css part here.

Whoaaa! we have just completed our MERN app using graphql via apollo-client.

Hope you learnt something from this articles.

Oldest comments (4)

Collapse
 
dance2die profile image
Sung M. Kim

Thanks for the series @vinodchauhan7 ~

You can link the series with series tag in the front matter.
You can refer to the Editor Guide 😉.

Collapse
 
vinodchauhan7 profile image
vinodchauhan7

Ok Kim, will do that surely.

Collapse
 
ragaviraghupathi profile image
ragavir

Please add update functionality

Collapse
 
hoale240803 profile image
hoale240803

tks so much guy!