Community-maintained. wavedash-react is an unofficial wrapper around @wvdsh/sdk-js, built by Adrien Guéret. It is not produced or supported by the Wavedash team — for issues with the library itself, open a ticket on the GitHub repo. The underlying SDK is still exposed directly, so any feature not yet wrapped is reachable through useWavedash().
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 or the TypeScript guide.
Install
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.
You must render WavedashProvider. The provider is what calls Wavedash.init() — without it, your game stays hidden behind the Wavedash loading screen.
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 or useMusic.
<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).
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 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.
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.
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.
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.
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.
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.
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, defaulttrue) — 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.
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:
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.
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.
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).
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.
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?)— passstoreNow: trueto flush immediately instead of batching.
Achievements
useAchievement(name) returns the unlocked state and an unlock function.
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?)— passstoreNow: trueto flush immediately.
TypeScript
The package ships its own type definitions and works from both TypeScript and plain JavaScript projects with no extra setup.