Skip to main content

Use scoped store

This tutorial will guide you on how to implement a scoped store. In this tutorial, we'll create a simple Counter component, which contains two sub components: Label and Updater. The Label component displays the current count, while the Updater component provides functionality to update the count.

Setup

Add Houp in you project.

npm install houp

Create useCounter hook

useCounter.ts
import { useState } from "react";

export default function useCounter() {
const [count, setCount] = useState(0);

return {
count,
setCount,
};
}

Create a Provider and add it to your app

provider.ts
import useCounter from "./useCounter";
import { createProvider } from "houp";

export const Provider = createProvider([useCounter]);
index.tsx
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import App from "./App"
import { Provider } from "./provider";

createRoot(document.getElementById("root")!).render(
<StrictMode>
<Provider>
<App />
</Provider>
</StrictMode>,
)

Create the Counter component

The Updater component has two buttons: increase and decrease. The increase button increases the count by 1, and the decrease button decreases the count by 1.

counter.tsx
import { useStore } from "houp";
import useCounter from "./useCounter";

function Label() {
const counter = useStore(useCounter);

return (
<>
<div>{counter.count}</div>
</>
);
}

function Updater() {
const counter = useStore(useCounter);

return (
<>
<button onClick={() => counter.setCount(n => n + 1)}>increase</button>
<button onClick={() => counter.setCount(n => n - 1)}>decrease</button>
</>
);
}

export default function Counter() {
return (
<>
<Label />
<Updater />
</>
);
}
0

The number will be changed when we click the buttons, and it works as expected. Currently, we have added only one Counter component. What happens if we add multiple Counter components? Let's add two Counter components and see what happens when we click the buttons.

0
0
info

You've probably noticed the problem: every time you click the button to change the number, the number in the other Counter component also changes.

This happens because we are using useStore(useCounter) to manage state. What this does is find the nearest StoreProvider in the component tree that contains the specified hook and returns the hook's state from that provider. Currently, the nearest StoreProvider is the root provider, meaning the state is shared across the entire application. However, this isn't what we want. We want the state of each Counter component to be independent of the others.

Add a provider to create a scoped store

Now, let's modify the Counter component to use the useCount store as a scoped store.

counter.tsx
import { useStore } from "houp";
import { createProvider, useStore } from "houp";
import { useCounter } from "./useCounter";

const Provider = createProvider([useCounter]);

function Label() {
const counter = useStore(useCounter);

return (
<>
<div>{counter.count}</div>
</>
);
}

function Updater() {
const counter = useStore(useCounter);

return (
<>
<button onClick={() => counter.setCount(n => n + 1)}>increase</button>
<button onClick={() => counter.setCount(n => n - 1)}>decrease</button>
</>
);
}

export default function Counter() {
return (
<>
<Provider>
<Label />
<Updater />
</>
</Provider>
);
}
info

createProvider function creates a StoreProvider component with an array of hooks as its parameter, which will be used in the Counter component. We use the StoreProvider component as the root of the Counter component. Now, click the button to see if the two Counter components still affect each other.

0
0

Full Example

Here's the complete example, running on CodeSandbox.