6. CRUD Operations

Perform Create, Read, Update and Delete calls.

Create, read, update, delete data records

One of the core design principles of the HAT has been its ability to store any structure data. Data is grouped under a combination of namespace and endpoint.

The namespace is similar to a "database" and you can define the unique namespace name in your application. The applications can by default have read-and-write data permission in this namespace and they cannot read data from other namespaces.

The endpoint is similar to a "table" in a "database". We have the option to create multiple endpoints for our app. One common pattern is to create endpoints to keep the same structured data together. For example, if we want to store the user's profile and their location in our app, we can create two separate endpoints – "profile" and "locations" – and store the data there. We don't have to define the endpoints in our application; once we write the first data the endpoints will be created.

You can learn more about the API and how you can structure the data here.

Create data into the HAT

For the Notes we can use the following structure:

{
    value: 'This is a note!',
    dateCreated: "2020-05-20T10:17:10.398Z",
}

Lets start by storing our first note into the HAT:

// src/features/Notes/Notes.js
import React, { useContext, useState } from 'react';
import { HatClient } from '@dataswift/hat-js';
import './Notes.scss';
import { appConfig } from '../../app.config';
import AuthContext from "../../components/context/AuthContext";

function Notes() {
    const [newNote, setNewNote] = useState('');
    const authContext = useContext(AuthContext);
    // The endpoint to store the Notes data
    const notesEndpoint = 'my-notes';

    // Create the configuration object to pass the auth token, api version and
    // the ssl flag to the HAT-JS
    const config = {
        token: authContext.user.token,
        apiVersion: appConfig.hatApiVersion,
        secure: appConfig.secure,
    };

    // Initialize the HatClient with the configuration file
    const hat = new HatClient(config);

    const handleChange = event => {
        setNewNote(event.target.value);
    };

    const handleSubmit = event => {
        event.preventDefault();
        createData();
    };

    const createData = async () => {
        try {
            if (!newNote) return;

            const dateCreated = new Date().toISOString();

            // Create the structure for the Note
            const body = {
                value: newNote,
                dateCreated: dateCreated,
            };
            // Use the HAT-JS library to create our first data record
            const res = await hat.hatData().create(appConfig.namespace, notesEndpoint, body);

            if (res.parsedBody) {
                // Reset the Note value
                setNewNote('');
            }
        } catch (e) {
          console.log(e.cause + e.state)
        }
    };

    return (
        <form
            onSubmit={e => handleSubmit(e)}
        >

            <input
                .
                .
                value={newNote}
                onChange={e => handleChange(e)}
            />
            .
            .
        </form>
    );
}

export default Notes;

Read data from the HAT

And now, we can create a list to fetch the available data:

// src/features/Notes/Notes.js
function Notes() {
    const [notes, setNotes] = useState([]);
    .
    .
    .
    const fetchNotes = async () => {
        try {
            // Use the HAT-JS library to get all the data records for the Note endpoint
            const res = await hat.hatData().getAllDefault(appConfig.namespace, notesEndpoint);

            if (res.parsedBody) {
                setNotes(res.parsedBody);
            }
        } catch (e) {
            console.log(e.cause + e.status);
        }
    };

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

    const ListNotes = () =>
        notes
            .sort((a, b) => new Date(b.data.dateCreated) - new Date(a.data.dateCreated))
            .map((item, index) => {
                return (
                    <li key={index}>
                        <div className={'note-row-wrapper'}>
                            <div className={'note-content'}>{item.data.value}</div>
                            <button type={'button'}>
                                <i className={'material-icons'}>delete</i>
                            </button>
                            <button type={'button'}>
                                <i className={'material-icons'}>edit</i>
                            </button>
                        </div>
                    </li>
                );
            });

    return (
        <form ..>
            .
            .
            <ul className={'notes-list'}>
                <ListNotes />
            </ul>
        </form>
    );
}

export default Notes;

We can now see our first note!

The response from the HAT is:

[
   {
      "endpoint":"testhatapp/my-notes",
      "recordId":"552551c8-6b46-451d-b91c-c2022561d0c6",
      "data":{
         "value":"this is my first note!",
         "dateCreated":"2020-05-13T10:44:59.651Z"
      }
   }
]

The "endpoint" is combined with the application namespace. The unique "recordId" for every data record that can be used for the data record deletion, and the "data" field where we can find the stored data.

Update a data record

In order to update the HAT record, we have to send the same structure as the response from the HAT. To keep the example simple, we can add an exclamation mark at the end of the value every time the user clicks the Update button. In order to update a data record, we have to send the data as structured in the HAT's response.

// src/features/Notes/Notes.js
function Notes() {
    .
    .
    const updateData = async hatRecord => {
        const noteIndex = notes.indexOf(hatRecord);
        // Update the value
        hatRecord.data.value += '!';
        try {
            // Update the HAT record. We have the option to pass multiple records with the array
            const res = await hat.hatData().update([hatRecord]);

            if (res.parsedBody) {
                setNotes(prevNotes => {
                    const draft = [...prevNotes];
                    draft[noteIndex] = res.parsedBody[0];
                    return draft;
                });
            }
        } catch (e) {
            console.log(e.cause + e.status);
        }
    };
    .
    .
    const ListNotes = () =>
        notes
            .sort((a, b) => new Date(b.data.dateCreated) - new Date(a.data.dateCreated))
            .map((item, index) => {
                return (
                    <li key={index}>
                        <div className={'note-row-wrapper'}>
                            .
                            .
                            <button type={'button'} onClick={() => updateData(item)}>
                                <i className={'material-icons'}>edit</i>
                            </button>
                        </div>
                    </li>
                );
            });

    return (
        <form ..>
            ..
        </form>
    );
}

export default Notes;

Delete data from the HAT

Using the unique record ID from the Note, we can delete it easily from the HAT:

// src/features/Notes/Notes.js
function Notes() {
    .
    .
    const deleteData = async recordId => {
        try {
            // Delete a note with record ID. We can pass an array of multiple records IDs
            const res = await hat.hatData().delete([recordId]);

            if (res.parsedBody) {
                setNotes(prevNotes => {
                    // Find index to remove from the Note array
                    const index = prevNotes.findIndex(note => note.recordId === recordId);

                    if (index !== -1) {
                        const draft = [...prevNotes];
                        draft.splice(index, 1);
                        return draft;
                    } else {
                        return prevNotes;
                    }
                });
            }
        } catch (e) {
            console.log(e.cause + e.status);
        }
    };

    const ListNotes = () =>
        notes
            .sort((a, b) => new Date(b.data.dateCreated) - new Date(a.data.dateCreated))
            .map((item, index) => {
                return (
                    <li key={index}>
                        <div className={'note-row-wrapper'}>
                            <div className={'note-content'}>{item.data.value}</div>
                            <button type={'button'} onClick={() => deleteData(item.recordId)}>
                                <i className={'material-icons'}>delete</i>
                            </button>
                            .
                            .
                        </div>
                    </li>
                );
            });

    return (
        <form ..>
            ..
        </form>
    );
}

export default Notes;

To learn more detail about HAT Login, HAT-JS and storing data into the HAT please see the Javascript Developers Guide.

Last updated