# Getting Started

ensforge React turns SDK actions into Effect Atom hooks with shared caching, Suspense, invalidation,
and typed mutation state.

Ensforge supports viem `2.49.0` and newer within version 2.

## Install

Install the React package with its peer dependencies.

:::code-group
```sh [pnpm]
pnpm add @ensforge/react react scheduler effect@^4 viem
```

```sh [npm]
npm install @ensforge/react react scheduler effect@^4 viem
```

```sh [yarn]
yarn add @ensforge/react react scheduler effect@^4 viem
```

```sh [bun]
bun add @ensforge/react react scheduler effect@^4 viem
```
:::

## Add the provider

Create a viem client and place `EnsforgeProvider` once near the root of your client application. The
provider accepts SDK configuration directly.

```tsx [providers.tsx]
"use client";

import type { ReactNode } from "react";

import { EnsforgeProvider } from "@ensforge/react";
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http(process.env.NEXT_PUBLIC_MAINNET_RPC_URL),
});

export function Providers({ children }: { children: ReactNode }) {
  return (
    <EnsforgeProvider config={{ network: "mainnet", publicClient }}>{children}</EnsforgeProvider>
  );
}
```

The provider creates one immutable SDK and one Effect Atom registry. If application code already
shares an `Ensforge` instance, pass `sdk={sdk}` instead of `config`.

## Read a name

Read hooks start below the provider and share work when their action parameters and atom options are
equal.

```tsx [profile.tsx]
import { useAvatar, useOwner } from "@ensforge/react";

export function Profile({ name }: { name: string }) {
  const owner = useOwner({ name });
  const avatar = useAvatar({ name });

  if (owner.isInitial || avatar.isInitial) return <p>Loading…</p>;
  if (owner.isFailure) return <p>{owner.error.message}</p>;
  if (avatar.isFailure) return <p>{avatar.error.message}</p>;

  return (
    <article>
      {avatar.data?.uri ? <img alt="" src={avatar.data.uri} /> : null}
      <p>{owner.data.owner ?? "Unowned"}</p>
    </article>
  );
}
```

Use `enabled` for conditional reads and `map` to expose only the value a component needs. Configure
caching, retries, and refresh behavior through the nested `atom` options.

## Add Wagmi

When the application already uses Wagmi, pass its config to both providers. ensforge supports Wagmi
2.19 and 3.x.

```sh
pnpm add wagmi
```

:::code-group
```tsx [providers.tsx]
"use client";

import type { ReactNode } from "react";

import { EnsforgeProvider } from "@ensforge/react";
import { WagmiProvider } from "wagmi";
import { wagmiConfig } from "./wagmi";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <WagmiProvider config={wagmiConfig}>
      <EnsforgeProvider config={{ network: "mainnet", wagmiConfig }}>{children}</EnsforgeProvider>
    </WagmiProvider>
  );
}
```

```ts [wagmi.ts]
// [!include ~/snippets/wagmi/config.ts]
```
:::

Reads use the configured public client. Mutations resolve the active account and wallet when they
run, so reconnecting does not require recreating the ensforge provider.

## Write a record

Mutation hooks stay idle until `mutate`, `mutateAsync`, or `mutateEffect` is called.

```tsx [website-form.tsx]
import { useSetText } from "@ensforge/react";

export function WebsiteForm({ name }: { name: string }) {
  const update = useSetText({
    onExit: (exit) => console.log("Record update finished", exit),
  });

  return (
    <button
      disabled={update.isWaiting}
      onClick={() => update.mutate({ name, key: "url", value: "https://example.com" })}
    >
      {update.isWaiting ? "Updating…" : "Update website"}
    </button>
  );
}
```

Use `mutate` with Effect `Exit` callbacks, `mutateAsync` in Promise control flow, or
`mutateEffect` while composing Effect programs.

## Use Effect

Query and mutation controls expose Effect-native operations without bypassing hook state.

```tsx [refresh-owner.tsx]
import { Effect } from "effect";
import { useOwner } from "@ensforge/react";

export function RefreshOwner({ name }: { name: string }) {
  const owner = useOwner({ name });

  const refresh = () =>
    Effect.runPromise(
      owner.refreshEffect().pipe(
        Effect.retry({ times: 2 }),
        Effect.tap((result) => Effect.log(`owner: ${result.owner}`)),
      ),
    );

  return <button onClick={refresh}>Refresh owner</button>;
}
```

Import atom factories from `@ensforge/react/atoms` when derived state should be composed directly
at the Effect Atom layer.

## Next steps

* Configure request behavior with [Atom Options](/react/api/atom-options).
* Learn [Caching and Invalidation](/react/guides/caching).
* Add loading boundaries with [Suspense](/react/guides/suspense).
* Compose atoms directly with [Effect Atom](/react/guides/effect-atom).
* Browse all [React hooks](/react/api/hooks/name/use-owner).
