If you want to add data to your React app but don't want to have to build an API then this is the article for you. Learn how you can use AWS Amplify to create and access a DynamoDB table in the easiest way possible.

We're starting with our React and Amplify app from the last tutorial. If you didn't follow along with that then you can read it here or clone the code by running this command.

git clone --single-branch --branch amplify-login git@github.com:SamWSoftware/amplify-react-tutorial-project.git

In this video we'll be having a look at how we can use AWS amplify inside our react app to allow us to access our database on the back end using GraphQL. To start we need to go into the terminal and run:

amplify add api

This will start us in a set of CLI options, asking us a few configuration questions:

What kind of API we want to use: GraphQL
The name of the API: songAPI
How we want to authenticate the API: Amazon Cognito User Pool
Advanced Settings: No, I am done
Do you have a schema: No
What kind of schema do you want: Single object with fields

After a little setup we are asked if we want to edit our new schema. We want to say yes. This opens the GraphQL schema which we're going to update to be the schema listed here.

type Song @model {
    id: ID!
    title: String!
    description: String!
    filePath: String!
    likes: Int!
    owner: String!
}

With our schema set up we're going to run amplify push which will compare out current amplify setup with that on our AWS account. As we've added a new API we'll have changes so will be asked if we want to continue with the changes.

Once we've selected Yes then we're put into another set of options.

Do we want to generate code for our GraphQL API: Yes
Which Language: JavaScript
File pattern for the new files: src/graphql/**/*.js
Generate all operations: Yes
Maximum statement depth: 2

This will now deploy all of the changes to AWS and also set up the new request files in our React app. This does take a few minutes to do.

Once that is completed we can go into our App.js file and rename it to be App.jsx. We now need to write a function in here to get the list of songs from our new database. This function calls the graphql api passing in the opperation of listSongs. We also need to add a new state to the App component.

const [songs, setSongs] = useState([]);

const fetchSongs = async () => {
    try {
        const songData = await API.graphql(graphqlOperation(listSongs));
        const songList = songData.data.listSongs.items;
        console.log('song list', songList);
        setSongs(songList);
    } catch (error) {
        console.log('error on fetching songs', error);
    }
};

We now need to add or update a few imports to our file to get this working

import React, { useState, useEffect } from 'react';
import { listSongs } from './graphql/queries';
import Amplify, { API, graphqlOperation } from 'aws-amplify';

The listSongs is one of those functions created by amplify to help us access our data.

Now we want this function to be called once when the component renders, but not every time that it re-renders. To do this we use useEffect but make sure to add a second parameter of [] so that it only gets triggered once.

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

If we now start our app using npm start and then go to the app we can open the console and see a log of song list []. This means that the useEffect has called the fetchSongs which is console logging out the result, but currently there is nothing in the database.

To correct this we need to go into our AWS account and add find Dynamo. We should find a new table called something like Song-5gq8g8wh64w-dev. This currently has no data so we need to add some. For now we're going with manually creating new data in here. Under Items click Create item and then make sure the dropdown in the top left shows text. If it shows tree then just click it and change it to tree. We can then make the data to go into that row.

We start with the GraphQL schema, giving the row some data for each attribute but also need to add a createdAt and updatedAt value. This can be found using the console and typing new Date() .toISOString() and copying the result of that. You should end up with an object like this:

{
  "id": "gr4334t4tog345ht35",
  "title": "My First Song",
  "description": "A test song for our amplify app",
  "owner": "Sam Williams",
  "filePath": "",
  "likes": 4,
  "createdAt": "2020-08-13T07:01:39.176Z",
  "updatedAt": "2020-08-13T07:01:39.176Z"
}

If we save that new object then we can go back into our app and refresh the page. We should now be able to see our data in the console.log.

We can now use this data in our app to show the list of songs that we just got. Replace the existing text of song list with this set of JSX.

<div className="songList">
    {songs.map((song, idx) => {
        return (
            <Paper variant="outlined" elevation={2} key={`song${idx}`}>
                <div className="songCard">
                    <IconButton aria-label="play">
                        <PlayArrowIcon />
                    </IconButton>
                    <div>
                        <div className="songTitle">{song.title}</div>
                        <div className="songOwner">{song.owner}</div>
                    </div>
                    <div>
                        <IconButton aria-label="like">
                            <FavoriteIcon />
                        </IconButton>
                        {song.like}
                    </div>
                    <div className="songDescription">{song.description}</div>
                </div>
            </Paper>
        );
    })}
</div>

This code is mapping over each song in the list and rendering a new Paper for them with all the details we need. We're using the MaterialUI library to help make this look nice for us so we need to make sure to run npm install --save @material-ui/core @material-ui/icons to install those packages and then add them to the imports at the top of the file too:

import { Paper, IconButton } from '@material-ui/core';
import PlayArrowIcon from '@material-ui/icons/PlayArrow';
import FavoriteIcon from '@material-ui/icons/Favorite';

With this, if we save and reload our app we now get this:

Whilst this is ok, we can update the CSS to make it look far better. Open up your App.css file and change it to this.

.App {
    text-align: center;
}

.App-logo {
    height: 10vmin;
    pointer-events: none;
}

.App-header {
    background-color: #282c34;
    min-height: 5vh;
    display: flex;
    align-items: center;
    justify-content: space-around;
    font-size: calc(10px + 2vmin);
    color: white;
}

.App-link {
    color: #61dafb;
}

.songList {
    display: flex;
    flex-direction: column;
}

.songCard {
    display: flex;
    justify-content: space-around;
    padding: 5px;
}

.songTitle {
    font-weight: bold;
}

Now we get it looking like this - much better.

Now we've got one item in the database so only get one record. If we go back into Dynamo and create a new item or duplicate the existing one then we can see how multiple songs look.

Now that we can get the data, what about updating that info. For this we are going to add the ability to like a song. To start this we can add an onClick function to the icon button that we have for the likes.

<IconButton aria-label="like" onClick={() => addLike(idx)}>
    <FavoriteIcon />
</IconButton>

You may have realised that there is this idx property that we haven't see before. That is short for index. Where we do the songs.map we can update it slightly to get the position of each item in the list. We can also use this idx to add a key to the top level Paper in that map to remove an error we get from React.

{songs.map((song, idx) => {
    return (
        <Paper variant="outlined" elevation={2} key={`song${idx}`}>
            ...
        </Paper>
    )}
)}

With the new index and the onClick function call we now need to make the addLike function. This function needs to take the index of the song to find the correct song, update the number of likes. It then removes some fields that can't be passed into the updateSong operation before calling that operation.

const addLike = async idx => {
    try {
        const song = songs[idx];
        song.like = song.like + 1;
        delete song.createdAt;
        delete song.updatedAt;

        const songData = await API.graphql(graphqlOperation(updateSong, { input: song }));
        const songList = [...songs];
        songList[idx] = songData.data.updateSong;
        setSongs(songList);
    } catch (error) {
        console.log('error on adding Like to song', error);
    }
};

Once the song has been updated in the database, we need to get that update back into our state. We need to clone the existing songs using const songList = [...songs]. If we just mutates the original list of songs then React wouldn't have re-rendered the page. With that new song list we call setSongs to update our state and we're done with the function. We just need to add one more import to the top of the file which we get from the mutators that Amplify created:

import { updateSong } from './graphql/mutations';

Now when we click on the like button on a song, it is updated in state and in the database.