Amplify DataStore is an AWS Amplify feature that provides a persistent on-device storage bucket to read, write, and observe data online or offline without additional code.
With DataStore, we can persist data locally on our device, making working with shared cross-user data just as simple as working with local-only data.
This post will discuss setting up a DataStore environment by building a simple blog application with React.js.
The complete source code of this project is on this Github Repository.
Prerequisites
The following are requirements in this post:
- Basic knowledge of JavaScript and React.js.
- Node.js and AWS CLI are installed on our computers.
- AWS Amplify account; create one here.
Getting Started
We'll run the following command in our terminal:
npx create-react-app datastore-blog
The above command creates a react starter application in a folder; datastore-blog.
Next, we'll navigate into the project directory and bootstrap a new amplify project with the following commands:
cd datastore-blog # to navigate into the project directory npx amplify-app # to initialize a new amplify project
Next, we'll install amplify/core, amplify/datastore, and react-icons libraries with the following command:
npm install @aws-amplify/core @aws-amplify/datastore react-icons
Building the application
First, let's go inside the amplify folder and update the schema.graphql
file with the following code:
//amplify/backend/api/schema.graphql type Post @model { id: ID! title: String! body: String status: String }
In the code above, we instantiated a Post
model with some properties.
AWS amplify datastore uses data model(s) defined in the schema.graphql
to interact with the datastore API.
Next, let's run the following command:
npm run amplify-modelgen
The command above will inspect the schema.graphql
file and generate the model for us.
We should see a model folder with the generated data model inside the src
directory.
Next, we'll run amplify init
command and follow the prompts to register the application in the cloud:
Next, we'll deploy our applications to the cloud with the following command:
amplify push
After deploying the application, we'll head straight to index.js
and configure AWS for the application's UI.
//src/index.js import React from "react"; import ReactDOM from "react-dom/client"; import "./index.css"; import App from "./App"; import "bootstrap/dist/css/bootstrap.min.css"; import Amplify from "@aws-amplify/core"; import config from "./aws-exports"; Amplify.configure(config); const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <App /> </React.StrictMode> );
In the code snippets above, we:
- Imported bootstrap minified CSS for styling the application.
- We imported
Amplify
andConfig
and did the Amplify configuration.
Now, let's head over to App.js
and update it with the following snippets:
//src/App.js import React, { useState, useEffect } from "react"; import { DataStore } from "@aws-amplify/datastore"; import { Post } from "./models"; import { Form, Button, Card } from "react-bootstrap"; import { IoCreateOutline, IoTrashOutline } from "react-icons/io5"; import "./App.css"; const initialState = { title: "", body: "" }; const App = () => { const [formData, setFormData] = useState(initialState); const [posts, setPost] = useState([]); useEffect(() => { getPost(); const subs = DataStore.observe(Post).subscribe(() => getPost()); return () => subs.unsubscribe(); }); const handleChange = (e) => { setFormData({ ...formData, [e.target.name]: e.target.value }); }; async function getPost() { const post = await DataStore.query(Post); setPost(post); } async function createPost(e) { e.preventDefault(); if (!formData.title) return; await DataStore.save(new Post({ ...formData })); setFormData(initialState); } async function deletePost(id) { const auth = window.prompt( "Are sure you want to delete this post? : Type yes to proceed" ); if (auth !== "yes") return; const post = await DataStore.query(Post, `${id}`); DataStore.delete(post); } return ( <> <div className="container-md"> <Form className="mt-5" onSubmit={(e) => createPost(e)}> <h1 className="text-center">AWS Datastore Offline-First Data Manipulations</h1> <Form.Group className="mt-3" controlId="formBasicEmail"> <Form.Control type="text" placeholder="Enter title" value={formData.title} className="fs-2" onChange={handleChange} name="title" /> </Form.Group> <Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1"> <Form.Control size="lg" as="textarea" rows={3} required className="fs-5" name="body" onChange={handleChange} value={formData.body} placeholder="Write post" /> </Form.Group> <div className="d-md-flex justify-content-md-end"> <Button variant="primary" type="submit"> Create Post </Button> </div> </Form> <div> {posts.map((post) => ( <Card key={post.id} className="mt-5 mb-5 p-2"> <Card.Body> <Card.Title className="fs-1 text-center"> {post.title} </Card.Title> <Card.Text className="fs-4 text-justify">{post.body}</Card.Text> </Card.Body> <div className="d-md-flex justify-content-md-end fs-2 p-3 "> <h1> <IoCreateOutline style={{ cursor: "pointer" }} /> </h1> <h1 onClick={() => deletePost(post.id)}> <IoTrashOutline style={{ cursor: "pointer" }} /> </h1> </div> </Card> ))} </div> </div> </> ); }; export default App;
In the code snippets above, we did the following:
- Imported
Datastore
from "@aws-amplify/datastore" and the Post model from "../models/Post". - Imported
Card
,Form
, andButton
from "react-bootstrap" andIoCreateOutline
andIoTrashOutline
from "react-icons/io5" - Initialized constant properties
title
andbody
and createdformData
constant with useState hook and passed the initial state to it. - Created
posts
state constant to hold all our posts when we fetch them from the database. - Used the
handleChange
function to handle changes in our inputs - Used the
getPost
function to get all the posts from the database and update theposts
state - Used the
createPost
function to save our inputs and thedeletePost
function to delete a particular post. - Used
Form
andButton
to implement our form inputs, then looped through posts and usedCard
and the icons from "react-icons/io5" to display the posts if we have some.
In the browser, we'll have the application like the below:
The edit button does nothing now; let's create a component that will receive the post id and give us a form to update the post title and body.
Next, let's create a Components folder inside the src
folder and create a UpdatePost.js
file with the following snippets:
//Components/UpdatePost.js import React, { useState } from "react"; import { Form, Button } from "react-bootstrap"; import { Post } from "../models"; import { DataStore } from "@aws-amplify/datastore"; import { IoCloseOutline } from "react-icons/io5"; const editPostState = { title: "", body: "" }; function UpdatePost({ post: { id }, setShowEditModel }) { const [updatePost, setUpdatePost] = useState(editPostState); const handleChange = (e) => { setUpdatePost({ ...updatePost, [e.target.name]: e.target.value }); }; async function editPost(e, id) { e.preventDefault(); const original = await DataStore.query(Post, `${id}`); if (!updatePost.title && !updatePost.body) return; await DataStore.save( Post.copyOf(original, (updated) => { updated.title = `${updatePost.title}`; updated.body = `${updatePost.body}`; }) ); setUpdatePost(editPostState); setShowEditModel(false); } return ( <div className="container"> <Form className="mt-5 border border-secondary p-3" onSubmit={(e) => editPost(e, id)} > <h1 className="d-md-flex justify-content-md-end" onClick={() => setShowEditModel(false)} > <IoCloseOutline /> </h1> <Form.Group className="mt-3" controlId="formBasicEmail"> <Form.Control type="text" placeholder="Enter title" className="fs-3" value={updatePost.title} onChange={handleChange} name="title" /> </Form.Group> <Form.Group className="mb-3 " controlId="exampleForm.ControlTextarea1"> <Form.Control size="lg" as="textarea" required name="body" className="fs-4" onChange={handleChange} value={updatePost.body} placeholder="Write post" /> </Form.Group> <div> <Button variant="primary" type="submit"> Update Post </Button> </div> </Form> </div> ); } export default UpdatePost;
In the code above, we:
- Initialised
editPostState
object, createdupdatePost
with react’s useState hook and passededitPostState
to it. - Destructured
id
from the post property that we would get from theApp.js
. - Created
handleChange
function to handle changes in the inputs. - Created
editPost
function to target post with theid
and updated it with the new input values. - Used
Form
andButton
from "react-bootstrap" and implemented the inputs form. - Used
IoCloseOutline
from "react-icons" to close the inputs form.
Next, let's import the UpdatePost.js
file and render it inside App.js
like below:
//src/App.js import React, { useState, useEffect } from "react"; //other imports here import UpdatePost from "./Components/UpdatePost"; const initialState = { title: "", body: "" }; const App = () => { const [formData, setFormData] = useState(initialState); const [posts, setPost] = useState([]); const [postToEdit, setPostToEdit] = useState({}); const [showEditModel, setShowEditModel] = useState(false); //useEffect function here //handleChange function here //getPost function here //createPost function here //deletePost function here return ( <> <div className="container-md"> {/* Form Inputs here */} <div> {posts.map((post) => ( <Card key={post.id} className="mt-5 mb-5 p-2"> <Card.Body> <Card.Title className="fs-1 text-center"> {post.title} </Card.Title> <Card.Text className="fs-4 text-justify">{post.body}</Card.Text> </Card.Body> <div className="d-md-flex justify-content-md-end fs-2 p-3 "> <h1 onClick={() => { setPostToEdit(post); setShowEditModel(true); }} > <IoCreateOutline style={{ cursor: "pointer" }} /> </h1> <h1 onClick={() => deletePost(post.id)}> <IoTrashOutline style={{ cursor: "pointer" }} /> </h1> </div> </Card> ))} {showEditModel && ( <UpdatePost post={postToEdit} setShowEditModel={setShowEditModel} /> )} </div> </div> </> ); }; export default App;
In the code above, we :
- Imported
UpdatePost
from the Components folder - Created
postToEdit
to target the particular post we'll be updating andshowEditModel
to show the input form; with the useState hook. - Set an onClick function on the edit icon to update the
postToEdit
andshowEditModel
states. - Conditionally rendered the
UpdatePost
component and passedpostToEdit
andsetShowEditModel
to it.
When we click the edit icon in the browser, we would see a form to fill and update a post.
There is a list of other notable features of the AWS Datastore that we did not cover in this post; see the AWS DataStore documentation.
Conclusion
This post discussed setting up the AWS DataStore environment and building a simple blog posts application with React.js.
Resources
The following resources might be helpful.
Top comments (0)