How I created an interactive website with React, Chakra UI + Supabase

Joshen Lim
5 min readDec 5, 2022

--

To celebrate #SupaLaunchWeek 6, @supabase invited me to participate in their Content Storm, so I’m sharing a personal website I built really quickly with a supabase backend — Syn (from Synesthesia). Click here to try it out.

Introduction

After 3 weeks, I finally finished my first React.js (Vite, TS, supabase.com, chakra ui) project:

A website for people to vote on what colour comes to their mind when they listen to some classical music pieces, which then creatively displays the data through gradients and timestamps.

My reddit post on /r/reactjs currently sits at 28.6k views, 94% upvote rate, 15 total shares, and 134 upvotes.

Syn: A website for people to vote on what colour comes to their mind when they listen to classical music.
Syn: A website for people to vote on what colour comes to their mind when they listen to classical music.

So.. why did I make it? I made this website to practise full-stack web development, and to simultaneously share my passion in music with others. The pieces I chose to feature are from the Impressionist era, which has close ties to the Impressionist art movement in 19th century France. They both share similar aesthetic characteristics, focusing on atmosphere and mood, which I feel provides a listener/viewer with the creative flexibility when interpreting the works.

How to Use “Syn”

On Syn, you can vote the exact hexcode you “hear” at a specific timestamp in the music.
On Syn, you can vote the exact hexcode you “hear” at a specific timestamp in the music.

On Syn, you can vote the exact hexcode you “hear” at a specific timestamp in the music. Once submitted, all components that display data are re-rendered through a callback function to the parent component.

// SongSection.tsx
const fetchColorsData = async () => {
let { data, error, status } = await supabase
.rpc('collect_votes_of', {
songid: songId
})

if (error && status !== 406) {
console.error(error)
}
setColorData(data);
}
// ...
const refreshData = () => {
fetchColorsData();
fetchHexData();
setRefresh(true);
}
// ...
<ColorPopover dataUpdater={refreshData} songId={songId} />


// ColorPopver.tsx
...
const handleSubmit = async () => {
// ... if successful
openSuccess();
console.log(`${color} successfully submitted.`);
props.dataUpdater(); // <--
};

Above the player, each colored triangle represents a single vote. It is positioned by calculating (vote’s timestamp / songDuration) * 100%. It’s position is set to absolute in a container, and it’s left attribute is set to the value above.

Each triangle is positioned based on the timestamp.

It works pretty well, but gets cluttered when there are many votes. Future iterations should group hex codes together or hide some hex codes, or prioritise the votes with comments.

// CustomPlayer.tsx
function initMarkers() {
console.log(`Initialising markers..`)
const newMarkers = data?.map((row, id) => {
return (
<Tooltip
arrowSize={2}
hasArrow
placement='top-start'
key={id}
label={row.username
? <Stack spacing='0'>
<Text >{row.comment}</Text>
<Text fontSize='xx-small'>-{row.username}</Text>
</Stack>
: row.comment}
fontSize='sm'>
<TriangleDownIcon
color={row.hex_code}
position="absolute"
left={timestampToPercentage(row.timestamp, duration!)}
sx={{
":hover": {
transform: "scale(2)",
zIndex: "2",
filter: `drop-shadow(0px 0px 3px ${row.hex_code})`
},
transition: "all cubic-bezier(.02,.62,.26,1) 0.5s"
}}
/>
</Tooltip>
)});
setMarkers(newMarkers);
console.log(`New markers set.`);
}
Some data is displayed through a table

The data is retrieved from the database and displayed on the top right. To avoid a really long scrolling list, I limited the number of rows to 10. So, how does the backend work, and how did I learn to set it up in only 3 weeks?

Back-end: Supabase

Supabase is an open source Firebase alternative, a PostgreSQL database that provides an instant RESTful API. Start your project with a Postgres database, Authentication, instant APIs, Edge Functions, Realtime subscriptions, and Storage. To get started, head over to supabase.com to start a new project. Once inside, head over to Table Editor and create your tables.

Then, you can setup supabase in your React project to start retrieving and uploading data to the tables. Find more detailed information here on how to use their REST API.

Once you’ve installed the library into your project with
npm install --save @supabase/supabase-js and acquired your API URL and API KEY, you can put them in your .env file in your root directory.

# .env
VITE_SUPABASE_URL='https://<project_ref>.supabase.co'
VITE_SUPABASE_ANON_KEY='YOURKEY'

Then, initialise your supabase client, and call it in any component you like.

// supabaseClient.tsx
import { createClient } from '@supabase/supabase-js';

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl!, supabaseAnonKey!);
import { supabase } from '../supabaseClient';

// SongSection.tsx
const fetchHexData = async () => {
let { data, error, status } = await supabase
.from('hexcodes')
.select('*')
.eq('song_id', songId);

if (error && status !== 406) {
console.error(error);
}

console.log(`Hexcodes found with ID ${songId}`)
setHexData(data);
}

// ColorPopover.tsx, submitting a vote
const handleSubmit = async () => {
const { error } = await supabase
.from('hexcodes')
.upsert({
song_id: props.songId,
hex_code: color,
timestamp: currTime,
comment: comment,
username: name})
.select();
// ... error handling
}

If you are familiar with writing SQL queries, simple CRUD operations can be handled by built-in supabase JS functions. It’s that simple!

Tables can be populated with data directly upserted from the API calls.

Supabase: Complex Data Handling

For more complex queries, you can write your own SQL code in the SQL Editor to create SQL functions and triggers. They can be created in SQL, PL/pgSQL, and more.

Write your own SQL functions.

What I love is that supabase also auto-generates API Docs for the functions/triggers you create, so all you have to do is click a single button, and copy-paste the code snippet into your IDE. Read more about Remote Propcedure Calls (RPC) here.

Auto-generate the code-snippets for your functions with a single click.

Conclusion

From start to finish, my first ever full-stack personal website took 3 weeks, and it was definitely because I had tools like Chakra-UI and Supabase to help me make an app that functions correctly. Supabase allows me to add as many songs as I want to feature on my site, and even gather feedback from users. So far I love it, so give Syn a try today!

--

--

Joshen Lim

Software Engineer from National University of Singapore. Music > Design > Software Engineering > Tinkering.