Tutorials
Color Grid (Next.js)

Making a collaborative Color Grid

Create a color grid and make pixel pictures with friends. As users edit the color grid, changes will be automatically persisted and synced in real-time across clients.

Your GIF

Getting Started

Create a NextJS app and install y-sweet.

npx create-next-app@latest colorgrid --ts --tailwind
cd colorgrid
npm install @y-sweet/sdk @y-sweet/react

Run your app

npm run dev

Enable state synchronization and persistence

Get a connection string

If you haven't already, copy your connection string from the y-sweet dashboard (opens in a new tab). This connection string is used to connect your app to the y-sweet server that handles state sync and persistence.

getOrCreateDocAndToken

Replace the contents of src/page.tsx with the following. Pass your connection string key to getOrCreateDocAndToken. Change the definition of CONNECTION_STRING to include your personal connection string.

This connection string is then passed to YDocProvider, which creates a client-side websocket connection. This is how the client speaks to the y-sweet server. In this example, the getClientToken function is a React Server Action (opens in a new tab), as denoted by the 'use server' directive, which means this function will be executed on the server. In a production application, you will need to perform an auth check at the top of this function just as you would for any API endpoint.

page.tsx
import { getOrCreateDocAndToken } from '@y-sweet/sdk'
import { YDocProvider } from '@y-sweet/react'
import { randomId } from '@/lib/utils'
import { ColorGrid } from './ColorGrid'
 
// *****************************************************
// ** TO DO: Replace this with your connection string **
// *****************************************************
// For simplicity, we are hard-coding the connection string in the
// file. In a real app, you should instead pass this in through a
// secret store or environment variable.
const CONNECTION_STRING = '[paste your connection string]'
 
export default function Home({ searchParams }: { searchParams: { doc: string } }) {
  const docId = searchParams.doc ?? randomId()
  async function getClientToken() {
    'use server'
    // In a production application, you'd want to authenticate the user and
    // check that they have access to the given doc.
    return await getOrCreateDocAndToken(CONNECTION_STRING, docId)
  }
  return (
    <YDocProvider docId={docId} setQueryParam="doc" authEndpoint={getClientToken}>
      <ColorGrid />
    </YDocProvider>
  )
}

Note that you'll see errors when you run this code, because we haven't defined ColorGrid. We'll do that in the next section.

The Color Grid

Create a Color Grid Component

In another file that runs on the client side with use client, create your color grid component. Using the y-sweet hook useMap (opens in a new tab) to sync client state and persist it across sessions.

ColorGrid.tsx
'use client'
 
import { useMap } from '@y-sweet/react'
 
const GRID_SIZE = 10
const DEFAULT_COLOR = 'white'
// For simplicty, we'll just add one color option
// We'll add the color picker in the next step
const COLOR_SELECTIONS = ['black']
 
export function ColorGrid() {
  // Initialize our Color Grid as an object of cell positions that map to
  // color values. `useMap` returns a Y.Map, and subscribes the component
  // to changes in that map.
  const items = useMap<string>('colorgrid')
 
  return (
    <div className="space-y-3 p-4 lg:p-8">
      <div>Color Grid</div>
      <table>
        <tbody>
          {Array.from({ length: GRID_SIZE }, (x, i) => (
            <tr key={i}>
              {Array.from({ length: GRID_SIZE }, (x, j) => {
                // Turn the cell position as a string.
                const key = `${i},${j}`
                const itemColor = items!.get(key)
                return (
                  <td key={key}>
                    <div
                      className="w-8 h-8 cursor-pointer"
                      style={{ backgroundColor: itemColor || DEFAULT_COLOR }}
                      onClick={() => items!.set(key, COLOR_SELECTIONS[0])}
                    />
                  </td>
                )
              })}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

Add a color picker

  1. Add a list of hex color codes to COLOR_SELECTIONS
  2. Keep selected color in local state, since this information doesn't need to be persisted across clients
  3. Set the value for a selected cell the active color onClick
ColorGrid.tsx
'use client'
 
import { useMap } from '@y-sweet/react'
import { useState } from 'react'
 
const GRID_SIZE = 10
const COLOR_SELECTIONS = [
  '#500724',
  '#831843',
  '#9d174d',
  '#be185d',
  '#db2777',
  '#f472b6',
  '#f9a8d4',
  null,
]
const DEFAULT_COLOR = 'white'
 
export function ColorGrid() {
  // Initialize our Color Grid as an object of cell positions that map to
  // color values. `useMap` returns a Y.Map, and subscribes the component
  // to changes in that map.
  const items = useMap<string>('colorgrid')
  // Keep the selected color as local state.
  const [selectedColor, setSelectedColor] = useState<string | null>(COLOR_SELECTIONS[0])
 
  return (
    <div className="space-y-3 p-4 lg:p-8">
      <div>Color Grid</div>
      <div className="space-x-2 flex flex-row">
        {COLOR_SELECTIONS.map((c) => (
          <div
            key={c}
            className={`w-8 h-8 cursor-pointer ring-2 ring-offset-1 ${
              c === selectedColor ? 'ring-pink-100' : 'ring-transparent'
            }`}
            style={{ backgroundColor: c ?? DEFAULT_COLOR }}
            onClick={() => setSelectedColor(c)}
          />
        ))}
      </div>
      <table>
        <tbody>
          {Array.from({ length: GRID_SIZE }, (x, i) => (
            <tr key={i}>
              {Array.from({ length: GRID_SIZE }, (x, j) => {
                // Turn the cell position as a string.
                const key = `${i},${j}`
                const item = items!.get(key)
                return (
                  <td key={key}>
                    <div
                      className="w-8 h-8 cursor-pointer"
                      style={{ backgroundColor: item || DEFAULT_COLOR }}
                      onClick={() => {
                        if (selectedColor === null) {
                          items!.delete(key)
                        } else {
                          items!.set(key, selectedColor)
                        }
                      }}
                    />
                  </td>
                )
              })}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

Run the app

We have enough here to test the to do list.

Run npm run dev and navigate to localhost:3000 (opens in a new tab) or whichever port you're running on.

When you load the page, you'll notice a ?doc=... parameter appended to the url. This is a document ID automatically created by getOrCreateDocAndToken. If this ID is supplied in the url, the same doc will appear. Otherwise, a new doc will be created.

Using the debugger

In the developer tools, you'll find a link to the Y-Sweet Debugger. You can use it to inspect the state of your app.

Your GIF

How to open developer tools:

Next Steps

Refer to our Color Grid demo

You can see all the code in action in our color grid demo (opens in a new tab) and style your Color Grid to match the cover image.

y-sweet was created by Jamsocket.