Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
629 changes: 13 additions & 616 deletions README.md

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions adapters/storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A persistent storage adapter for [contection](https://github.com/alexdln/contection) that automatically saves and restores state to browser storage (localStorage or sessionStorage).

[npm](https://www.npmjs.com/package/contection-storage-adapter) • [demo](https://www.contection.dev/)
[npm](https://www.npmjs.com/package/contection-storage-adapter)

## Overview

Expand Down Expand Up @@ -150,8 +150,6 @@ The adapter automatically detects storage availability and gracefully degrades i

The repository includes example applications demonstrating storage adapter capabilities:

- **[demo](examples/demo)** - Demonstrates fine-grained subscriptions with various optimization strategies, storage adapters for state persistence, and integration with `contection-viewport` and `contection-top-layer` modules.

- **[nextjs-bsky](examples/nextjs-bsky)** - Showcases performance improvements in Next.js applications using `cacheComponents` and a combined client-server architecture with next-cookie adapter and storage adapter for state persistence.

## License
Expand Down
35 changes: 35 additions & 0 deletions docs/01-getting-started/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Getting Started

Contection extends React Context API with fine-grained subscriptions and computed values.

## Installation

```bash switcher tab="npm"
npm install contection
```

```bash switcher tab="pnpm"
pnpm add contection
```

```bash switcher tab="yarn"
yarn add contection
```

```bash switcher tab="bun"
bun add contection
```

**Requirements:** React 18+ (React 19 recommended), TypeScript 4.5+ (optional, types included)

## Features

- **Granular Subscriptions** - Subscribe to specific store keys
- **Selective Re-renders** - Components update only when subscribed keys change
- **Computed Values** - Derive state with mutation functions
- **Type-Safe** - Full TypeScript support
- **SSR Compatible** - Works with Next.js and other SSR frameworks

## Next

- [Quick Start](./quick-start.md) - Get up and running in minutes
143 changes: 143 additions & 0 deletions docs/01-getting-started/quick-start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Quick Start

## Step 1: Create a Store

```tsx filename="store.ts" switcher tab="TypeScript"
import { createStore } from "contection";

type AppStoreType = {
user: { name: string; email: string };
count: number;
theme: "light" | "dark";
};

const AppStore = createStore<AppStoreType>({
user: { name: "", email: "" },
count: 0,
theme: "light",
});

export { AppStore };
export type { AppStoreType };
```

```js filename="store.js" switcher tab="JavaScript"
import { createStore } from "contection";

const AppStore = createStore({
user: { name: "", email: "" },
count: 0,
theme: "light",
});

export { AppStore };
```

## Step 2: Provide the Store

```tsx filename="App.tsx" switcher tab="TypeScript"
import { AppStore } from "./store";

function App() {
return (
<AppStore>
<YourComponents />
</AppStore>
);
}
```

```jsx filename="App.jsx" switcher tab="JavaScript"
import { AppStore } from "./store";

function App() {
return (
<AppStore>
<YourComponents />
</AppStore>
);
}
```

Each Provider creates an isolated scope, similar to React Context.Provider.

## Step 3: Use the Store

### Subscribe to Specific Keys

```tsx filename="Counter.tsx" switcher tab="TypeScript"
import { useStore } from "contection";
import { AppStore } from "./store";

function Counter() {
// Component re-renders only when 'count' value changes
const { count } = useStore(AppStore, { keys: ["count"] });

return (
<div>
<p>Count: {count}</p>
</div>
);
}
```

```jsx filename="Counter.jsx" switcher tab="JavaScript"
import { useStore } from "contection";
import { AppStore } from "./store";

function Counter() {
const { count } = useStore(AppStore, { keys: ["count"] });

return (
<div>
<p>Count: {count}</p>
</div>
);
}
```

### Access Nested Values

```tsx filename="UserEmail.tsx" switcher tab="TypeScript"
import { useStore } from "contection";
import { AppStore } from "./store";

function UserEmail() {
// Component re-renders only when 'user.email' changes
const email = useStore(AppStore, {
keys: ["user"],
mutation: (store) => store.user.email,
});

return <p>E-mail: {email}</p>;
}
```

### Update the Store

```tsx filename="CounterControls.tsx" switcher tab="TypeScript"
import { useStoreReducer } from "contection";
import { AppStore } from "./store";

function CounterControls() {
// useStoreReducer never triggers re-render
const [store, setStore] = useStoreReducer(AppStore);

return (
<div>
<button onClick={() => alert(store.count)}>Show count</button>
<button onClick={() => setStore({ count: store.count + 1 })}>
Increment
</button>
<button onClick={() => setStore((prev) => ({ count: prev.count - 1 }))}>
Decrement
</button>
</div>
);
}
```

## Next

- [Stores and Providers](../02-core/stores-and-providers.md)
- [Hooks](../02-core/hooks.md)
10 changes: 10 additions & 0 deletions docs/02-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Core Topics

Contection provides two main primitives: **stores** for state containers and **hooks** for accessing them.

Stores are created once and scoped via Providers. Unlike global state managers, each Provider maintains its own isolated state - components only see the nearest Provider's data.

Hooks connect components to stores. `useStore` subscribes to specific keys and triggers re-renders; `useStoreReducer` provides state access and updates without re-renders.

- [Stores and Providers](./stores-and-providers.md) - Create stores, scope with Providers, handle multiple instances
- [Hooks](./hooks.md) - Subscribe with `useStore`, update with `useStoreReducer`, compute derived values
188 changes: 188 additions & 0 deletions docs/02-core/hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Hooks

## useStore

Subscribes to store state and triggers re-renders when subscribed keys change.

```tsx
import { useStore } from "contection";

function Counter() {
const { count } = useStore(AppStore, { keys: ["count"] });
return <div>Count: {count}</div>;
}
```

### Options

#### `keys?: string[]`

Keys to subscribe to. Omit for all keys.

```tsx
// Single key
const { count } = useStore(AppStore, { keys: ["count"] });

// Multiple keys - re-renders when 'user' OR 'theme' changes
const { user, theme } = useStore(AppStore, { keys: ["user", "theme"] });

// All keys - re-renders when ANY key changes
const store = useStore(AppStore);
```

#### `mutation?: (newStore, prevStore?, prevMutatedStore?) => T`

Compute derived values:

```tsx
const email = useStore(AppStore, {
keys: ["user"],
mutation: (store) => store.user.email,
});

const initials = useStore(AppStore, {
keys: ["user"],
mutation: (store) => store.user.name.split(" ").map((n) => n[0]).join("").toUpperCase(),
});
```

**Memoization** - use previous values to avoid recomputation:

```tsx
const filtered = useStore(AppStore, {
keys: ["items", "filter"],
mutation: (newStore, prevStore, prevMutatedStore) => {
if (prevMutatedStore && prevStore?.filter === newStore.filter) {
return prevMutatedStore;
}
return newStore.items.filter((item) => item.includes(newStore.filter));
},
});
```

#### `enabled?: "always" | "never" | "after-hydration" | (store) => boolean`

Conditional subscription:

```tsx
// Role-based
const { adminData } = useStore(AppStore, {
keys: ["adminData"],
enabled: (store) => store.user.role === "admin",
});

// SSR - active after mount
const { clientData } = useStore(AppStore, {
keys: ["clientData"],
enabled: "after-hydration",
});
```

## useStoreReducer

Returns store state and update functions **without triggering re-renders**.

```tsx
import { useStoreReducer } from "contection";

function Counter() {
const [store, setStore] = useStoreReducer(AppStore);

return (
<div>
<button onClick={() => alert(store.count)}>Show count</button>
<button onClick={() => setStore({ count: store.count + 1 })}>
Increment
</button>
</div>
);
}
```

### Return Value

```tsx
const [store, setStore, subscribe, unsubscribe] = useStoreReducer(AppStore);
```

- `store` - Current state (read-only, no re-renders)
- `setStore` - Update function (object or function)
- `subscribe` - Imperative subscription
- `unsubscribe` - Remove subscription

### Updating State

```tsx
// Object update
setStore({ count: 10 });
setStore({ count: 10, theme: "dark" });

// Function update
setStore((prev) => ({ count: prev.count + 1 }));
```

### Imperative Subscriptions

Subscribe to key changes outside React's render cycle:

```tsx
const [, , subscribe] = useStoreReducer(AppStore);

useEffect(() => {
const unsubscribe = subscribe("user", (user) => {
analytics.track("user_updated", { userId: user.email });
});
return unsubscribe;
}, [subscribe]);
```

**Use cases:**

```tsx
// DOM manipulation
subscribe("theme", (theme) => {
document.documentElement.setAttribute("data-theme", theme);
});

// WebSocket sync
const ws = new WebSocket("wss://example.com");
subscribe("data", (data) => ws.send(JSON.stringify(data)));
```

## Consumer Component

Render props pattern with same options as `useStore`:

```tsx
<AppStore.Consumer options={{ keys: ["user"] }}>
{({ user }) => (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)}
</AppStore.Consumer>

// With mutation
<AppStore.Consumer
options={{
keys: ["user"],
mutation: (store) => store.user.email,
}}
>
{(email) => <p>E-mail: {email}</p>}
</AppStore.Consumer>
```

Prefer `useStore` for better TypeScript inference.

## Comparison

| Feature | `useStore` | `useStoreReducer` |
|---------|-----------|-------------------|
| Re-renders on changes | Yes | No |
| Subscribe to keys | Yes | No |
| Computed values | Yes | No |
| Read state | Yes | Yes |
| Update state | No | Yes |
| Imperative subscriptions | No | Yes |
Loading