0.0.6 • Published 4 months ago

keydux v0.0.6

Weekly downloads
-
License
-
Repository
-
Last release
4 months ago

keydux

Keydux is a lightweight shared state management library for React that offers a streamlined alternative to Redux and other state management libraries.

Keydux provides hooks for a minimalistic API for reading, updating, and watching state changes across your application. Unlike the Context API, updates only rerender the components that hook into each state key, not the entire component tree under the provider. Keydux reduces boilerplate while delivering the performance optimizations, type safety, and flexibility you would get from Redux, other state management libraries, and other handrolled solutions.

Features

  • Simple Shared State: Easily create and use shared state without the complexities of Redux.
  • Hook-Based API: Use simple React hooks (useSharedState) to access and modify your state.
  • Debounced Updates: Prevent excessive re-renders by debouncing state updates.
  • Avoids Context API pitfalls Unlike Context API, developers don't need to constantly search for context providers in their true or worry about global rerenders
  • Persistent Storage: Optionally save state changes to localStorage (or a fallback) with minimal configuration.
  • Type Safety: Fully typed with TypeScript to ensure reliable state management.
  • No Dependencies: keydux has no dependencies other than React (a peer dependency)
  • Lightweight: keydux is extremely lightweight -- 1kb minified and gzipped, 75% smaller than Redux

Installation

You can install keydux via npm or yarn:

npm install keydux
# or
yarn add keydux

Usage

Setting Up the Provider

Wrap your entire application with the SharedStateProvider to enable shared state management:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { SharedStateProvider } from "keydux/src/SharedStateProvider";

ReactDOM.render(
  <SharedStateProvider>
    <App />
  </SharedStateProvider>,
  document.getElementById("root")
);

Creating a Shared State

Use the useSharedState hook to manage a shared piece of state. The hook provides:

  • value: the current value.
  • debouncedValue: the value after applying a debounce delay.
  • setValue: a function to update the state.
  • clear: a helper function to reset the state to its initial value.

Example: A Counter with Storage and Debouncing

import React from "react";
import { useSharedState } from "keydux/src/useSharedState";

const useCounter = () => {
  return useSharedState({
    key: "counter",
    initialValue: 0,
    debounceTime: 1000,
  });
}

function Counter() {
  const { setValue: setCount, value: count, clear } = useCounter()

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => setCount(count => count + 1)}>Increment</button>
      <button onClick={clear}>Reset</button>
      <CounterDisplay />
    </div>
  );
}

function CounterDisplay() {
  const { value: count, debouncedValue } = useCounter()

  return (
    <>
      <div>Counter: {count}</div>
      <div>Debounced Counter: {debouncedValue}</div>
    </>
  );
}

export default Counter;

Adding Type Safety

keydux is written in TypeScript, so you can easily enforce type safety by providing an explicit type for your state values. This helps catch errors at compile time and provides better autocompletion. You can also use the createUseSharedState function to provide type safety for your state key values.

import React from "react";
import { createUseSharedState } from "keydux/src/useSharedState";

enum STATE_KEY {
  USER_PROFILE = "userProfile",
}

type UserProfile = {
  name: string;
  age: number;
};

const useSharedState = createUseSharedState<STATE_KEY>()

function UserProfile() {
  const { value: userProfile, setValue } = useSharedState<UserProfile>({
    key: STATE_KEY.USER_PROFILE,
    initialValue: { name: "John Doe", age: 30 },
  });

  const updateName = () => {
    setValue({ ...userProfile, name: "Jane Doe" });
  };

  return (
    <div>
      <h2>{user.name}</h2>
      <p>Age: {user.age}</p>
      <button onClick={updateName}>Change Name</button>
    </div>
  );
}

export default UserProfile;

Debouncing State Updates

Avoid performance issues through debouncing, which delays the propagation of state changes until the specified time has elapsed. This is especially useful when reacting to fast-changing input, such as text inputs:

import React from "react";
import { useSharedState } from "keydux;



function SearchInput() {
  const {
    value: searchTerm,
    debouncedValue: debouncedSearchTerm,
    setValue: setSearchTerm
  } = useSharedState<string>({
    key: "searchTerm",
    initialValue: "",
    debounceTime: 500, // wait 500ms after the last change to update debouncedValue
  });

  useEffect(() => {
    // Fire my API call with the debounced value
  }, [debouncedSearchTerm])

  return (
    <div>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="Type to search..."
      />
    </div>
  );
}

export default SearchInput;

Saving State to Storage

To persist state changes (for example, between page reloads), enable storage support by setting shouldSaveToStorage to true. keydux utilizes a storage adapter that defaults to localStorage (with a fallback if not in the browser):

import React from "react";
import { useSharedState } from "keydux/src/useSharedState";

function TodoList() {
  const { value: todos, setValue, clear } = useSharedState<string[]>({
    key: "todos",
    initialValue: [],
    shouldSaveToStorage: true, // persist todos in storage
  });

  const addTodo = (todo: string) => {
    setValue([...todos, todo]);
  };

  return (
    <div>
      <h2>Todo List</h2>
      <button onClick={() => addTodo("New Todo")}>Add Todo</button>
      <button onClick={clear}>Clear Todos</button>
      <ul>
        {todos.map((todo, idx) => (
          <li key={idx}>{todo}</li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

Updatng state outside of React

You can also update state outside of React by instantiating a StateManager instance and passing it to the SharedStateProvider.

The StateManager instance has read and write methods that can be call directy to access and update state outside of React

import { StateManager } from "keydux";
import { router } from "./router";
import { STATE_KEY } from "./constants";

const stateManager = new StateManager()

router.on("beforeNavigate", () => {
  stateManager.write(STATE_KEY.COUNTER, 0)
})

const MyApp = () => {
  return (
    <SharedStateProvider stateManager={stateManager}>
      <App />
    </SharedStateProvider>
  )
}

Comparison to Redux

While Redux is a powerful state management library, it comes with its own set of complexities and boilerplate. keydux offers a simpler, hook-based approach.

In practice, redux usage often involves basic state management -- ie getters and setters -- complicated by a whole lot of indirection and boilerplate.

In contract, keydux gets to the meat of your usage without all the headaches.

FeaturekeyduxRedux
BoilerplateMinimal setup with a provider and hooksRequires explicit reducers, actions, and middleware
API StyleSimple, hook-driven APIAction dispatch, reducers, and middleware chaining
Debouncing SupportBuilt-in with useDebouncedValueNeeds external libraries or custom implementation
Local PersistenceBuilt-in with shouldSaveToStorageMust be implemented separately
Type SafetyFull TypeScript integration out-of-the-boxAvailable but requires additional configuration
Browser DevtoolsAvailable and easy to installAvailable and easy to install

Conclusion

keydux provides an effortless way to manage shared state in React applications with built-in support for debouncing, localStorage persistence, and type safety. It's ideal for projects looking for a practical, less verbose state management solution compared to Redux.

License

MIT

0.0.6

4 months ago

0.0.5

5 months ago

0.0.4

5 months ago

0.0.3

5 months ago

0.0.2

5 months ago

0.0.1

5 months ago