Amplify, React and Typescript
Tags: typescript, aws, react, javascript
Published: 2022-02-20 20:23:02

Recently I wanted to start a project with an aws backend. I wanted to use typescript and create-react-app. The guide on aws is all in vanilla react, that won't do and in searching around I couldn't find a good example so I struggled through so you don't have to.
 
The [AWS guide](https://docs.amplify.aws/start/getting-started/data-model/q/integration/react/#connect-frontend-to-api) can be followed up until connecting the front end. In the example I went with blogs instead of ToDo's, my project was closer inline to blogs so it just made sense, you can choose whatever data type you would like. 

During the configuration, make sure that you select typescript where you can. This will make codegen create all the types you need for your front-end. 

The base project is created with `npx create-react-app --template typescript` instead of the default create-react-app template. 
The updated App.tsx looks like this, we'll break it down afterwards. 

```javascript
import React, { useState, useEffect } from "react";
import Amplify, { API, graphqlOperation } from "aws-amplify";
import { createBlog } from "./graphql/mutations";
import { listBlogs } from "./graphql/queries";

import awsExports from "./aws-exports";
import { ListBlogsQuery } from "./API";

Amplify.configure(awsExports);
const initialState = { name: "", body: "" };

const App = () => {
  const [formState, setFormState] = useState(initialState);
  const [blogs, setBlogs] = useState<ListBlogsQuery>();

  useEffect(() => {
    fetchBlogs()
  }, []);

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setFormState({ ...formState, [event.target.name]: event.target.value });
  };

  const fetchBlogs = async () => {
    try {
      const blogData = (await API.graphql(graphqlOperation(listBlogs))) as {
        data: ListBlogsQuery
      }
      setBlogs(blogData.data);
    } catch (err) {
      console.log("Error fetching blogs" + err);
    }
  };
  const addBlog = async () => {
    try {
      if (!formState.name || !formState.body) return;
      const blog = { ...formState };
      if (blogs) {
        await API.graphql(graphqlOperation(createBlog, { input: blog }));
        await fetchBlogs();
        setFormState(initialState);
      }
    } catch (err) {
      console.log("error creating blog: ", err);
    }
  };

  return (
    <div id="wrapper" style={styles.container}>
      <h2>Amplify Todos</h2>
      <input
        onChange={handleInputChange}
        name="name"
        style={styles.input}
        value={formState.name}
        placeholder="Name"
      />
      <input
        onChange={handleInputChange}
        name="body"
        style={styles.input}
        value={formState.body}
        placeholder="Type your blog..."
      />
      <button style={styles.button} onClick={addBlog}>
        Create Blog
      </button>
      {blogs &&
        blogs?.listBlogs?.items?.map((blog, index) => {
          return (
            <div key={blog?.id || index} style={styles.todo}>
              <p style={styles.todoName}>{blog?.name}</p>
              <p style={styles.todoDescription}>{blog?.body}</p>
            </div>
          );
        })}
    </div>
  );
};
const styles = {
  container: {
    width: 400,
    margin: "0 auto",
    display: "flex",
    //real weird issue: https://github.com/cssinjs/jss/issues/1344
    flexDirection: "column" as "column",
    justifyContent: "center",
    padding: 20,
  },
  todo: { marginBottom: 15 },
  input: {
    border: "none",
    backgroundColor: "#ddd",
    marginBottom: 10,
    padding: 8,
    fontSize: 18,
  },
  todoName: { fontSize: 20, fontWeight: "bold" },
  todoDescription: { marginBottom: 0 },
  button: {
    backgroundColor: "black",
    color: "white",
    outline: "none",
    fontSize: 18,
    padding: "12px 0px",
  },
};

export default App;

```

Imports are pretty straight forward, we'll be using graphql and we need to import the queries and mutations that we'll use to fetch and update our blogs.

This bit is pretty important. This holds the state of the form we use to create new blogs, fairly common. The next line is holding the blogs that we fetch from our backend, note the type notation is a `ListBlogsQuery` this is a type that is created for us by codegen when we push out backend to aws. The useEffect fetches our posts on page load, again pretty standard stuff.

```javascript 
  const [formState, setFormState] = useState(initialState);
  const [blogs, setBlogs] = useState<ListBlogsQuery>();

  useEffect(() => {
    fetchPosts()
  }, []);
```

The `fetchBlogs` and `addBlog` methods were tricky for me, I had never used the `as {data: <Type>}` syntax so it took me a minute to fully understand what I was doing. 

``` javascript
const fetchBlogs = async () => {
    try {
      const blogData = (await API.graphql(graphqlOperation(listBlogs))) as {
        data: ListBlogsQuery
      }
      setBlogs(blogData.data);
    } catch (err) {
      console.log("Error fetching blogs" + err);
    }
  };
  const addBlog = async () => {
    try {
      if (!formState.name || !formState.body) return;
      const blog = { ...formState };
      if (blogs) {
        await API.graphql(graphqlOperation(createBlog, { input: blog }));
        await fetchBlogs();
        setFormState(initialState);
      }
    } catch (err) {
      console.log("error creating blog: ", err);
    }
  };
```

I'm not going to cover the rest of the content, as its pretty standard react stuff. One note is the super weird issue I ran into with `flexDirection: 'column'` If you note in my styles there is a link in a comment to explain more. 

Anyway thats about it, I'm happy with it and I hope someone else gets some use out of it.