TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - part 4

CREATE REACT APP - THE RUNDOWN

create-react-app creates a series of files and folders as it instantiates and creates a Webpack bundled React application. For those who may be unfamiliar with the scaffold created from create-react-app , we’ll spend this lesson going through the generated client/ application.

create-react-app has created an application scaffold that looks like the following:

client/
  node_modules/
  public/
  src/
  .gitignore
  package-lock.json
  package.json
  README.md
  tsconfig.json

NODE_MODULES/

The node_modules/ directory in our application refers to all the different JavaScript libraries that have been installed in our application.

PUBLIC/

public/index.html

The public/ folder holds the favicon asset as well as the root markup page of the application - the index.html file. When we take a look at the public/index.html file, we can see a simple HTML file that contains a <head> element and a <body> element. The <head> tag specifies standard viewport settings and references the favicon asset in the public/ folder.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
    <title>React App</title>
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

In the <head> tag of our public/index.html file, we can see some documentation and use of a manifest.json file. A manifest JSON file helps provide metadata for when an app is installed on a user’s mobile device, which makes a web application more progressive and is set up in the public/manifest.json file of our app. This ties into some of the boilerplate code create-react-app gives us to help create a Progressive Web Application.

Some comments exist in the <head> tag that tells us how the PUBLIC_URL tag behaves. In the create-react-app setting, PUBLIC_URL is replaced with the URL of the public folder during build which helps reference assets within the public/ folder. We won’t be making any changes to the public/index.html file except for renaming the title of our application to the name of our app - TinyHouse .

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
    <title>TinyHouse</title>
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

In the <body> element of the public/index.html file, we’re presented with a <noscript> tag followed by the <div id="root"></div> element.

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
  </body>
</html>

The content within <noscript> is displayed to the user when the user has disabled scripts in the browser or is using a browser that has an unsupported script. The <div> element with the id of root is where our entire React application will be mounted onto. This is the element that’s referenced in our React render() function with which we’ll see shortly. Finally, we have some comments in the <body> element that tells us upon building the application, the bundled scripts of our app will be placed into this <body> tag.

SRC/

We’ll hardly find the need to be making any significant change in the surrounding files that involve the set up of our application. The vast majority of the time, we’ll be working within the src/ directory that contains the TypeScript, React, and CSS files that we’ll be working directly with.

src/index.tsx

The src/index.tsx file is the root React/TypeScript file in our application. If you’ve never used TypeScript and React before, the .tsx file extension may seem new to you. JSX (or otherwise known as JavaScript XML) is an extension that allows us to write JavaScript that looks like HTML.

We can see the use of JSX in the ReactDOM.render() function where the first argument being passed is the <App /> component.

ReactDOM.render(<App />, document.getElementById("root"));

The declaration of <App /> in the render() function is JSX - XML-like syntax in our JavaScript file. In standard React, we’re able to use JSX within files that have the normal .js file extension though certain people prefer to use the .jsx file extension to denote the React JavaScript files that contain JSX.

If we intend on using JSX within our TypeScript files, we must name our files with the .tsx file extension. The other requirement to using JSX in TypeScript is to have the jsx option enabled in our TypeScript configuration, which we’ll see has already been done in our client project’s tsconfig.json file.

The ReactDOM.render() function helps render the parent <App /> component on the root element in our public/index.html file. The render function is from the ReactDOM library and takes up to three arguments with which the first two is often defined. The first argument is essentially the root level element we’d like our React application to render (which is currently the <App /> component). The second argument is where to render (i.e. mount) it with which in this case is the reference to the DOM node with the id of root .

The third argument of the render() function takes a callback function that when provided runs the callback after the component is mounted or updated. We won’t need to run this callback function so we won’t introduce it. The only change we’ll make is more of a preference - instead of importing the ReactDOM global, we can directly import the render() function that’s also being exported from the react-dom library.

Our src/index.tsx file will then look like the following:

import React from "react";
import { render } from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

render(<App />, document.getElementById("root"));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

At the end of the src/index.ts file, there’s exists an additional reference to help in the introduction of a service worker. Service workers are scripts that run in the browser to help applications be more progressive by introducing features like asset caching and push notifications. We’re not going to spend much time in this course explaining how service workers and Progressive Web Apps behave, so we’ll leave this in its current state for now which is essentially the unregistered state of the service worker defined in the src/service-worker.ts file.

src/react-app-env.d.ts

In the src/ folder exists a react-app-env.d.ts declaration file. Declaration files are usually denoted with the .d.ts file extension and are files that describe the typings of a JavaScript file that exists elsewhere.

client/src/react-app-env.d.ts

/// <reference types="react-scripts" />

A triple-slash directive ( /// ) is used in the react-app-env.d.ts file and is a unique TypeScript capability that pertains to single-line comments that contain a single XML tag. Triple-slash directives instruct the TypeScript compiler to include additional files in the compilation process. The directive in the react-app-env.d.ts file instructs the compiler to include and reference the type declarations of the react-scripts library which is the library used to build, test, and run our React application.

With that said, there has been discussion among the community as to why this particular declaration file is placed within the src/ folder, and not in a types/ folder of sorts. An open issue currently exists where the React team is currently looking to place a comment in the react-app-env.d.ts file to better explain the purpose of this declaration file.

Rest of src/

The rest of the src/ folder contains a few of the other files responsible in setting up the UI we see when we start our client application.

  • App.css : CSS file used in the <App> component.
  • App.test.tsx : The test file of the <App> component that demonstrates the use of the Jest testing framework installed in our application.
  • App.tsx : The parent <App> component that contains the boilerplate code shown in our UI.
  • index.css : CSS file used in the src/index.tsx file.
  • logo.svg : The React logo in SVG format used in the <App> component.

We’ll remove the index.css , App.tsx , App.test.tsx , App.css , and the logo.svg file from the src/ folder. In the src/index.tsx file, we’ll remove the imports of the removed files and in the render() function, simply render a single <div> element that says 'Hello World!' for now.

client/src/index.tsx

import React from "react";
import { render } from "react-dom";
import * as serviceWorker from "./serviceWorker";

render(<div>Hello World!</div>, document.getElementById("root"));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

.GITIGNORE

The .gitignore file dictates the files we don’t want to check in with git . This includes node_modules/ , the bundled build/ folder when created, and any .env files.

PACKAGE-LOCK.JSON

The package-lock.json file is an automatically generated JSON file that stores a dependency tree that highlights the dependencies installed from the package.json file at that moment in time. It should be committed to source code but we’ll never make changes to it directly.

PACKAGE.JSON

The package.json file lists all the locally installed npm packages as well as the npm script commands that can be made.

client/package.json

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@types/jest": "24.0.16",
    "@types/node": "12.6.9",
    "@types/react": "16.8.24",
    "@types/react-dom": "16.8.5",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.1",
    "typescript": "3.5.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

create-react-app provides four script commands - start , build , test , and eject .

client/package.json

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  • start : runs our local Webpack server for development.
  • build : bundles our application in a build folder ready for deployment.
  • test : Runs the test files in our workspace with the help of the Jest testing framework.
  • eject : enables us to abandon the configuration that create-react-app provides which subsequently allows us to tweak the application Webpack configuration to our liking.

Ejecting is often done when one either feels that their project ‘outgrows’ the configuration create-react-app sets up or needs some special type of configuration. It’s always important to remember that once a create-react-app application is ejected - one can never go back to the application’s original state.

In this course, we won’t find the need to eject our application workspace.

All our application script commands use the react-scripts dependency available in our app to run the intended command. react-scripts is essentially the core service that’s installed into every create-react-app project. react-scripts specifies all the app’s development dependencies like Webpack, Babel, and ESLint. It also contains “scripts” that glue these dependencies together to help us start , build , test , and eject our application.

In the dependencies section of the package.json file, we can see all the locally installed dependencies in our application.

client/package.json

  "dependencies": {
    "@types/jest": "24.0.16",
    "@types/node": "12.6.9",
    "@types/react": "16.8.24",
    "@types/react-dom": "16.8.5",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.1",
    "typescript": "3.5.3"
  },

The eslintConfig property of package.json is one way to specify the ESLint configuration within an application (as opposed to having an .eslintrc file in the root of the project directory).

client/package.json

  "eslintConfig": {
    "extends": "react-app"
  },

The create-react-app scaffold sets the ESLint configuration to extend a specific package known as react-app which was created for create-react-app projects.

And finally, browserslist works with a few other tools (such as the application’s postcss configuration as well as autoprefixer ) to dictate which browsers our application will support.

client/package.json

  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }

README.MD

The README.md file explains the create-react-app project as well as provides details behind each of the available script commands.

TSCONFIG.JSON

create-react-app creates a tsconfig.json file for our project by default. Like we’ve seen when working with the server/ project, the tsconfig.json file guides the TypeScript compiler with options required to compile the TypeScript project. Though we won’t alter any of the configuration create-react-app has set up for us, we’ll quickly summarize the options that have been declared:

target :

target specifies the target JavaScript version the compiler will output. The target output is es5 which would be compatible for both modern and older browsers.

"target": "es5",

lib :

lib indicates the list of library files to be included in the compilation. lib is important because it helps us decouple the compile target and the library support we would want. For example, the Promise keyword doesn’t exist in ES5 and if we intend to use it, TypeScript would display a warning since our compile target is es5 . By specifying a lib option of esnext , we can still have our compiler compile down to ES5 while using the Promise keyword with no issues.

The create-react-app scaffold sets the lib option to ["dom", "dom.iterable", "esnext"] . dom allows us to directly manipulate the DOM while dom.iterable gives us the ability to iterate over a list of DOM nodes.

"lib": [
  "dom",
  "dom.iterable",
  "esnext"
],

allowJs :

allowJs allows JavaScript files to be compiled.

"allowJs": true,

skipLibCheck :

skipLibCheck skips type checking of all declaration files.

"skipLibCheck": true,

esModuleInterop | allowSyntheticDefaultImports :

esModuleInterop and allowSyntheticDefaultImports combined gives us the ability to allow default imports of modules that have no default export.

"esModuleInterop": true,
"allowSyntheticDefaultImports": true,

strict :

strict enables a series of strict type checking options such as noImplicitAny , noImplicitThis , strictNullChecks , etc.

"strict": true,

forceConsistentCasingInFileNames :

forceConsistentCasingInFileNames disallows inconsistently cased references to the same file names.

"forceConsistentCasingInFileNames": true,

module :

module refers to the module manager to be used in the compiled JavaScript output. systemjs and commonjs among other options can be specified but esnext is defined in our set-up.

"module": "esnext",

moduleResolution :

moduleResolution refers to the strategy used to resolve module declaration files (i.e. the type definition files for external JavaScript code). With the node approach, they’re simply loaded from the node_modules/ folder.

"moduleResolution": "node",

resolveJsonModule :

resolveJsonModule includes the modules with the .json extension in the compilation process.

"resolveJsonModule": true,

isolatedModules :

isolatedModules allows for the transpiling of each file as a separate module.

"isolatedModules": true,

noEmit :

noEmit specifies we don’t want the emitting of any output during the compilation process.

"noEmit": true,

jsx :

jsx allows us to state the support of JSX within TypeScript files. The preserve or react option states that JSX should be supported within TypeScript JSX files.

"jsx": "preserve"

include :

include specifies the files or folders that we would want to be included during the TypeScript compilation process. In our tsconfig.json file, src/ is being specified which states that any TypeScript code within the src/ folder will be compiled to normal JavaScript. Everything outside of the src/ folder won’t be included within the TypeScript compilation process.

If you haven’t fully understood all the options specified in the tsconfig.json file, this isn’t a problem. The main takeaways are the tsconfig.json file dictates the details behind how we want to compile our TypeScript code and create-react-app provides a detailed configuration out of the box. If you’re interested in seeing all the different possible compiler options that we could use, be sure to check out the TypeScript compiler options documentation.

As of now, the only significant change we’ve made to our create-react-app scaffold is in the src/ folder where we’ve removed the initial boilerplate code and had the render() function in the src/index.tsx file simply render a 'Hello World!' message.

To verify our changes, we’ll start our client application:

client $: npm run start

And when we head to http://localhost:3000 , we’ll be presented with the 'Hello World!' message.

MODULE 6 INTRODUCTION

We have a new React project now created. In this module, we’ll have our client make API requests to our server using GraphQL.

In detail we’ll:

  • First, construct a functional React component.
  • Use TypeScript to check the props of the component.
  • See how we can go about defining the type of the functional component created.
  • Create a fetch() function to help query our GraphQL API.
  • See how TypeScript generics can help abstract the type of retrieved data from the API.
  • Finally, use the fetch() function we’ve created to run the deleteListing mutation.

THE LISTINGS COMPONENT

Our eventual objective is to render and display a list of listing items in the UI that’s retrieved and obtained from the GraphQL API we’ve set up in the server. Before we concern ourselves with the custom styling and UI of the page, we’ll first attempt to have data be available and displayed on the client.

First and foremost, we’ll begin working on a new component that we’ll create called <Listings> with which we’re going to keep in a sections/ folder in our src/ directory. Let’s create the sections/ folder and an index.ts file in src/sections/ . We’ll also create a Listings/ folder within src/sections/ that has a Listings.tsx file and an index.ts file.

client/
  src/
    sections/
      Listings/
        index.ts
        Listings.tsx
      index.ts
  // ...

Since we’re creating a single <Listings> component, these additional folders may appear unnecessary. When we start building the application in more detail (especially in Part 2 of this course), we’ll want to structure and separate all the components in our app in a neat and structured format.

We’ll aim to separate and declare “parent level” components within their own section in the sections/ folder. Within a module (i.e. folder) in sections/ , we’ll explicitly namespace and contain each module within itself since each section module will contain all the accompanying files for that section such as the styles, child components, and additional assets relative to that module alone. The index.ts files are established within the root of each module to create explicit public interfaces for the ease of importing and exporting between modules. This will appear clearer when we start to introduce more code.

In the src/sections/Listings/Listings.tsx file, we’ll import React from the react library.

client/src/sections/Listings/Listings.tsx

import React from "react";

We’ll then export a functional component with the name of Listings . We’ll state the <Listings> component is to simply return a <h2> tag that says 'TinyHouse Listings' . Though we’re not directly using the React import declared, we have to import React if we ever want to define JSX in a component.

import React from "react";

export const Listings = () => {
  return <h2>TinyHouse Listings</h2>;
};

In the corresponding index.ts file within src/sections/Listings/ , we’ll re-export the <Listings> component.

client/src/sections/Listings/index.ts

export * from "./Listings";

In the src/sections/index.ts file, we’ll also re-export the <Listings> component from the sections/ folder.

client/src/sections/index.ts

export * from "./Listings";

The <Listings> component is now being exported from src/sections . In the src/index.tsx file, we’ll import the <Listings> component from the src/sections folder and render it in our application render() function.

client/src/index.tsx

import React from "react";
import { render } from "react-dom";
import { Listings } from "./sections";
import * as serviceWorker from "./serviceWorker";

render(<Listings />, document.getElementById("root"));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

When we start our application, we’ll see the 'TinyHouse Listings' header message instead of a 'Hello World!' message.

PROPS & TYPESCRIPT

There are two main types of components in React - Class components and Functional components. Functional (or Function) components are functions that can accept props and return JSX. In our <Listings> component we can see that the Listings component doesn’t receive props yet, but returns JSX - which makes it a valid functional component.

import React from "react";

export const Listings = () => {
  return <h2>TinyHouse Listings</h2>;
};

With that said, let’s attempt to have the <Listings> component now receive some props. We’ll look to have the 'TinyHouse Listings' text be given to the <Listings> component from the parent. The parent, in this context, is the root src/index.tsx file. In the src/index.tsx file where <Listings> is being rendered, we’ll pass in a new prop labeled title that has a string value of 'TinyHouse Listings' .

client/src/index.tsx

render(
  <Listings title="TinyHouse Listings" />,
  document.getElementById("root")
);

TypeScript will throw an error since we haven’t stated that the <Listings> component is to accept a title prop of type string .

In the <Listings> component, we’ll specify that props are being passed down and the title field from props is to be used in the <h2> tag.

import React from "react";

export const Listings = props => {
  return <h2>{props.title}</h2>;
};

We’ll now get an error in the <Listings> component that says that the props argument has an implicit any type. We could explicitly type define this argument like we type define any function argument:

import React from "react";

export const Listings = (props: any) => {
  return <h2>{props.title}</h2>;
};

We’ve set props to the generic any type which tells the compiler the type of props is now explicitly defined. Our app will now work. However, any isn’t useful so let’s type define props as an object that expects a title field of type string :

import React from "react";

export const Listings = (props: { title: string }) => {
  return <h2>{props.title}</h2>;
};

Our application now works and the type of the title prop is more appropriately defined. When a component has a large number of props, specifying the type of props right where the argument is being defined can make things hard to read. So we can describe the shape of our props data as an interface above the component. We’ll call this interface Props .

With the Props interface defined, we’re now able to specify the type of our props with this newly created interface:

import React from "react";

interface Props {
  title: string;
}

export const Listings = (props: Props) => {
  return <h2>{props.title}</h2>;
};

We can go ahead and also destruct the title prop directly from the props argument.

client/src/sections/Listings/Listings.tsx

import React from "react";

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  return <h2>{title}</h2>;
};

When we hover over the declaration of the <Listings> component, we can see that TypeScript infers the <Listings> component to be a function that accepts props and returns a JSX Element . In essence, the simplest TypeScript definition of a Functional Component.

Since the <Listings> component renders the same text despite it coming from props, our UI should appear the same.

FUNCTIONAL COMPONENTS & TYPESCRIPT

Though TypeScript infers that the <Listings> component is a function that renders JSX (i.e. a functional component), we can take an additional step to specify an explicit type of the function component itself.

React offers specific unique types we can use if we want to enforce additional type definitions for functional components.

Previously, functional components were recognized to be stateless presentational components since functional components could never keep track of its own state. As a result, we would be able to define a functional component with the SFC type or the StatelessComponent type which still exists today and can be exported from the type definitions of react .

import React, { StatelessComponent, SFC } from "react";

Though they exist, React has deprecated the StatelessComponent and SFC types. This is because functional components can now keep track of its own state with the emergence of React Hooks.

The FunctionComponent type (or the shorthand version FC ) is encouraged if we intend to take advantage of additional typings of our functional components.

To visualize the difference the FunctionComponent type brings, we’ll replicate the <Listings> component with another component we’ll label <Listings2> . We’ll define the type of the <Listings2> component as the FunctionComponent interface type.

import React, { FunctionComponent } from "react";

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  return <h2>{title}</h2>;
};

export const Listings2: FunctionComponent = ({ title }: Props) => {
  return <h2>{title}</h2>;
};

We’ll see a TypeScript error emerge from the <Listings2> component where it states that the title property is missing as part of the Props type of the component.

If we take a look at the FunctionComponent interface type, we’ll get a clearer understanding of why this error occurs. The FunctionComponent type is an interface that accepts a couple of properties. The first and only required property is a function that accepts props and returns a ReactElement (or null ).

// from the react type definitions file

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement | null;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  displayName?: string;
}

In VSCode, the CMD + Click shortcut allows you to navigate and inspect the type definitions of interfaces and type aliases being used.

The FunctionComponent is a generic that accepts a type variable denoted with the letter P that has a default value of an empty object type. Though the FunctionComponent interface doesn’t require the P type variable to be defined, it passes the P type variable to the PropsWithChildren type used for the props argument of the first function property.

When we take a look at the PropsWithChildren type in our React type definitions file, we can see that PropsWithChildren is a type alias that accepts a type variable ( P ) and combines the P type variable with a new object type that has a children property. The capability of combining multiple types into one is a TypeScript capability known as intersection types.

// from the react type definitions file

type PropsWithChildren<P> = P & { children?: ReactNode };

The first required property of the FunctionComponent interface type is the function declaration of the function component (i.e. the function that accepts props and returns JSX). The props argument of that function is type defined as the PropsWithChildren type. When a type variable isn’t passed, the props object argument is recognized to only have the children property. As a result, when we attempt to access another property from our props (e.g title ), TypeScript displays an error.

To solve the error, we’ll need to pass in our Props interface type as the type variable of our FunctionComponent . By doing so, the props returned from our FunctionComponent will recognize the title property of type string. When using the FunctionComponent interface type, we also no longer need to specify the type of props from the argument itself.

import React, { FunctionComponent } from "react";

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  return <h2>{title}</h2>;
};

export const Listings2: FunctionComponent<Props> = ({ title }) => {
  return <h2>{title}</h2>;
};

At this moment the <Listings> and <Listings2> component behave identically to one another. So what does the FunctionComponent type give us?

CHILDREN

The props argument of a component with the FunctionComponent type will contain a children property of type ReactNode .

import React, { FunctionComponent } from "react";

// ...

export const Listings2: FunctionComponent<Props> = ({ title, children }) => {
  return <h2>{title}</h2>;
};

The children prop references the children JSX elements rendered within a component.

E.g. <ComponentA><ComponentB /></ComponentA> - <ComponentB> is the child (i.e. children) of <ComponentA>

If we wanted to replicate having a typed children prop in our original <Listings> component, we’ll need to import the ReactNode type and define a children property with ReactNode in our Props interface.

The FunctionComponent interface also provides a series of typed optional properties.

PROPTYPES

We can specify the propTypes of the component and propTypes will be able to infer what kind of props is expected to be passed in the component.

import React, { FunctionComponent } from "react";
import PropTypes from 'prop-types';

// ...

export const Listings2: FunctionComponent<Props> = ({ title }) => {
  return <h2>{title}</h2>;
};

Listings2.propTypes = {
  title: propTypes.string // title is recognized as a prop
  description: propTypes.description // description is NOT recognized as a prop
}

propTypes is a React feature that allows us to type-check props without using a JavaScript extension like TypeScript. Since we are using TypeScript to type-check our props, propTypes isn’t really helpful.

Like propTypes , the FunctionComponent interface helps type-check the contextTypes property which is used to type define the types of properties within context. Similar to propTypes , contextTypes isn’t helpful if we leverage TypeScript to define the types of our application’s context properties.

DEFAULTPROPS

React provides a defaultProps option to allow us to provide default values for props. With FunctionComponent , the props stated in the defaultProps property is type-checked.

import React, { FunctionComponent } from "react";

// ...

export const Listings2: FunctionComponent<Props> = ({ title }) => {
  return <h2>{title}</h2>;
};

Listings2.defaultProps = {
  title: "TinyHouse Listings", // title is recognized as a prop
  description: "The most fabolous listings in the city" // description is NOT recognized as a prop
};

defaultProps isn’t helpful as well since ES6 gives us the capability of defining default parameters.

import React, { FunctionComponent } from "react";

// ...

export const Listings2: FunctionComponent<Props> = ({ title = "TinyHouse Listings" }) => {
  return <h2>{title}</h2>;
};

DISPLAYNAME

Finally, the FunctionComponent interface states the type of the displayName property as a string . displayName is a component property that allows us to set the display name of components for debugging messages.

import React, { FunctionComponent } from "react";

// ...

export const Listings2: FunctionComponent<Props> = ({ title }) => {
  return <h2>{title}</h2>;
};

// displayName must be a string

Listings2.displayName = "Listings"; // Pass
Listings2.displayName = 5; // Error

displayName isn’t often used since by default, the display name of components will be inferred from the name of the function.

Though the FunctionComponent interface type is useful, it’s most helpful when we want to use and specify unique information within our components such as rendering children, or maybe setting the displayName , or possibly using propTypes / defaultProps (though we wouldn’t recommend these in a TypeScript + ES6 environment).

We don’t need the type functionality the FunctionComponent interface type gives us. As a result, we won’t use the FunctionComponent interface type and we’ll stick with the simple means of having our function components be inferred as functions that accept props and return JSX.

With that said, we’ll close this lesson by removing the import of the FunctionComponent type and the Listings2 component we’ve created.

QUERYING LISTINGS

We’ll look to make our first interaction with the local GraphQL API we’ve set up on the server. There’s a large number of tools in the ecosystem that is geared to help clients make GraphQL queries. And these tools provide a lot of value with which we’ll see shortly, but they’re not a requirement to making GraphQL requests.

In the next couple of lessons, we’ll be taking our time and looking to make our GraphQL requests through some simple means which will give us a good understanding of what it takes to make a GraphQL request.

Here’s a gameplan of what we’ll do to make our first request.

GAMEPLAN

We’ll invoke a POST HTTP method

GraphQL supports both the POST and GET methods with some requirements in each.

In traditional REST applications, POST and GET are denoted to invoke what kind of request is going to be made. In GraphQL, however, we address retrieving or mutating information with queries and mutations . The one main distinction between GraphQL queries or mutations is that mutations are executed one after the other while queries are executed in parallel. Since our GraphQL server is served over HTTP, we can make either a POST or GET request to invoke a GraphQL query or mutation.

Most GraphQL clients use the POST HTTP method to both retrieve and persist data with which we’ll be doing as well.

We’ll specify the content type of our POST request as application/json

We’ll pass our GraphQL documents (i.e. queries) as a JSON object.

We’ll reference the URL of the API endpoint when we make our requests

Our GraphQL API is served over a single endpoint ( http://localhost:9000/api ), so we’ll reference this single endpoint in all our requests.

SERVER FETCH()

We’ll be using the native browser fetch() method to make our GraphQL requests.

If you’re using the Internet Explorer browser to run the React project, the window fetch() method is unsupported. We’ll suggest using Chrome/Firefox or installing a fetch() polyfill library.

We’ll create our server fetch() function in a new lib/ folder within src/ .

client/
  src/
    lib/
    // ...
  // ...

The src/sections/ folder will be responsible for containing the main section components of our app. The src/lib/ folder will be where we introduce code that is to be most likely shared between the different components in our app.

We’ll create an api/ folder within lib/ . We’ll additionally create an index.ts and server.ts file inside the api/ folder.

client/
  src/
    lib/
      api/
        index.ts
        server.ts

In the src/lib/api/server.ts file, we’ll export a server const object that will remain empty for now.

export const server = {};

We’ll re-export the server object from the src/lib/api/index.ts file.

client/src/lib/api/index.ts

export * from "./server";

In the server object, we’ll define a property on the object that’s given a label of fetch . This property is to be a function.

export const server = {
  fetch: () => {}
};

In the server.fetch() function, we’ll use the window fetch() function to help make our request. We’ll assign the results of the window fetch() method to a constant variable we’ll call res (i.e. response).

export const server = {
  fetch: () => {
    const res = fetch();
  }
};

Window fetch() is a promise . We want the results of the promise to be assigned to the res constant variable. We’ll use the async / await syntax by making the server.fetch property function an async function and placing the await expression on the window fetch() function:

export const server = {
  fetch: async () => {
    const res = await fetch();
  }
};

When our server.fetch() function gets called, we’ll expect an object to contain the GraphQL document. We’ll label the GraphQL document as query regardless if a GraphQL query or mutation is being invoked.

Let’s create an interface for this object body, that we’ll call Body , that will have a query field of type string . We’ll dictate that the server.fetch() function is to accept a body argument of type Body .

interface Body {
  query: string;
}

export const server = {
  fetch: async (body: Body) => {
    const res = await fetch();
  }
};

With our server fetch() function expecting the request body object, we can begin to build out the window fetch() method.

/API PROXY

The first argument of the window fetch() method is a required argument that is the reference to the path of the resource we want to fetch. In our instance, the path or endpoint of our API is http://localhost:9000/api when our server is running. If we were to directly use the http://localhost:9000/api in our fetch() method, our client Webpack server ( http://localhost:3000 ) will attempt to load a resource from a different origin ( http://localhost:9000 ). This will essentially be performing Cross-Origin Resource Sharing and for security reasons, browsers may reject the ability for our client to make the request to the server. To avoid this, we can have our Webpack Server proxy requests intended for our API Server.

create-react-app makes the ability to proxy requests in development fairly easy by allowing us to define a proxy field in the package.json file.

In the package.json file, we’ll introduce a proxy field with a value of the base path of our running server url - http://localhost:9000 .

client/package.json

  "proxy": "http://localhost:9000"

If a request within our React Webpack server is now made to an unrecognized request prefix, for example /api , it will be redirected to the specified proxy - http://localhost:9000/api .

Proxying requests from the client to the server is only for development. When it comes to deployment, we have to ensure these URLs point to the right locations in the production state.

You may need to restart the React Webpack server to take the proxy change made in the package.json file into account.

SERVER.FETCH()

In our server.fetch() function, we can specify an argument of /api in the window fetch() method. /api will get proxied to http://localhost:9000/api .

interface Body {
  query: string;
}

export const server = {
  fetch: async (body: Body) => {
    const res = await fetch("/api");
  }
};

Since we want our window fetch() method to make a POST request, we’ll need pass in the appropriate request options.

The first option we’ll specify is the method option with a value of 'POST' .

interface Body {
  query: string;
}

export const server = {
  fetch: async (body: Body) => {
    const res = await fetch("/api", {
      method: "POST"
    });
  }
};

The second request option will be where we declare the content type of our request as application/json . This is to be done in the headers option of our request body.

interface Body {
  query: string;
}

export const server = {
  fetch: async (body: Body) => {
    const res = await fetch("/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      }
    });
  }
};

Finally, we’ll pass in the data that is to be sent as the body of the request. We can’t pass in a JavaScript object so this is where we’ll use the JSON.stringify() method to convert the data body to be passed in as a JSON string.

When the fetch is successful it returns a JSON response. To have this response handled as a native JavaScript object, we can return res.json() .

client/src/lib/api/server.ts

interface Body {
  query: string;
}

export const server = {
  fetch: async (body: Body) => {
    const res = await fetch("/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });

    return res.json();
  }
};

At this moment, our server.fetch() method is a promise that when resolved returns a data type of any . This isn’t strongly typed so we’ll revise this in an upcoming lesson. For now, we’ll verify if we’re able to interact with the Node server from the React client.

We’ll look to query the listings information from the <Listings> component directly. In the src/sections/Listings/Listings.tsx component file, we’ll import the newly created server object from the src/lib/api/ folder.

import React from "react";
import { server } from "../../lib/api";

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  return <h2>{title}</h2>;
};

To get things started, we’ll state that we want the listings query to be made only when a button has been clicked. We’ll introduce this button element in our component template and attach a click event listener on the button, that when clicked will call a component function called fetchListings() .

import React from "react";
import { server } from "../../lib/api";

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = () => {};

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>Query Listings!</button>
    </div>
  );
};

In our component fetchListings() function, we’ll attempt to make the server fetch request. Our server fetch() function expects a body argument with which contains a query field of the GraphQL query (i.e request). Since the request to be made is of content-type application/json and passes a stringified JSON, we’ll create our query as a string that represents the shape of data we’d like to return from our API. We’ll want to query the only root level query available in our server - the listings query. We’ll define this query request at the top of the Listings.tsx file.

To distinguish our GraphQL query document from any other variable, we’ll declare the query in a constant variable named LISTINGS . We’ll set up our LISTINGS query document to query all the fields from the Listing GraphQL object type.

import React from "react";
import { server } from "../../lib/api";

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = () => {};

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>Query Listings!</button>
    </div>
  );
};

In our component fetchListings() function - we’ll call the server.fetch() function, pass in a body object and specify a query field in body to be that of the LISTINGS query we’ve set up. We’ll assign the result of our server.fetch() function to a constant variable called listings and attempt to console.log() the listings result right after the request has been made.

import React from "react";
import { server } from "../../lib/api";

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = () => {
    const listings = server.fetch({ query: LISTINGS });
    console.log(listings);
  };

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>Query Listings!</button>
    </div>
  );
};

The .json() method in our server.fetch() function takes the response and returns a promise. We’ll make our component fetchListings() function asynchronous since we’ll have to await for the results from the async server.fetch() function. The data of the returned promise will live in a data field within the response object so we’ll destructure data from the response object.

GraphQL conveniently places the data we expect from a request in a data field of the response.

Our Listings.tsx component file will now look like the following:

client/src/sections/Listings/Listings.tsx

import React from "react";
import { server } from "../../lib/api";

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = async () => {
    const { data } = await server.fetch({ query: LISTINGS });
    console.log(data); // check the console to see the listings data from our GraphQL Request!
  };

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>Query Listings!</button>
    </div>
  );
};

To verify if our client can make requests to the GraphQL server, we’ll first run our Node server.

server $: npm run start

With the server running, we’ll also run our React Webpack server.

client $: npm run start

When we click the Query Listings button, we’ll see the listings data from our server in our browser console!

ABSTRACTING THE TYPE OF DATA FROM SERVER FETCH

Although our server.fetch() function works, we still have an issue where data retrieved from the server.fetch() function doesn’t have a type associated with it.

To solve this, we’ll take advantage of Typescript generics. We’ve seen how generics worked both in the Node server application as well the FunctionComponent type we initially surveyed, but we haven’t had the chance to create our own generic abstraction.

We want the server.fetch() function to return the data from the request but we also want this data to be typed. We also want the fetch() function to work for any data object with its own unique type. The server.fetch() function has no idea what the type of data it returns could be. However, in the component where the fetch function is being called, we have an idea of the data we’re expecting because we’re defining the query we want to make .

For the listings query field, we expect an array of listings where id , title , image , address are to be strings and the price , numOfGuests , numOfBeds , numOfBaths , and rating fields are to be numbers.

client/src/sections/Listings/Listings.tsx

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

If we have an idea of the shape of the data from the request, we essentially want to pass this shape of data into the server.fetch() function and we can achieve this with generics. First and foremost, in the server.fetch() function in the src/lib/api/server.ts file, we’ll use angle brackets and declare that the function can accept a type variable denoted by TData .

export const server = {
  fetch: async <TData>(body: Body) => {
    const res = await fetch("/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });

    return res.json();
  }
};

We’ll specify a default type parameter of any to say if the type of data isn’t provided, we’ll ensure the type is to be returned as any .

export const server = {
  fetch: async <TData = any>(body: Body) => {
    const res = await fetch("/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });

    return res.json();
  }
};

By having access to the TData type variable in our server.fetch() function, we can set the return type of the function to the type we expect. The res.json() function simply returns a value of Promise<any> and doesn’t give us the option of passing in a type variable.

When our function is successful, we’re confident of how the data is going to be returned. This is where we can type assert the returned value to what we expect. In our instance, we’ll type assert the returned statement as Promise<{ data: TData }> .

export const server = {
  fetch: async <TData = any>(body: Body) => {
    const res = await fetch("/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });

    return res.json() as Promise<{ data: TData }>;
  }
};

Type assertions are a TypeScript capability where one can override the types that TypeScript either infers or analyzes. There are two ways of type asserting - either using the as syntax or using the angle brackets syntax.

return <Promise<{ data: TData }>>res.json(); // angle brackets to type assert res.json()

return res.json() as Promise<{ data: TData }>; // (as) to type assert res.json()

Our ESLint configuration prefers the as syntax and so do we, so we’ll use the as syntax to type assert the returned promise.

Type assertions should be used sparingly because it’s easy to type assert information without specifying the properties that are needed in the type. Most of the time we’ll assign types normally but only in cases we know better than the compiler - do we assert.

The data being returned from our server.fetch() can be now be typed as the type value passed into the function! Let’s see how this would work for our <Listings> component.

For the <Listings> component, we’ll create an interface type that is to resemble the shape of listings data that can be retrieved from our GraphQL listings query. We’ll create this type in a types.ts file located within the src/sections/Listings/ folder.

client/
  src/
    lib/
      api/
      sections/
        Listings/
          index.ts
          Listings.ts
          types.ts
  // ...

In the Listings/types.ts file, we’ll first create an interface called Listing that describes the shape of a single listing object.

client/src/sections/Listings/types.ts

interface Listing {
  id: string;
  title: string;
  image: string;
  address: string;
  price: number;
  numOfGuests: number;
  numOfBeds: number;
  numOfBaths: number;
  rating: number;
}

We’ll then export an interface labeled ListingsData that represents the shape of the data field returned from our API. When we query listings, we’ll observe the data field is to contain a listings field which is an array of listing items so we’ll specify the interface as so.

client/src/sections/Listings/types.ts

interface Listing {
  id: string;
  title: string;
  image: string;
  address: string;
  price: number;
  numOfGuests: number;
  numOfBeds: number;
  numOfBaths: number;
  rating: number;
}

export type ListingsData = {
  listings: Listing[];
};

In the Listings.tsx component file, we’ll import the ListingsData interface type and pass it down as the type variable for the server.fetch() function.

client/src/sections/Listings/Listings.tsx

import React from "react";
import { server } from "../../lib/api";
import { ListingsData } from "./types";

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = async () => {
    const { data } = await server.fetch<ListingsData>({ query: LISTINGS });
    console.log(data); // check the console to see the listings data from our GraphQL Request!
  };

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>Query Listings!</button>
    </div>
  );
};

The data now being returned from the server fetch() function is appropriately typed.

If we attempt to access the listings field within data , we’ll be able to access the listings information we expect. If we wanted to access some other property that didn’t exist, TypeScript will throw an error.

const fetchListings = async () => {
  const { data } = await server.fetch<ListingsData>({
    query: LISTINGS
  });
  console.log(data.listings); // listings exists in data
  console.log(data.bookings); // bookings does not exist in data
};

Our app will still behave the same as before since we haven’t made any functional changes. We’ll be able to query the listings from our database and see the queried result in our browser console.

DELETING A LISTING

With our custom server.fetch() function, we’ve been able to query the listings field in our GraphQL API. Now, we’ll look to see how we can make the GraphQL mutation available in our API - deleteListing .

In our server.fetch() function, we’ve specified that the request body has to have at the very least the query string that represents the GraphQL query (i.e. request) we want to make.

interface Body {
  query: string;
}

export const server = {
  fetch: async <TData = any>(body: Body) => {
    // ...
  }
};

We can reuse this server.fetch() function to help conduct our mutation. The mutation we’ll want to make ( deleteListing ) expects a variable argument to be passed which is the id of the listing that is to be deleted. As a result, we should expect the body of the request to contain the variables that can be passed into our GraphQL request.

The JSON body we pass into our request expects a query field of type string . The other field we’ll pass in is the variables field which is to be an object for holding the variables that are to be passed into the GraphQL request.

Just like how the data of a request is to be typed, it can be helpful if we’re able to type define the expected variables of a request since every request may expect different variables. Because of this, we’ll make the Body interface a generic that accepts a TVariables type with which will be the type of the variables field.

interface Body<TVariables> {
  query: string;
  variables: TVariables;
}

Variables of a request can be optional since we don’t have to pass in variables for every GraphQL request. We’ll denote the variables field as being optional by placing a question mark at the end of the variables field in the Body interface.

client/src/lib/api/server.ts

interface Body<TVariables> {
  query: string;
  variables?: TVariables;
}

In the server.fetch() function, we’ll need to pass the value of the TVariables type to the Body interface type assigned to the body argument. Just like how we’ve introduced a TData type variable into our server.fetch() function, we can introduce a TVariables type variable as well and give it a default value of any . We’ll then pass the TVariables type value along to the Body interface used as the type of the body argument.

client/src/lib/api/server.ts

interface Body<TVariables> {
  query: string;
  variables?: TVariables;
}

export const server = {
  fetch: async <TData = any, TVariables = any>(body: Body<TVariables>) => {
    const res = await fetch("/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });

    return res.json() as Promise<{ data: TData }>;
  }
};

Our server.fetch() function is now prepared to allow components to make GraphQL mutations. In the <Listings> component, we’ll create a button that’ll trigger a method when clicked. We’ll call this method deleteListing() and the button text will be 'Delete a listing!' . Since our deleteListing() function will make an asynchronous request, we’ll label the function as an async function.

import React from "react";
import { server } from "../../lib/api";
import { ListingsData } from "./types";

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = async () => {
    const { data } = await server.fetch<ListingsData>({
      query: LISTINGS
    });
    console.log(data); // check the console to see the listings data from our GraphQL Request!
  };

  const deleteListing = async () => {};

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>Query Listings!</button>
      <button onClick={deleteListing}>Delete a listing!</button>
    </div>
  );
};

The deleteListing() function is where we’ll want to call the server.fetch() function, pass in the expected GraphQL request and the variables of the request. Let’s first define the shape of both the data we expect to retrieve as well as the shape of variables we want to pass in.

In the Listings/types.ts file, we’ll declare the expected types of the data and variables field of our deleteListing mutation as DeleteListingData and DeleteListingVariables respectively.

The data we expect returned from the deleteListing mutation is the deleted listing itself. So we’ll specify a deleteListing field within the DeleteListingData interface that will have a type of the Listing interface we’ve defined earlier.

interface Listing {
  id: string;
  title: string;
  image: string;
  address: string;
  price: number;
  numOfGuests: number;
  numOfBeds: number;
  numOfBaths: number;
  rating: number;
}

export type ListingsData = {
  listings: Listing[];
};

export interface DeleteListingData {
  deleteListing: Listing;
}

The only variable of the deleteListing mutation is to be an id of type string.

client/src/sections/Listings/types.ts

interface Listing {
  id: string;
  title: string;
  image: string;
  address: string;
  price: number;
  numOfGuests: number;
  numOfBeds: number;
  numOfBaths: number;
  rating: number;
}

export type ListingsData = {
  listings: Listing[];
};

export interface DeleteListingData {
  deleteListing: Listing;
}

export interface DeleteListingVariables {
  id: string;
}

In the Listings.tsx file, we’ll import the newly created types for our GraphQL deleteListing request.

client/src/sections/Listings/Listings.tsx

import {
  DeleteListingData,
  DeleteListingVariables,
  ListingsData
} from "./types";

We’ll need to construct the query document of our deleteListing mutation. We’ll set this document in a const variable called DELETE_LISTING . We’ll use the mutation keyword and declare the name of the mutation as DeleteListing and we’ll call the deleteListing mutation. For what is to be returned, we’ll state we’re only interested in returning the id of the deleted listing.

const DELETE_LISTING = `
  mutation DeleteListing {
    deleteListing {
      id
    }
  }
`;

We now need to find a way to pass in the id variable needed in our deleteListing mutation. In the mutation declaration of our document, we can say we expect a variable named id and we can say the GraphQL type of this id variable is GraphQLID and it is to be required. Since we can use the GraphQL schema language here - this will appear as ID! .

In GraphQL, we can use the $ syntax to define variables in a GraphQL request, and we can pass the variables as a separate map into the request field. This will appear as follows:

client/src/sections/Listings/Listings.tsx

const DELETE_LISTING = `
  mutation DeleteListing($id: ID!) {
    deleteListing(id: $id) {
      id
    }
  }
`;

With our mutation query document available, we can now establish the server.fetch() function in the component deleteListing() function. We’ll trigger the server.fetch() function, pass in the DeleteLisingData and DeleteListingVariables type variables, and specify in the body of the request the mutation query we’ve set up and id variable to be passed in. To get an id of a listing in our database, we’ll retrieve and use an id from a listing object when we query all the listings.

We’ll destructure the data returned from our server.fetch() call and console.log() it at the end of our deleteListing(() component function.

This will make our Listings.tsx component file now look like the following:

client/src/sections/Listings/Listings.tsx

import React from "react";
import { server } from "../../lib/api";
import {
  DeleteListingData,
  DeleteListingVariables,
  ListingsData
} from "./types";

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

const DELETE_LISTING = `
  mutation DeleteListing($id: ID!) {
    deleteListing(id: $id) {
      id
    }
  }
`;

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = async () => {
    const { data } = await server.fetch<ListingsData>({ query: LISTINGS });
    console.log(data); // check the console to see the listings data from our GraphQL Request!
  };

  const deleteListing = async () => {
    const { data } = await server.fetch<
      DeleteListingData,
      DeleteListingVariables
    >({
      query: DELETE_LISTING,
      variables: {
        id: "5d4507a9cf295034813b35c2" // hardcoded id variable,
      }
    });
    console.log(data); // check the console to see the result of the mutation!
  };

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>Query Listings!</button>
      <button onClick={deleteListing}>Delete a listing!</button>
    </div>
  );
};

We’ll start both the Node server and React Webpack server from our terminal.

server $: npm run start
client $: npm run start

When we click the 'Delete a listing!' button in our UI, we’ll see the id information of the deleted listing in our console!

If we were to query all the listings again, we’ll see the listing we’ve deleted has been removed.

At any moment in time, you can run the seed script on the Node server project to introduce more listings into the listings collection in the database.

TVARIABLES

When we’ve set a value of the TData type variable in our server.fetch() function, we’ve been able to assign the type of data being returned from the request. Specifying a value of TVariables on the other hand helps type check the variables data being used in the request.

In our component deleteListing() function, if we were to introduce another variable that we haven’t stated is part of the DeleteListingVariables interface - TypeScript will emit an error.

const deleteListing = async () => {
  const { data } = await server.fetch<DeleteListingData, DeleteListingVariables>({
    query: DELETE_LISTING,
    variables: {
      id: "5d4507a9cf295034813b35c2",
      rating: 3 // rating does not exist in DeleteListingVariables
    }
  });
  console.log(data);
};

Since not every request needs variables, the variables field in our body request is optional.

MODULE 6 SUMMARY

We’ve created our first functional component, the <Listings> component, and we’ve investigated how TypeScript allows us to type-check the props a component is expected to receive.

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  // ...
};

SERVER.FETCH()

With the help of the browser window fetch() method, we were able to create a server.fetch() function that’s able to make a POST request to conduct our GraphQL queries and mutations.

We’ve stated the content of our POST request as application/json . The JSON body passed in our requests requires the query of the GraphQL request while variables is optional.

client/src/lib/api/server.ts

interface Body<TVariables> {
  query: string;
  variables?: TVariables;
}

export const server = {
  fetch: async <TData = any, TVariables = any>(
    body: Body<TVariables>
  ) => {
    const res = await fetch("/api", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(body)
    });

    return res.json() as Promise<{ data: TData }>;
  }
};

<LISTINGS>

In our <Listings> component, we’ve introduced two buttons. One button is responsible for querying the listings from our collection and the other in deleting a certain listing from our collection.

We’ve set up our GraphQL query documents as simple strings that are passed to each server fetch request we make in our component functions. For the delete listing request, we’ve hardcoded an id to delete a listing of that particular id.

client/src/sections/Listings/Listings.tsx

import React from "react";
import { server } from "../../lib/api";
import {
  DeleteListingData,
  DeleteListingVariables,
  ListingsData
} from "./types";

const LISTINGS = `
  query Listings {
    listings {
      id
      title
      image
      address
      price
      numOfGuests
      numOfBeds
      numOfBaths
      rating
    }
  }
`;

const DELETE_LISTING = `
  mutation DeleteListing($id: ID!) {
    deleteListing(id: $id) {
      id
    }
  }
`;

interface Props {
  title: string;
}

export const Listings = ({ title }: Props) => {
  const fetchListings = async () => {
    const { data } = await server.fetch<ListingsData>({
      query: LISTINGS
    });
    console.log(data); // check the console to see the listings data from our GraphQL Request!
  };

  const deleteListing = async () => {
    const { data } = await server.fetch<
      DeleteListingData,
      DeleteListingVariables
    >({
      query: DELETE_LISTING,
      variables: {
        id: "5d4507a9cf295034813b35c2" // hardcoded id variable,
      }
    });
    console.log(data); // check the console to see the result of the mutation!
  };

  return (
    <div>
      <h2>{title}</h2>
      <button onClick={fetchListings}>
        Query Listings!
      </button>
      <button onClick={deleteListing}>
        Delete a listing!
      </button>
    </div>
  );
};

In each of our server.fetch() requests within the <Listings> component, we’ve established generics to help define the type of data that is to be returned from our request as well as type check the variables that are to be passed into a request.

MOVING FORWARD

Though we’ve been able to create a custom server.fetch() function to help us query listings or delete a certain listing, we’ve only been able to see the results of our requests in our browser console. In the next module, we’ll investigate how we can keep track of state in our <Listings> component as well as modify how we make our query and mutation. We’ll be interested in making our listings query the moment the component mounts and for the deleteListing mutation, we’ll pass in an id from a listing that is to be selected from the UI.

We’ll be achieving all this by introducing and using React Hooks!