DEV Community

Shane Walker
Shane Walker

Posted on

Amplify, React and Typescript

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 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.

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;

Enter fullscreen mode Exit fullscreen mode

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.

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

  useEffect(() => {
    fetchPosts()
  }, []);
Enter fullscreen mode Exit fullscreen mode

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.

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);
    }
  };
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
leob profile image
leob

Maybe better TS support has been added in the meantime?