Skip to content

Instantly share code, notes, and snippets.

@hungdev
Created December 12, 2024 13:15
Show Gist options
  • Save hungdev/f195a543f558a7650f3b26208931b8b7 to your computer and use it in GitHub Desktop.
Save hungdev/f195a543f558a7650f3b26208931b8b7 to your computer and use it in GitHub Desktop.
zustand simple

// Simple version of Zustand

zustand.js

import React from "react";
// Function to create store with core features
function createStore(createState) {
  // Store the current state
  let state;

  // List of listener functions to update when state changes
  const listeners = new Set();

  // Function to register listeners
  const subscribe = (listener) => {
    listeners.add(listener);

    // Return the unsubscribe function
    return () => listeners.delete(listener);
  };

  // Function to update the state
  const setState = (partial) => {
    // Calculate the new state
    const nextState = typeof partial === "function" ? partial(state) : partial;

    // Check if the state actually changes
    if (nextState !== state) {
      state = { ...state, ...nextState };

      // Notify all listeners
      listeners.forEach((listener) => listener(state));
    }
  };

  // Function to get the current state
  const getState = () => state;

  // Initialize the initial state
  state = createState(setState, getState);

  // Return methods to work with the store
  return {
    getState,
    setState,
    subscribe,
  };
}

// Function to create a hook for use in React
function createHook(store) {
  return function useStore(selector) {
    // React hook to manage state
    const [state, setState] = React.useState(
      selector ? selector(store.getState()) : store.getState()
    );

    // Effect to register and unsubscribe listener
    React.useEffect(() => {
      const handleChange = (newState) => {
        const selectedState = selector ? selector(newState) : newState;

        // Only update if state changes
        setState(selectedState);
      };

      // Register listener
      const unsubscribe = store.subscribe(handleChange);

      // Unsubscribe when component unmounts
      return unsubscribe;
    }, [selector]);

    return state;
  };
}

// Main function to create store easily
function create(createState) {
  // Create store
  const store = createStore(createState);

  // Create hook to use the store
  const useStore = createHook(store);

  // Return hook and some additional methods
  return Object.assign(useStore, {
    getState: store.getState,
    setState: store.setState,
    subscribe: store.subscribe,
  });
}

export { create, createHook, createStore };

// Explanation of how it works:
// 1. createStore: Creates a basic state management mechanism
//    - Stores state
//    - Allows registering listeners
//    - Provides method to update state
// 2. createHook: Creates a React hook to use the store
//    - Manages local state
//    - Registers listener to track changes
//    - Only re-renders when state changes
// 3. create: Main function to create store easily
//    - Combines createStore and createHook
//    - Provides a simple API to create and use store

// Key principles:
// - Immutability: Always create a new state instead of mutating
// - Minimal API: Only provide necessary methods
// - Performance: Only render when state actually changes

// Main idea of the library:
// Zustand solves state management issues in React by:
// - Creating a central store to store state
// - Providing a mechanism to register and track changes
// - Allowing easy and efficient state updates
// - Minimizing prop-drilling through multiple levels of components

App.js

import React from "react";
import { create } from "./zustand";

// Using the create function defined in the previous code
// Create a store to manage user state
const useUserStore = create((set) => ({
  // Initial state
  user: null,
  isLoggedIn: false,
  theme: "light",

  // Actions to modify the state
  login: (userData) =>
    set({
      user: userData,
      isLoggedIn: true,
    }),

  logout: () =>
    set({
      user: null,
      isLoggedIn: false,
    }),

  toggleTheme: () =>
    set((state) => ({
      theme: state.theme === "light" ? "dark" : "light",
    })),
}));

// Profile component using the store
const Profile = () => {
  // Access state and actions from the store
  const { user, isLoggedIn, login, logout } = useUserStore();

  return (
    <div className={`p-6 ${isLoggedIn ? "bg-green-100" : "bg-red-100"}`}>
      {isLoggedIn ? (
        <div>
          <h2 className="text-2xl">Hello, {user.name}</h2>
          <p>Email: {user.email}</p>
          <button
            onClick={() => logout()}
            className="mt-4 bg-red-500 text-white p-2 rounded"
          >
            Logout
          </button>
        </div>
      ) : (
        <button
          onClick={() =>
            login({
              name: "Nguyễn Văn A",
              email: "[email protected]",
            })
          }
          className="bg-blue-500 text-white p-2 rounded"
        >
          Login
        </button>
      )}
    </div>
  );
};

// Main component
const UserManagementApp = () => {
  return (
    <div className="max-w-md mx-auto mt-10 space-y-4">
      <h1 className="text-3xl font-bold text-center">User Management</h1>
      <Profile />
      <StateLogger /> {/* Component to display the entire state */}
    </div>
  );
};

// Component to log the entire state (for demonstration)
const StateLogger = () => {
  // Get the entire state
  const state = useUserStore();

  return (
    <div className="bg-gray-100 p-4 rounded">
      <h3 className="font-bold mb-2">Current State:</h3>
      <pre>{JSON.stringify(state, null, 2)}</pre>
    </div>
  );
};

export default UserManagementApp;

// Detailed explanation of the example:
// 1. Create a store with multiple states and actions
// 2. Use a selector to get a part of the state
// 3. Different components can access and modify the state
// 4. No need to pass props or use Context
// 5. Easy to extend and manage global state

https://codesandbox.io/p/sandbox/zustand-simple-6pdqrx?file=%2Fsrc%2FApp.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment