Hello everyone!
Today we will incorporate the SWR library to fetch data updates in realtime using React Hooks. While working on a project, I realized that changes made within my database were not reflected on my site unless I preformed a refresh. This bothered me as user's are expecting for changes to be made in real time.
After some researching, I stumbled upon SWR which is a react hook used for data fetching. A short TLDR is that
SWR is a strategy to first return the data from cache , then send a fetch request to revalidate the data, and finally come up with the most recent data
A more comprehensive overview can be found here
SWR seemed to have exactly what I needed so I began implementation.
I will be recreating my particular situation here using dummy data but with the same technologies. (Next Js, Supabase, SWR)
We first need to set up our NextJS application.
npx create-next-app name-of-project
Follow the prompts and once installed simply cd over to the newly created project
Installing Tailwind CSS
We will be adding tailwind css to the application as well in order to aid us with our styling.
Run the following two commands
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
The second command will create the 'tailwind.config.js' file for you, it is a config JS file for Tailwind CSS.
Copy and past the content below which will tell Tailwind CSS which files to watch out for.
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
We then will have to add this to our globals.css file.
@tailwind base;
@tailwind components;
@tailwind utilities;
BOOM! Our Nextjs application is now configured to use TailwindCSS!
Finally, run the following command
npm run dev
this will spin up the Nextjs application on your https://localhost:3000
Our local environment is now ready to go! Time to set up Supabase.
Setting up Supabase
Go to the official Supabase website and sign in.
Click + New Project and then click + New Organization to create your new project team. Then enter a name to create your organization.
Once your organization is created, we can proceed to create the project.
- Name: Pick whichever you'd like
- Database Password: Enter a strong password but save it as you may need it in the future!
- Region: Choose region closest to you
- Pricing Plan: Free will suffice, you can always upgrade later.
Finally hit Create new Project, and there we have our new Supabase project! Lets now create our tables.
Creating Tables
Click the Table Editior from the left panel of the dashboard. Then click Create a new table in the center of the screen.
A side panel will appear and we will fill it out as follows.
- Name: Pick whichever you'd like
- Description: Optional
- Enable Row Level Security (RLS) : We will skip at this time, useful for more complex applications but beyond the scope of this post
Enable Realtime: We will skip at this time, this may replace SWR but at this time I have not messed with it enough. (A new blog will come soon once I have a better understanding )
id: Unique identifier with a type uuid with the default value of uuid_generate_v4()
title: The title of post
content: The content of post
Connecting to Supabase
Next step is to connect our application to the supabase client with the use of the supabase-js dependency.
npm install @supabase/supabase-js
We must create a .env file in the root of our project and add the following lines to it.
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
The SUPABASE_URL and SUPABASE_ANON_KEY can be found by following these steps:
- Go to the "Settings" section of your project.
- Click "API" in the sidebar.
- Find your API URL in this page.
- Find your "anon" and "service_role" keys on this page.
Now restart the server in order for the changes made in the .env file to reflect properly.
Now that the API credentials are in place, lets create a helper file to initialize the Supabase client. Create a utils folder in the root of your project and within that folder, create a supabaseClient.js file. Paste the following inside.
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
Now whenever we need to interact with Supabase, we can simply call upon by importing utils/supabase.js.
Creating first post
Our index page is currently just the default so lets change it up. Copy and paste the code below to have
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className='flex items-center justify-center h-screen flex-col'>
<h1 className="text-3xl font-bold ">
Create a post!
</h1>
<form className=' p-20 flex gap-8'>
<input type='text' className='shadow p-2 border-b w-full outline-none' placeholder='Type here...'></input>
<button type='submit' className='bg-blue-300 p-2 rounded' >Submit</button>
</form>
</main>
</div>
Inserting post into Supabase
In order for our post to reach Supabase, there are a few things we need.
- Import the supabase client
- Adding a title,content state to hold our values.
- Adding an onSubmit function to handle that data.
Add the following to your code.
import { supabase } from '../utils/supabaseClient'
...
const [title, setTitle] = useState('')
const [content,setContent] = useState('')
const onSubmit = async (e) => {
e.preventDefault()
try {
const {data, error} = await supabase
.from('Posts')
.insert([
{title: title, content: content}
])
if(data){
console.log('success')
}
} catch (error) {
console.log(error)
}
setTitle('')
setContent('')
}
...
<form onSubmit={onSubmit} className=' p-20 flex flex-col gap-8'>
<input type='text' className='shadow p-2 border-b w-full outline-none'
placeholder='Title...' onChange={e => setTitle(e.target.value)} value={title} ></input>
<input type='text' className='shadow p-2 border-b w-full outline-none'
placeholder='Content...' onChange={e => setContent(e.target.value)} value={content} ></input>
<button type='submit' className='bg-blue-300 p-2 rounded' >Submit</button>
</form>
Fill out and submit the form. Now if everything is working correctly you should see a 'success' message in your console and the data on your Supabase table!
Great! Now lets retrieve and display the data within our application.
Retrieving Data from Supabase
Next steps will be creating a components folder at the root of your project. Within that folder, create a Posts.js file.
Copy and paste the code below
import React from 'react'
const Posts = ({title,content}) => {
return (
<div className='p-10 bg-zinc-200 rounded w-1/3 mb-10 '>
<div className='flex flex-col gap-y-5'>
<h1 className='text-xl font-bold'>{title}</h1>
<p className='font-semibold'>{content}</p>
</div>
</div>
)
}
export default Posts
This will be our Posts component and we are passing it the title ** and **content props that we will see shortly.
Back in our index.js file, we will be adding a getTableData function that will retrieve all the posts from supabase and storing it within our posts state.
const [posts, setPosts] = useState()
...
const getTableData = async () =>{
try {
const {data, error} = await supabase
.from('Posts')
.select('*')
if(data){
setPosts(data)
}
} catch (error) {
console.log(error)
}
}
useEffect(()=>{
getTableData()
}, [])
We must invoke this function by calling it within a useEffect hook. This will call the function only once, when the page is first rendered.
After importing the Posts component at the top of the application, add the following code below to your index.js file.
import Posts from '../components/Posts'
...
<div className='pb-20 flex flex-col items-center justify-center '>
{posts && posts.map(elem =>{
return(<Posts key={elem.id} title={elem.title} content={elem.content} />)
})}
</div>
This will display the data only when the posts state is not null. We are then mapping through the data and passing these props:
- key : Elements identity or (unique identifier)
- title : title of post
- content : content of post
We can now successfully view our data but only if we manually refresh. This is where SWR steps in.
Implementing SWR
Lets begin by installing SWR and refactoring our code a bit.
npm install swr
Now we could leave our code as is and leave everything on client side, however lets take advantage of what makes Nextjs so powerful.
We will move all our api related tasks into the api directory.
Create a new file posts.js and paste the code found below within.
import { supabase } from "../../utils/supabaseClient";
const PostsApi = async (req,res) =>{
if(req.method === 'GET'){
const {data,error} = await supabase.from('Posts')
.select('*')
if(error){
return res.status(500).json({message: error})
}
return res.status(200).json({data})
}
}
export default PostsApi
Lets go over what is going on so far.
We have imported our supabase client in order to retrieve neccessary data. There is a PostsApi asychronous function taking in a req which is a request being made to the API and res which is a response based off the request made. If a 'GET' request has been made, we will then retrieve the data from Supabase and return it.
Now we can interact with our API by fetching 'api/posts'!
Refactoring index.js
Now that our API is complete, we can now remove our getTableData and useEffect to make use of SWR.
Remove the following code
const [posts, setPosts] = useState()
...
const getTableData = async () =>{
try {
const {data, error} = await supabase
.from('Posts')
.select('*')
if(data){
setPosts(data)
}
} catch (error) {
console.log(error)
}
}
useEffect(()=>{
getTableData()
}, [])
Replace it with
import useSWR from 'swr'
const fetcher = (url) => fetch(url, {method: 'GET'}).then(res => res.json());
...
const {data: posts ,error} = useSWR('api/posts', fetcher)
Finally we just need to wrap our application in the context SWRConfig providing our global configuration options. Head over to your _app.js file and add the following
import '../styles/globals.css'
import { SWRConfig } from 'swr'
function MyApp({ Component, pageProps }) {
return (<SWRConfig
value={{
refreshInterval: 10000,
}}
>
<Component {...pageProps} />
</SWRConfig>)
}
export default MyApp
In our configuration, we are simply telling SWR to refresh our data every 10 seconds. There are several other options that are available to use and they can be found here.
All done!
Woohoo! Our application now can successfully insert data and see it in real time how exciting! In summary, we have created a Nextjs / Tailwind CSS / Supabase application accompanied with the SWR library to help with our data fetching. A gif is provided below to see it in action.
We have only scratched the surface of a full on CRUD application and there is certainly room for improvements but I will leave that up to you to experiment and learn on your own.
I hope you have found this helpful and I hope to see you on my next post. Take care!
Top comments (1)
So it seems that when you submit your post, it would just silently submit, and wait 0-10s for the interval to refresh the post listing?
It seems more like it could do an optimistic update, and invalidate the data