SyncState

SyncState

  • Docs
  • FAQ
  • Github
  • Hire The Creators

›Introduction

Introduction

  • Getting started
  • Installation
  • Motivation
  • Core Concepts

Recipes

  • Recipes

Examples

  • Counter
  • Counter with Undo / Redo
  • Todo app
  • Multi User Counter
  • Multi User Todo
  • Multi User Todo With Undo/Redo

Plugins

  • History
  • Remote

API Reference

  • API Overview
  • createDocStore
  • Store
  • Provider
  • useDoc
  • useSyncState

Tips & Tricks

  • Common Pitfalls & Best Practices
  • Using SyncState without React
  • Performance Tuning

FAQ

  • FAQ's

Getting started

SyncState is a document-based state management library for React and JS apps.

In a SyncState store, all your data is contained in a single document. SyncState is based on JSON patches and uses these patches to update the document.

While SyncState can very well be used as a general purpose state management solution, we created it to make it easy to build realtime multi-user, undoable apps.

Installation

You can install @syncstate/core for the core functionality from NPM.

# NPM
npm install @syncstate/core --save

# Yarn
yarn add @syncstate/core

The recommended way to use SyncState with React is to use @syncstate/react.

# NPM
npm install @syncstate/react --save

# Yarn
yarn add @syncstate/react

Both packages are available as CJS as well as ESM packages.

Basic examples

Note: All the examples are based on SyncState with React. For usage without react, refer this

SyncState maintains a single state tree for your data just like Redux. But instead of reducers, it works on mutator functions (which generate JSON patches). We'll start with a basic example and learn the concepts as we go.

Counter example

import { createDocStore } from "@syncstate/core";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider, useDoc } from "@syncstate/react";

// Create a store with an initial state
const store = createDocStore({ counter: 0 });

// Wrap your App with Provider and pass the store prop
ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById("root")
);

function App() {
    /**
     * useDoc hook with no arguments returns the root document and the function to modify the document.
     * This also adds a listener at the document root and updates the component
     * when the document changes.
     */
    const [doc, setDoc] = useDoc();

    const increment = () => {
        setDoc((doc) => {
            // This looks like a mutation but here we are updating the draft state
            // using Immer.js which generates JSON patches for our internal reducers.
            doc.counter++;
        });
    };

    const decrement = () => {
        setDoc((doc) => {
            doc.counter--;
        });
    };

    let input;

    return (
        <div>
            <button onClick={decrement}>-</button>
            {doc.count}
            <button onClick={increment}>+</button>
        </div>
    );
}

Todo example

import { createDocStore } from "@syncstate/core";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Provider, useDoc } from "@syncstate/react";

const store = createDocStore({ todos: [] });

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById("root")
);

function App() {
    /**
     * SyncState works on JSON patches. Hence, we work on paths instead of state slices.
     * Child components can be passed a path to state that they need from the document
     * through props.
     */
    const todoPath = "/todos";
    /**
     * You do need to read/modify the whole document everytime. useDoc hook accepts a
     * path parameter, it returns the state at path and the function to modify that state.
     * This also adds a listener at the path and updates the component
     * when the state at the path changes.
     */
    const [todos, setTodos] = useDoc(todoPath);

    const addTodo = (todoItem) => {
        setTodos((todos) => {
            todos.push({
                caption: todoItem,
            });
        });
    };

    let input;

    return (
        <div>
            <ul>
                {todos.map((todo) => (
                    <li>{todo.caption}</li>
                ))}
            </ul>
            <form
                onSubmit={(e) => {
                    e.preventDefault();
                    if (!input.value.trim()) {
                        return;
                    }
                    addTodo(input.value);
                    input.value = "";
                }}
            >
                <input ref={(node) => (input = node)} />
                <button type="submit">Add Todo</button>
            </form>
        </div>
    );
}

For a more complete example refer Todo example codesandbox

You can explore SyncState further:

  • Motivation
  • Core concepts
  • Recipes
  • API Reference
Installation →
  • Installation
  • Basic examples
    • Counter example
    • Todo example
Docs
Getting StartedExamplesPluginsAPI ReferenceFAQ
Community
TwitterStack OverflowDiscord
More
GitHub   Contribution Guidelines
Stars
Built with ❤️ at GeekyAnts.
Copyright © 2020 SyncState