# React

Use the community-maintained wavedash-react wrapper to integrate Wavedash into a React game with hooks and components.

Source: https://docs.wavedash.com/engines/react

<GithubLink href="https://github.com/adrien-gueret/wavedash-react" label="View wavedash-react on GitHub" />

<Note>
**Community-maintained.** `wavedash-react` is an unofficial wrapper around `@wvdsh/sdk-js`, built by [Adrien Guéret](https://github.com/adrien-gueret). It is not produced or supported by the Wavedash team — for issues with the library itself, open a ticket on [the GitHub repo](https://github.com/adrien-gueret/wavedash-react). The underlying SDK is still exposed directly, so any feature not yet wrapped is reachable through `useWavedash()`.
</Note>

[`wavedash-react`](https://github.com/adrien-gueret/wavedash-react) wraps the Wavedash JavaScript SDK in React idioms: a context provider, typed hooks for users, leaderboards, stats, achievements, and audio, plus a `UserAvatar` component. For everything else, see the [JavaScript guide](/engines/javascript) or the [TypeScript guide](/engines/typescript).

## Install

```bash
npm install wavedash-react
```

The package ships its own TypeScript definitions and re-exports the underlying `@wvdsh/sdk-js` SDK through `useWavedash()`.

## Setup

Wrap your app with `WavedashProvider`. The provider mounts the SDK, calls `init()` once preload completes, and makes hooks available to descendants.

<Urgent>
**You must render `WavedashProvider`.** The provider is what calls `Wavedash.init()` — without it, your game stays hidden behind the Wavedash loading screen.
</Urgent>

```tsx
import { WavedashProvider } from "wavedash-react";

export function App() {
  return (
    <WavedashProvider config={{ debug: true }}>
      <YourGame />
    </WavedashProvider>
  );
}
```

### Preloading assets

Pass a `preload` prop to register assets the provider should load before rendering children. Audio assets are a `Record` so each track has a stable id you can reference later with [`useSound`](#usesound) or [`useMusic`](#usemusic).

```tsx
<WavedashProvider
  preload={{
    audio: {
      click: "./audio/click.mp3",
      title: ["./audio/title.ogg", "./audio/title.mp3"],
      battle: ["./audio/battle.ogg", "./audio/battle.mp3"],
    },
    images: ["./images/logo.png"],
    videos: ["./videos/intro.webm"],
  }}
>
  <YourGame />
</WavedashProvider>
```

For audio arrays, the browser picks the first supported source — the same behavior as a regular `<audio>` element with multiple `<source>` children.

## Core hooks

### useWavedash

Returns the SDK singleton plus a flag indicating whether the page is actually inside the Wavedash shell. Use the flag to fall back gracefully when running outside Wavedash (e.g. on itch.io).

```tsx
import { useWavedash } from "wavedash-react";

export function GameComponent() {
  const { isRunningInWavedash, wavedash } = useWavedash();

  if (!isRunningInWavedash) {
    return <p>Not running in Wavedash</p>;
  }

  return <p>Running in Wavedash!</p>;
}
```

`wavedash` exposes the full [Wavedash SDK](/sdk/functions) directly. Anything the React wrapper doesn't cover yet is still reachable through this object.

### useCurrentUser

Returns the current player, or `null` when running outside Wavedash.

```tsx
import { useCurrentUser } from "wavedash-react";

export function UserProfile() {
  const user = useCurrentUser();

  return user ? <p>Welcome, {user.username}!</p> : <p>Not logged in</p>;
}
```

### UserAvatar

Renders any player's avatar. Pass `userId` to target a specific player; omit it for the current user.

```tsx
import { UserAvatar } from "wavedash-react";

export function MyAvatar() {
  return <UserAvatar size={64} />;
}

export function OtherPlayerAvatar() {
  return <UserAvatar userId="specific_userid_here" size={64} />;
}
```

## Leaderboards

### useLeaderboardEntries

Fetch a paginated range of entries.

```tsx
import { useLeaderboardEntries } from "wavedash-react";

export function TopPlayers() {
  const { isLoading, entries } = useLeaderboardEntries("main-leaderboard", {
    start: 0,
    count: 10,
  });

  if (isLoading) return <p>Loading...</p>;

  return (
    <ul>
      {entries.map((entry) => (
        <li key={entry.userId}>
          {entry.username}: {entry.score} pts
        </li>
      ))}
    </ul>
  );
}
```

### useLeaderboardCurrentUserEntries

Fetch the current user's entries on a leaderboard.

```tsx
import { useLeaderboardCurrentUserEntries } from "wavedash-react";

export function MyStats() {
  const { isLoading, entries } =
    useLeaderboardCurrentUserEntries("main-leaderboard");

  return (
    <div>{entries.length > 0 && <p>Your rank: {entries[0].globalRank}</p>}</div>
  );
}
```

### useLeaderboardEntriesAroundCurrentUser

Fetch the entries immediately above and below the current user.

```tsx
import { useLeaderboardEntriesAroundCurrentUser } from "wavedash-react";

export function NearbyPlayers() {
  const { isLoading, entries } = useLeaderboardEntriesAroundCurrentUser(
    "main-leaderboard",
    { countAhead: 5, countBehind: 5 },
  );

  return <div>{entries.length} players nearby</div>;
}
```

### useLeaderboard

Returns a `submitScore` function for the named leaderboard.

```tsx
import { useLeaderboard } from "wavedash-react";

export function GameOver({ finalScore }: { finalScore: number }) {
  const { submitScore } = useLeaderboard("main-leaderboard");

  const handleSubmitScore = async () => {
    const entry = await submitScore(finalScore);
    const globalRank = entry?.globalRank ?? null;

    if (globalRank !== null) {
      alert(`Your rank: #${globalRank}`);
    }
  };

  return <button onClick={handleSubmitScore}>Submit Score</button>;
}
```

`submitScore(score, keepBest?, ugcId?)`:

- `score` (required) — the value to submit.
- `keepBest` (optional, default `true`) — whether the score should replace the existing entry only when it's better.
- `ugcId` (optional) — UGC item to attach for context.

Returns the created or updated entry, or `null` if the submission failed.

## Audio

The provider's `preload.audio` map seeds an internal audio bank. Sound effects and music are **disabled by default** — call `toggleSounds()` / `toggleMusic()` from `useAudio` before playing anything.

### useAudio

Shared audio state and controls. Use this to expose volume sliders, mute buttons, or to enable audio on first load.

```tsx
import { useAudio } from "wavedash-react";

export function AudioSettings() {
  const {
    areSoundsEnabled,
    isMusicEnabled,
    isAudioEnabled,
    toggleSounds,
    toggleMusic,
    playSound,
    soundsVolume,
    musicVolume,
    setSoundsVolume,
    setMusicVolume,
  } = useAudio();

  return (
    <div>
      <button onClick={() => toggleSounds()}>
        Sound effects: {areSoundsEnabled ? "on" : "off"}
      </button>
      <button onClick={() => toggleMusic()}>
        Music: {isMusicEnabled ? "on" : "off"}
      </button>
      <button onClick={() => playSound("click")}>Play click</button>
      <p>Any audio enabled: {isAudioEnabled() ? "yes" : "no"}</p>
      <button onClick={() => setSoundsVolume(0.5)}>
        SFX volume: {soundsVolume}
      </button>
      <button onClick={() => setMusicVolume(0.5)}>
        Music volume: {musicVolume}
      </button>
    </div>
  );
}
```

Available members: `areSoundsEnabled`, `isMusicEnabled`, `isAudioEnabled()`, `soundsVolume`, `musicVolume`, `toggleSounds(force?)`, `toggleMusic(force?)`, `toggleAudio(force?)`, `playSound(audioId, loop?)`, `stopSound(audioId)`, `playMusic(musicId)`, `pauseMusic()`, `resumeMusic()`, `setSoundsVolume(value)`, `setMusicVolume(value)`.

To enable audio immediately on app start:

```tsx
import { useEffect } from "react";
import { useAudio } from "wavedash-react";

export function EnableAudioOnStart() {
  const { toggleSounds, toggleMusic } = useAudio();

  useEffect(() => {
    toggleSounds(true);
    toggleMusic(true);
  }, [toggleSounds, toggleMusic]);

  return null;
}
```

When music is disabled mid-track the current music pauses; if the pause was caused by the toggle, the track resumes automatically when music is re-enabled.

### useSound

Controls a single preloaded sound effect.

```tsx
import { useSound } from "wavedash-react";

export function ShootButton() {
  const { playSound, stopSound } = useSound("click");

  return (
    <div>
      <button onClick={() => playSound()}>Play once</button>
      <button onClick={() => playSound(true)}>Loop</button>
      <button onClick={stopSound}>Stop</button>
    </div>
  );
}
```

### useSounds

Like `useSound`, but not bound to a single id — pass the sound to play at call time.

```tsx
import { useSounds } from "wavedash-react";

export function GenericSfxButtons() {
  const { playSound, stopSound } = useSounds();

  return (
    <div>
      <button onClick={() => playSound("click")}>Play click</button>
      <button onClick={() => playSound("explosion", true)}>Loop explosion</button>
      <button onClick={() => stopSound("explosion")}>Stop explosion</button>
    </div>
  );
}
```

### useMusic

Controls the shared music player. Only one music track plays at a time — starting a new one stops the previous track, and calling `playMusic()` with the track already playing is a no-op (the track is not restarted from zero).

```tsx
import { useMusic } from "wavedash-react";

export function MusicControls() {
  const { playMusic, pauseMusic, resumeMusic } = useMusic();

  return (
    <div>
      <button onClick={() => playMusic("title")}>Play title music</button>
      <button onClick={() => playMusic("battle")}>Play battle music</button>
      <button onClick={pauseMusic}>Pause</button>
      <button onClick={resumeMusic}>Resume</button>
    </div>
  );
}
```

## Stats

`useStat(name)` returns the current stat value and a setter.

```tsx
import { useStat } from "wavedash-react";

export function StatsDisplay() {
  const [stat, setStat] = useStat("player-kills");
  const kills = stat ?? 0;

  return (
    <div>
      <p>Kills: {kills}</p>
      <button onClick={() => setStat(kills + 1)}>Increment</button>
    </div>
  );
}
```

The tuple is:

- current value as `number | null` (null while unloaded or when running outside Wavedash).
- `setStat(newValue, storeNow?)` — pass `storeNow: true` to flush immediately instead of batching.

## Achievements

`useAchievement(name)` returns the unlocked state and an unlock function.

```tsx
import { useAchievement } from "wavedash-react";

export function AchievementHandler() {
  const [isUnlocked, unlockAchievement] = useAchievement("first-kill");

  return (
    <div>
      <p>Status: {isUnlocked ? "Unlocked" : "Locked"}</p>
      <button onClick={() => unlockAchievement()}>Unlock</button>
    </div>
  );
}
```

The tuple is:

- current state as `boolean | null` (null while unloaded or outside Wavedash).
- `unlockAchievement(storeNow?)` — pass `storeNow: true` to flush immediately.

## TypeScript

The package ships its own type definitions and works from both TypeScript and plain JavaScript projects with no extra setup.
