npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@funtools/store

v1.0.4

Published

Lightweight external store for React, React Native and Next.js

Readme

@funtools/store

A simple and lightweight state management library for React apps

npm version License: MIT

📋 What is @funtools/store?

@funtools/store is an easy-to-use state management library for React. Think of it as a smarter way to share data between your components without the complexity of Redux or other heavy tools.

Perfect for beginners and experienced developers alike!

✨ Why Choose @funtools/store?

  • Super Easy to Learn - Get started in minutes, not hours
  • Automatic Features - Get built-in functions for free (no need to write repetitive code)
  • TypeScript Friendly - Get helpful suggestions as you type
  • Very Small - Won't bloat your app size
  • Fast Performance - Components only update when they need to
  • Works Everywhere - React, React Native, and Next.js

📦 Installation

Choose your favorite package manager:

npm install @funtools/store

🚀 Quick Start - Your First Store

Let's create a simple counter in 3 easy steps:

Step 1: Create Your Store

import { createStore } from "@funtools/store";

// Create a store with initial values
const counterStore = createStore({
    states: {
        count: 0, // Our counter starts at 0
    },
});

Step 2: Use It in a Component

function Counter() {
    // Get the count value
    const count = counterStore.useStore((state) => state.count);

    // Get the handlers (functions to change the state)
    const handlers = counterStore.useHandlers();

    return (
        <div>
            <h1>Count: {count}</h1>
            {/* Set count to a specific number */}
            <button onClick={() => handlers.count.set(10)}>Set to 10</button>
            {/* Increment using current value */}
            <button onClick={() => handlers.count.set((prev) => prev + 1)}>
                Add 1
            </button>
            {/* Reset to initial value (0) */}
            <button onClick={() => handlers.count.reset()}>Reset</button>
        </div>
    );
}

That's it! You have a working counter. 🎉

📖 Core Concepts

1. Creating a Store

A store is where you keep your app's data. It's like a box that holds all your values.

const myStore = createStore({
    states: {
        // Put all your data here
        userName: "John",
        age: 25,
        isLoggedIn: false,
    },
});

2. Reading Data from the Store

Use useStore to read data in your components:

function MyComponent() {
    // Method 1: Get one value
    const userName = myStore.useStore((state) => state.userName);

    // Method 2: Get multiple values
    const { userName, age } = myStore.useStore((state) => ({
        userName: state.userName,
        age: state.age,
    }));

    return (
        <div>
            Hello, {userName}! You are {age} years old.
        </div>
    );
}

3. Changing Data (Using Handlers)

Handlers are functions that change your data. The library creates them automatically!

function MyComponent() {
    const handlers = myStore.useHandlers();

    // Change the userName
    handlers.userName.set("Jane");

    // Reset to initial value
    handlers.userName.reset();
}

🎨 Auto-Generated Handlers

The best part? You get FREE handlers based on your data type!

For Simple Values (String, Number)

const store = createStore({
    states: {
        name: "John",
        age: 25,
    },
});

const handlers = store.useHandlers();

// ✅ Set to a new value
handlers.name.set("Jane");
handlers.age.set(26);

// ✅ Set using current value
handlers.age.set((currentAge) => currentAge + 1);

// ✅ Reset to initial value
handlers.name.reset(); // Back to "John"
handlers.age.reset(); // Back to 25

For Boolean (True/False)

const store = createStore({
    states: {
        isOpen: false,
        isDarkMode: true,
    },
});

const handlers = store.useHandlers();

// ✅ Toggle (switch between true/false)
handlers.isOpen.toggle();

// ✅ Set to specific value
handlers.isDarkMode.set(false);

// ✅ Reset to initial value
handlers.isOpen.reset();

For Arrays (Lists)

const store = createStore({
    states: {
        fruits: ["apple", "banana"],
        numbers: [1, 2, 3],
    },
});

const handlers = store.useHandlers();

// ✅ Add to end
handlers.fruits.push("orange");
// Result: ["apple", "banana", "orange"]

// ✅ Add to beginning
handlers.fruits.unShift("mango");
// Result: ["mango", "apple", "banana", "orange"]

// ✅ Remove from end
handlers.fruits.pop();
// Result: ["mango", "apple", "banana"]

// ✅ Remove from beginning
handlers.fruits.shift();
// Result: ["apple", "banana"]

// ✅ Update item at specific position
handlers.fruits.update(0, "grape");
// Result: ["grape", "banana"]

// ✅ Update item using current value
handlers.numbers.update(1, (current) => current * 2);

// ✅ Remove item at specific position
handlers.fruits.remove(1);
// Result: ["grape"]

// ✅ Set entire array
handlers.fruits.set(["kiwi", "melon"]);

// ✅ Reset to initial value
handlers.fruits.reset();
// Result: ["apple", "banana"]

For Objects

const store = createStore({
    states: {
        user: {
            name: "John",
            email: "[email protected]",
            settings: {
                theme: "light",
                notifications: true,
            },
        },
    },
});

const handlers = store.useHandlers();

// ✅ Update single property
handlers.user.update("name", "Jane");

// ✅ Update with current value
handlers.user.update("name", (currentName) => currentName.toUpperCase());

// ✅ Update nested property (use dot notation)
handlers.user.update("settings.theme", "dark");

// ✅ Update multiple properties at once
handlers.user.updateMany({
    name: "Jane",
    email: "[email protected]",
});

// ✅ Update nested properties
handlers.user.updateMany({
    settings: {
        theme: "dark",
    },
});

// ✅ Set entire object
handlers.user.set({
    name: "Bob",
    email: "[email protected]",
    settings: { theme: "blue", notifications: false },
});

// ✅ Reset to initial value
handlers.user.reset();

🔧 Custom Handlers

Sometimes you need custom logic. Create your own handlers!

Sync Handlers (Instant Changes)

const store = createStore({
    states: {
        count: 0,
        firstName: "John",
        lastName: "Doe",
    },

    // Define your custom handlers here
    syncHandlers: {
        // Handler with no parameters
        increment: (state) => {
            state.count = state.count + 1;
        },

        // Handler with parameters
        incrementBy: (state, amount: number) => {
            state.count = state.count + amount;
        },

        // Handler that changes multiple values
        setFullName: (state, first: string, last: string) => {
            state.firstName = first;
            state.lastName = last;
        },
    },
});

// Use them in components
function MyComponent() {
    const handlers = store.useHandlers();

    return (
        <div>
            <button onClick={() => handlers.increment()}>Add 1</button>
            <button onClick={() => handlers.incrementBy(5)}>Add 5</button>
            <button onClick={() => handlers.setFullName("Jane", "Smith")}>
                Change Name
            </button>
        </div>
    );
}

Async Handlers (For API Calls)

Perfect for fetching data from servers!

const store = createStore({
    states: {
        user: null,
        loading: false,
        error: null,
    },

    asyncHandlers: {
        // Fetch user from API
        fetchUser: async (state, userId: string) => {
            // Set loading to true
            state.loading = true;
            state.error = null;

            try {
                // Fetch from API
                const response = await fetch(
                    `https://api.example.com/users/${userId}`,
                );
                const data = await response.json();

                // Update state with data
                state.user = data;
            } catch (err) {
                // Handle errors
                state.error = "Failed to fetch user";
            } finally {
                // Set loading to false
                state.loading = false;
            }
        },
    },
});

// Use in component
function UserProfile() {
    const { user, loading } = store.useStore((state) => ({
        user: state.user,
        loading: state.loading,
    }));
    const handlers = store.useHandlers();

    return (
        <div>
            <button onClick={() => handlers.fetchUser("123")}>Load User</button>
            {loading && <p>Loading...</p>}
            {user && <p>Name: {user.name}</p>}
        </div>
    );
}

🎁 Using Providers (Scoped Stores)

Sometimes you want a store that only works within a specific part of your app. Use createStoreProvider!

import { createStoreProvider } from "@funtools/store";

// Create a provider
const { Provider, useStore, useHandlers } = createStoreProvider({
    states: {
        theme: "light",
        language: "en",
    },
});

// Wrap part of your app
function App() {
    return (
        <Provider>
            <Header />
            <Content />
        </Provider>
    );
}

// Use in any child component
function Header() {
    const theme = useStore((state) => state.theme);
    const handlers = useHandlers();

    return (
        <button
            onClick={() =>
                handlers.theme.set(theme === "light" ? "dark" : "light")
            }>
            Current theme: {theme}
        </button>
    );
}

The difference:

  • createStore = Global (available everywhere)
  • createStoreProvider = Scoped (only available inside <Provider>)

💡 Performance Tips

Only Re-render When Needed

Components only re-render when the data they use changes:

// ❌ BAD: Component re-renders on ANY state change
const allState = store.useStore((state) => state);

// ✅ GOOD: Component only re-renders when count changes
const count = store.useStore((state) => state.count);

// ✅ GOOD: Component only re-renders when name or age change
const { name, age } = store.useStore((state) => ({
    name: state.name,
    age: state.age,
}));

📚 Real-World Examples

Example 1: Todo App

const todoStore = createStore({
    states: {
        todos: [] as Array<{ id: number; text: string; done: boolean }>,
    },

    syncHandlers: {
        addTodo: (state, text: string) => {
            state.todos.push({
                id: Date.now(),
                text: text,
                done: false,
            });
        },

        toggleTodo: (state, id: number) => {
            const todo = state.todos.find((t) => t.id === id);
            if (todo) {
                todo.done = !todo.done;
            }
        },

        deleteTodo: (state, id: number) => {
            state.todos = state.todos.filter((t) => t.id !== id);
        },
    },
});

function TodoApp() {
    const todos = todoStore.useStore((state) => state.todos);
    const handlers = todoStore.useHandlers();
    const [input, setInput] = React.useState("");

    return (
        <div>
            <input
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="Add a todo..."
            />
            <button
                onClick={() => {
                    handlers.addTodo(input);
                    setInput("");
                }}>
                Add
            </button>

            {todos.map((todo) => (
                <div key={todo.id}>
                    <input
                        type="checkbox"
                        checked={todo.done}
                        onChange={() => handlers.toggleTodo(todo.id)}
                    />
                    <span
                        style={{
                            textDecoration: todo.done ? "line-through" : "none",
                        }}>
                        {todo.text}
                    </span>
                    <button onClick={() => handlers.deleteTodo(todo.id)}>
                        Delete
                    </button>
                </div>
            ))}
        </div>
    );
}

Example 2: Shopping Cart

const cartStore = createStore({
    states: {
        items: [] as Array<{
            id: number;
            name: string;
            price: number;
            quantity: number;
        }>,
        total: 0,
    },

    syncHandlers: {
        addItem: (
            state,
            product: { id: number; name: string; price: number },
        ) => {
            // Check if item already exists
            const existing = state.items.find((item) => item.id === product.id);

            if (existing) {
                // Increase quantity
                existing.quantity++;
            } else {
                // Add new item
                state.items.push({ ...product, quantity: 1 });
            }

            // Update total
            state.total = state.items.reduce(
                (sum, item) => sum + item.price * item.quantity,
                0,
            );
        },

        removeItem: (state, id: number) => {
            state.items = state.items.filter((item) => item.id !== id);
            state.total = state.items.reduce(
                (sum, item) => sum + item.price * item.quantity,
                0,
            );
        },

        clearCart: (state) => {
            state.items = [];
            state.total = 0;
        },
    },
});

Example 3: User Authentication

const authStore = createStore({
    states: {
        user: null as { id: string; name: string; email: string } | null,
        isAuthenticated: false,
        isLoading: false,
    },

    asyncHandlers: {
        login: async (state, email: string, password: string) => {
            state.isLoading = true;

            try {
                const response = await fetch("/api/login", {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({ email, password }),
                });

                const data = await response.json();

                state.user = data.user;
                state.isAuthenticated = true;
            } catch (error) {
                console.error("Login failed:", error);
            } finally {
                state.isLoading = false;
            }
        },

        logout: async (state) => {
            await fetch("/api/logout", { method: "POST" });
            state.user = null;
            state.isAuthenticated = false;
        },
    },
});

🎓 TypeScript Support

The library works great with TypeScript! You get autocomplete and type safety.

Defining State Types

// Define your state shape
type UserState = {
    name: string;
    age: number;
    email: string;
};

const store = createStore({
    states: {
        count: 0,
        user: {
            name: "John",
            age: 25,
            email: "[email protected]",
        } as UserState,
    },

    syncHandlers: {
        // TypeScript knows the state type!
        updateUser: (state, newUser: UserState) => {
            state.user = newUser;
        },
    },
});

// TypeScript will catch errors
const handlers = store.useHandlers();
handlers.updateUser({
    name: "Jane",
    age: 26,
    // ❌ Error: missing 'email' property
});

❓ Common Questions

Q: When should I use a global store vs provider?

Use Global Store (createStore) when:

  • Data is needed across your entire app (like user auth, theme)
  • You want simple setup without wrapping components

Use Provider (createStoreProvider) when:

  • Data is only needed in a specific section
  • You want better component isolation
  • You're building reusable components

Q: How is this different from useState?

useState is great for local component state. Use @funtools/store when:

  • Multiple components need the same data
  • You want to avoid prop drilling
  • You need more powerful update functions

Q: Can I use multiple stores?

Yes! Create as many stores as you need:

const userStore = createStore({ states: { user: null } });
const cartStore = createStore({ states: { items: [] } });
const themeStore = createStore({ states: { theme: "light" } });

🤝 Contributors

This project is open source and welcomes contributions from the community! We appreciate all the developers who have helped make this library better.

How to Contribute

We welcome contributions of all kinds:

  • 🐛 Bug fixes
  • ✨ New features
  • 📝 Documentation improvements
  • 💡 Suggestions and ideas

To contribute:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Our Contributors

Thanks to all the amazing people who have contributed to this project! 🎉

Want to see your name here? Start contributing today!

🔗 Links


Made with ❤️ for developers who value simplicity by @funtools24

Happy coding! 🚀