# fluidstate-preact

**View interactive documentation on the official website.**:

- [Preact Reactive Layer](https://fluidstate.gitlab.io/layers_preact.html)
- [Preact Bindings](https://fluidstate.gitlab.io/integrations_preact.html)

`fluidstate-preact` provides Preact bindings for the [`fluidstate`](https://gitlab.com/fluidstate/fluidstate) fine-grained reactivity library. It allows you to build highly performant Preact applications where components update automatically and efficiently in response to state changes, without manual subscriptions or monolithic state updates.

## A Library with Two Roles

`fluidstate-preact` is designed to serve two distinct but complementary purposes:

1.  **Standalone Reactive Layer:** It offers a reactive layer for `fluidstate` built on top of `@preact/signals-core`. This part of the library can be used independently of Preact, enabling you to leverage the performance of Preact Signals in any `fluidstate`-powered JavaScript application.

2.  **Preact Bindings:** It provides a rich set of tools—hooks, Higher-Order Components (HOCs), and signal-based utilities—to seamlessly integrate `fluidstate`'s reactive state into your Preact components, offering multiple patterns for different use cases.

## Features

- **Fine-grained reactivity:** Components re-render only when the specific data they use changes.
- **Multiple Integration Patterns:** Choose from the `useReactive` hook, `withReactive` HOC, or the hyper-performant `useSignals` hook.
- **Standalone Reactive Layer:** A `fluidstate` reactive layer powered by `@preact/signals-core` that can be used in any environment, even without Preact.
- **`createReactiveSetup`:** A powerful pattern for creating encapsulated, reusable, and testable state management modules with Providers and consumer hooks/HOCs.
- **Seamless integration:** Works with the entire `fluidstate` ecosystem, regardless of the underlying reactive layer.

## Installation

You will need `fluidstate` and `fluidstate-preact`. Depending on which features you use, you may also need to install `preact`, `@preact/signals-core`, and `@preact/signals` as peer dependencies.

```bash
# Core library
npm install fluidstate fluidstate-preact

# or
yarn add fluidstate fluidstate-preact
```

**Optional Peer Dependencies:**

- To use the **Preact bindings** (`useReactive`, `withReactive`, `createReactiveSetup`): install `preact`.
- To use the **`useSignals` hook**: install `preact` and `@preact/signals`.
- To use the **standalone reactive layer**: install `@preact/signals-core`.

Example: To use everything, you would install:

```bash
npm install preact @preact/signals @preact/signals-core
# or
yarn add preact @preact/signals @preact/signals-core
```

## Part 1: Standalone Reactive Layer (via `@preact/signals`)

`fluidstate-preact` includes a reactive layer that bridges `fluidstate` with `@preact/signals-core`. This allows you to use `fluidstate`'s ergonomic API while leveraging the highly efficient Preact Signals engine under the hood.

This layer can be used in any project, even one that doesn't use Preact for its UI, making it a powerful choice for any `fluidstate`-based application.

### Setup

To enable `fluidstate`, you must provide it with a reactive layer. This is a one-time setup at your application's entry point.

```typescript
// file: main.ts
import {
	provideReactiveLayer,
	createReactive,
	createReaction,
	runAction,
} from "fluidstate";
// Note the import path for the reactive layer
import { getReactiveLayer } from "fluidstate-preact/reactive-layer";

// 1. Get the reactive layer from fluidstate-preact
const reactiveLayer = getReactiveLayer();

// 2. Provide it to fluidstate
provideReactiveLayer(reactiveLayer);

// 3. You can now use the fluidstate API throughout your application
const counter = createReactive({ value: 0 });

createReaction(() => {
	console.log(`Counter value: ${counter.value}`);
});
// LOGS: Counter value: 0

runAction(() => {
	counter.value++;
});
// LOGS: Counter value: 1
```

After this setup, you can use the `fluidstate` package for all your state management tasks. For more details on `createReactive`, `createReaction`, and other features, please refer to the [main `fluidstate` documentation](https://gitlab.com/fluidstate/fluidstate).

## Part 2: Preact Bindings

This part of the library is focused on integrating your `fluidstate` reactive state into Preact components.

### One-Time Setup

Before using any Preact bindings, you must configure `fluidstate` with a reactive layer, as shown in Part 1. You can use **any** `fluidstate` reactive layer (e.g., `fluidstate-alien`, `fluidstate-mobx` or `fluidstate-preact/reactive-layer`). The Preact bindings are agnostic to the underlying engine.

### Three Ways to Consume State

`fluidstate-preact` offers three primary ways to make your components reactive, each with different trade-offs.

#### 1. `useReactive` Hook

The `useReactive` hook is a flexible way to consume reactive state. You provide a function that reads from your state, and the hook ensures your component re-renders whenever any of the accessed state properties change.

_Example Store:_

```tsx
// file: counter-store.ts
import { createReactive } from "fluidstate";

export type CounterStore = {
	count: number;
	increment: () => void;
	decrement: () => void;
};

export const counterStore = createReactive<CounterStore>({
	count: 0,
	increment() {
		counterStore.count++;
	},
	decrement() {
		counterStore.count--;
	},
});
```

_Component using `useReactive`:_

```tsx
// file: counter.tsx
import { useReactive } from "fluidstate-preact";
import { counterStore } from "./counter-store";

export const Counter = () => {
	// The component will re-render only when `counterStore.count` changes.
	const count = useReactive(() => counterStore.count);

	return (
		<div>
			<h1>Counter: {count}</h1>
			<button onClick={counterStore.increment}>Increment</button>
			<button onClick={counterStore.decrement}>Decrement</button>
		</div>
	);
};
```

#### 2. `withReactive` HOC

The `withReactive` HOC provides an alternative, wrapping your component to make it automatically re-render when any reactive state it accesses changes.

```tsx
// file: counter-hoc.tsx
import { withReactive } from "fluidstate-preact";
import { counterStore } from "./counter-store";

export const CounterComponent = withReactive(() => {
	return (
		<div>
			<h1>Counter: {counterStore.count}</h1>
			<button onClick={counterStore.increment}>Increment</button>
			<button onClick={counterStore.decrement}>Decrement</button>
		</div>
	);
});
```

#### 3. `useSignals` Hook

The `useSignals` hook is the most performant option. It bridges `fluidstate` state with `@preact/signals`, allowing you to update parts of your UI without re-rendering the entire component. This requires `@preact/signals` to be installed.

You can import `useSignals` from `fluidstate-preact/signals`.

```tsx
// file: counter-signals.tsx
import { useSignals } from "fluidstate-preact/signals";
import { counterStore } from "./counter-store";

export const Counter = () => {
	// `get` is a function that creates a Preact signal for a given piece of state.
	const get = useSignals();

	return (
		<div>
			{/* The component itself only renders once. Only the text nodes update. */}
			<h1>Counter: {get(() => counterStore.count)}</h1>
			<button onClick={counterStore.increment}>Increment</button>
			<button onClick={counterStore.decrement}>Decrement</button>
		</div>
	);
};
```

### Recommended Usage: `createReactiveSetup`

For larger applications, `createReactiveSetup` helps create encapsulated, testable, and reusable state modules. It generates a Provider, consumer hooks/HOCs, and a `Context` object for a specific slice of state.

Any reactions (e.g., for side effects) returned in a `reactions` array will be automatically cleaned up when the Provider unmounts.

#### Example: User Profile Module

**1. Create the reactive setup**

```tsx
// file: user-profile-setup.ts
import {
	createReactive,
	Reaction,
	createReaction,
	cloneInert,
} from "fluidstate";
import { createReactiveSetup } from "fluidstate-preact";

export type UserProfileSetupProps = { initialName: string };
export type UserProfileData = { name: string; email: null | string };
export type UserProfileActions = {
	updateName: (newName: string) => void;
	setEmail: (email: string) => void;
};
export type UserProfileState = {
	data: UserProfileData;
	actions: UserProfileActions;
	reactions: Reaction[];
};

// Generate and rename the setup utilities for clarity.
export const {
	ReactiveProvider: UserProfileProvider,
	useReactiveState: useUserProfileState,
	withReactiveState: withUserProfileState,
	MockProvider: MockUserProfileProvider,
	StateContext: UserProfileContext, // Export the context for use with `useSignals`
} = createReactiveSetup((props: UserProfileSetupProps): UserProfileState => {
	const data = createReactive<UserProfileData>({
		name: props.initialName,
		email: null,
	});
	const actions = createReactive<UserProfileActions>({
		updateName(newName: string) {
			data.name = newName;
		},
		setEmail(email: string) {
			data.email = email;
		},
	});
	const syncProfile = createReaction(() => {
		fetch(`api/syncProfile`, {
			method: "POST",
			body: JSON.stringify(cloneInert(data)),
		});
	});
	return { data, actions, reactions: [syncProfile] };
});
```

**2. Provide the state**

```tsx
// file: app.tsx
import { UserProfileProvider } from "./user-profile-setup";
import { UserProfileEditor } from "./user-profile-editor";
import { UserProfileDisplay } from "./user-profile-display";

export const App = () => (
	<UserProfileProvider setupProps={{ initialName: "John Doe" }}>
		<div>
			<h1>User Management</h1>
			<UserProfileEditor />
			<hr />
			<UserProfileDisplay />
		</div>
	</UserProfileProvider>
);
```

**3. Consume the state**

You can now consume state using the hook, HOC, or `useSignals`.

_Using the `useReactiveState` hook:_

```tsx
// file: user-profile-editor.tsx
import { useUserProfileState } from "./user-profile-setup";

export const UserProfileEditor = () => {
	const { name, email, updateName, setEmail } = useUserProfileState(
		(state) => ({
			name: state.data.name,
			email: state.data.email,
			updateName: state.actions.updateName,
			setEmail: state.actions.setEmail,
		})
	);
	// ... component logic
};
```

_Using `useSignals` for maximum performance:_

```tsx
// file: user-profile-display.tsx
import { useSignals } from "fluidstate-preact/signals";
import { UserProfileContext } from "./user-profile-setup";

export const UserProfileDisplay = () => {
	// Pass the context to `useSignals` to connect to the provider state.
	const get = useSignals(UserProfileContext);
	return (
		<div>
			<h2>Current Profile</h2>
			<p>
				<strong>Name:</strong> {get((state) => state.data.name)}
			</p>
			<p>
				<strong>Email:</strong> {get((state) => state.data.email ?? "Not set")}
			</p>
		</div>
	);
};
```

## API Reference

### `useReactive<T>(getState: () => T, dependencyArray?: ReadonlyArray<unknown>): T`

A Preact hook that subscribes a component to reactive state changes, causing a re-render when dependencies change.

### `withReactive<P>(Component: FunctionComponent<P>): FunctionComponent<P>`

A HOC that makes a functional component reactive. It will re-render whenever any reactive state it accesses changes.

### `useSignals<CV>(Context?: Context<CV>): (fn: (value: CV) => T) => Signal<T>`

A hook that returns a function to create Preact signals from `fluidstate` data.

- `Context`: Optional. A Preact Context object (like one from `createReactiveSetup`) to source the state from. If omitted, it works with the state passed from the component.
- The returned function `get(fn)` takes a selector and returns a memoized `@preact/signal` signal for the selected value.

### `createReactiveSetup<SetupProps, State>(createState)`

A factory that creates a set of utilities for encapsulated state management.

It returns a `ReactiveSetup` object with:

- `ReactiveProvider`: A Provider component that accepts `setupProps`.
- `useReactiveState`: A hook to consume the state.
- `withReactiveState`: A HOC to inject the state via a `state` prop.
- `StateContext`: The Preact Context object used by the Provider. Pass this to `useSignals` to connect to the provided state.
- `MockProvider`: A provider for testing that accepts a `value` prop with mock state.
- `createState`: The original `createState` function, which can be useful for testing the state logic itself.

## Package Entry Points

To keep the library modular and dependencies optional, features are exposed via different entry points:

- `fluidstate-preact`: Core Preact bindings.

  - `useReactive`
  - `withReactive`
  - `createReactiveSetup`

- `fluidstate-preact/signals`: The `useSignals` hook that uses `@preact/signals`.

  - `useSignals`

- `fluidstate-preact/reactive-layer`: The standalone reactive layer backed by `@preact/signals-core`.
  - `getReactiveLayer`
