Skip to main content

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:

Last updated on by RohitGeekyAnts