Skip to main content
In this guide, you will:
  1. Set up a new React project with Basic
  2. Create a simple To-Do UI
  3. Connect to a Basic project and set up your schema
  4. Add sign in and sign out functionality
  5. Hook up to Basic DB so that your users can save their to-dos across their devices

Create a new React project

1

Create a new Vite React project

Terminal
npm create vite@latest my-todo-app -- --template react-ts
cd my-todo-app
npm install
2

Install Basic and Tailwind

Terminal
npm install @basictech/react
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
3

Configure Tailwind

Update tailwind.config.js:
tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Replace src/index.css with:
src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
4

Start your dev server

Terminal
npm run dev
Navigate to http://localhost:5173 to see your app running.

Create a simple To-Do UI

1

Create the basic layout

Replace the code in your src/App.tsx file:
src/App.tsx
import './App.css'

function App() {
  return (
    <div className="flex flex-col items-center p-8">
      <h1 className="text-2xl font-bold mb-4">my lofi to-do app</h1>
      <div className="flex gap-2">
        <input
          type="text"
          placeholder="Add a to-do"
          className="border rounded px-2 py-1"
        />
        <button className="bg-blue-500 text-white px-3 py-1 rounded">
          Add
        </button>
      </div>
    </div>
  )
}

export default App

Connect to a Basic project

1

Create a Basic account

Go to admin.basic.tech and create a free account. Then create a new project and copy your Project ID.
2

Create basic.config.ts

Create a basic.config.ts file in your project root:
basic.config.ts
export const schema = {
  project_id: "YOUR_PROJECT_ID", // Replace with your project ID
  version: 0,
  tables: {
    todos: {
      type: "collection",
      fields: {
        name: {
          type: "string"
        },
        completed: {
          type: "boolean"
        }
      }
    }
  }
}
3

Add BasicProvider to main.tsx

Update src/main.tsx:
src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BasicProvider } from '@basictech/react'
import { schema } from '../basic.config'
import App from './App'
import './index.css'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <BasicProvider schema={schema}>
      <App />
    </BasicProvider>
  </StrictMode>,
)

Add sign in and sign out functionality

1

Add auth buttons to App.tsx

Update src/App.tsx:
src/App.tsx
import { useBasic } from '@basictech/react'
import './App.css'

function App() {
  const { signIn, signOut, isSignedIn, user } = useBasic()

  return (
    <div className="flex flex-col items-center p-8">
      <h1 className="text-2xl font-bold mb-4">my lofi to-do app</h1>
      <div className="flex gap-2">
        <input
          type="text"
          placeholder="Add a to-do"
          className="border rounded px-2 py-1"
        />
        <button className="bg-blue-500 text-white px-3 py-1 rounded">
          Add
        </button>
      </div>

      <div className="mt-8">
        {isSignedIn ? (
          <div className="text-center">
            <p>Signed in as: {user?.email}</p>
            <button 
              onClick={signOut}
              className="mt-2 text-red-500 hover:underline"
            >
              Sign Out
            </button>
          </div>
        ) : (
          <button 
            onClick={signIn}
            className="bg-green-500 text-white px-4 py-2 rounded"
          >
            Sign In
          </button>
        )}
      </div>
    </div>
  )
}

export default App

Hook up to Basic DB

1

Add task creation

Update src/App.tsx to add state and the addTask function:
src/App.tsx
import { useBasic } from '@basictech/react'
import { useState } from 'react'
import './App.css'

function App() {
  const { signIn, signOut, isSignedIn, user, db } = useBasic()
  const [taskInput, setTaskInput] = useState('')

  const addTask = async () => {
    if (!taskInput.trim()) return
    
    await db.collection('todos').add({
      name: taskInput,
      completed: false
    })
    setTaskInput('')
  }

  return (
    <div className="flex flex-col items-center p-8">
      <h1 className="text-2xl font-bold mb-4">my lofi to-do app</h1>
      <div className="flex gap-2">
        <input
          type="text"
          placeholder="Add a to-do"
          className="border rounded px-2 py-1"
          value={taskInput}
          onChange={(e) => setTaskInput(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && addTask()}
        />
        <button 
          className="bg-blue-500 text-white px-3 py-1 rounded"
          onClick={addTask}
        >
          Add
        </button>
      </div>

      <div className="mt-8">
        {isSignedIn ? (
          <div className="text-center">
            <p>Signed in as: {user?.email}</p>
            <button 
              onClick={signOut}
              className="mt-2 text-red-500 hover:underline"
            >
              Sign Out
            </button>
          </div>
        ) : (
          <button 
            onClick={signIn}
            className="bg-green-500 text-white px-4 py-2 rounded"
          >
            Sign In
          </button>
        )}
      </div>
    </div>
  )
}

export default App
2

Display and delete tasks

Add useQuery to display tasks and a delete function:
src/App.tsx
import { useBasic, useQuery } from '@basictech/react'
import { useState } from 'react'
import './App.css'

function App() {
  const { signIn, signOut, isSignedIn, user, db } = useBasic()
  const [taskInput, setTaskInput] = useState('')
  
  // Subscribe to all todos - auto-updates when data changes
  const tasks = useQuery(() => db.collection('todos').getAll())

  const addTask = async () => {
    if (!taskInput.trim()) return
    
    await db.collection('todos').add({
      name: taskInput,
      completed: false
    })
    setTaskInput('')
  }

  const deleteTask = async (id: string) => {
    await db.collection('todos').delete(id)
  }

  return (
    <div className="flex flex-col items-center p-8">
      <h1 className="text-2xl font-bold mb-4">my lofi to-do app</h1>
      <div className="flex gap-2">
        <input
          type="text"
          placeholder="Add a to-do"
          className="border rounded px-2 py-1"
          value={taskInput}
          onChange={(e) => setTaskInput(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && addTask()}
        />
        <button 
          className="bg-blue-500 text-white px-3 py-1 rounded"
          onClick={addTask}
        >
          Add
        </button>
      </div>

      {/* Task list */}
      <div className="mt-6 w-full max-w-md">
        {tasks?.map((task: any) => (
          <div
            key={task.id}
            className="flex items-center justify-between p-3 border-b"
          >
            <span>{task.name}</span>
            <button
              onClick={() => deleteTask(task.id)}
              className="text-red-500 hover:text-red-700"
            >
              Delete
            </button>
          </div>
        ))}
      </div>

      <div className="mt-8">
        {isSignedIn ? (
          <div className="text-center">
            <p>Signed in as: {user?.email}</p>
            <button 
              onClick={signOut}
              className="mt-2 text-red-500 hover:underline"
            >
              Sign Out
            </button>
          </div>
        ) : (
          <button 
            onClick={signIn}
            className="bg-green-500 text-white px-4 py-2 rounded"
          >
            Sign In
          </button>
        )}
      </div>
    </div>
  )
}

export default App
And that’s it! You’ve built a local-first to-do app that allows your users to sign in and save their to-dos across their devices. Notice how snappily all the changes are reflected - from task creation to deletion. This is the power of local-first architecture!

Next steps