Skip to content

pontjs/zustand-oop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zustand-oop

Zustand OOP Logo

Version Downloads

Zustand OOP is a library that allows you to use Object-Oriented Programming (OOP) with Zustand. It combines the simplicity and power of Zustand with the structure and familiarity of class-based programming.

Installation

npm install zustand-oop
# or
yarn add zustand-oop

Why zustand-oop?

  • Combines the simplicity of Zustand with the structure of OOP
  • Allows for easy organization of complex state logic
  • Familiar syntax for developers coming from class-based backgrounds
  • Maintains the performance benefits of Zustand

Basic Usage

First, create a store class:

import { immutable, create } from "zustand-oop";

@immutable
class BearState {
  bears = 0;

  increasePopulation() {
    this.bears++;
  }

  removeAllBears() {
    this.bears = 0;
  }

  get hasBears() {
    return this.bears > 0;
  }
}

export const BearStore = create(new BearState());

Then use it in your components:

function BearCounter() {
  const state = BearStore.useState();
  return <h1>{state.bears} around here ...</h1>;
}

function Controls() {
  const actions = BearStore.useActions();
  return <button onClick={actions.increasePopulation}>one up</button>;
}

Advanced Usage

Combined Store

Zustand OOP allows you to create complex stores by combining multiple state classes. Here's an example using a Todos application:

First, we define the TodoItem class to represent individual todo items:

@immutable
export class TodoItem {
  id = uuidv4();
  title = "";
  detail = "";
  completed = false;
  deadline = moment().add(7, "days").format("YYYY-MM-DD HH:mm:ss");

  get remainingTime() {
    return moment(this.deadline).diff(moment(), "days");
  }

  setCompleted(value: boolean) {
    this.completed = value;
  }

  updateTodoTitle(title: string) {
    this.title = title;
  }

  updateTodoDeadline(deadline: string) {
    this.deadline = deadline;
  }
}

Now, we create the TodosState class to manage the collection of todos:

import { Type, immutable } from "zustand-oop";

@immutable
class TodosState {
  // @Type is used for deserialization from persisted state
  // if not using persist middleware, you don't need to use @Type for deserialization
  @Type(() => TodoItem)
  todos = [] as TodoItem[];

  keyword = "";

  setKeywords(value: string) {
    this.keyword = value;
  }

  completeAll() {
    this.todos.forEach((todo) => {
      todo.setCompleted(true);
    });
  }

  addTodo(title: string) {
    const todo = new TodoItem();
    todo.title = title;
    this.todos.push(todo);
  }

  deleteTodo(id: string) {
    this.todos = this.todos.filter((todo) => todo.id !== id);
  }

  get filteredTodos() {
    return this.todos.filter((todo) =>
      todo.title.toLowerCase().includes(this.keyword.toLowerCase())
    );
  }
}

Finally, we create and export the TodosStore using zustand-oop:

export const TodosStore = create(
  devtools(
    persist(() => new TodosState(), {
      name: "todos",
      deserializeClass: TodosState,
    }),
    {
      name: "todos",
    }
  )
);

Here are some examples of how to use zustand-oop store in your components:

class TodoItemComponentProps {
  todo: TodoItem;
  onDelete?: (id: string) => void;
  className?: string;
}

function TodoItemComponent(props: TodoItemComponentProps) {
  const { todo, onDelete } = props;
  const itemActions = TodosStore.useActions((state) =>
    state.todos?.find((todo) => todo.id === props.todo.id)
  );

  return (
    <div>
      <div className='todo-content'>
        <Checkbox
          checked={todo.completed}
          onChange={(checked) => {
            itemActions.setCompleted(checked);
          }}
        />
        <Input
          value={todo.title}
          className='ml-2'
          onChange={itemActions.updateTodoTitle}
        />
      </div>
      <div>
        <DatePicker
          value={todo.deadline}
          format={"YYYY-MM-DD HH"}
          showTime
          onChange={itemActions.updateTodoDeadline}
        ></DatePicker>
      </div>
      <div className='separator'></div>
      <div className='ops'>
        <Button
          onClick={() => {
            onDelete(todo.id);
          }}
        >
          delete
        </Button>
      </div>
    </div>
  );
}

function Todos() {
  const [store, actions] = TodosStore.useStore();
  const [createTodoVisible, setCreateTodoVisible] = React.useState(false);

  return (
    <div>
      <div className='headers'>
        <div className='flex items-center'>
          search todo:
          <Input
            value={store.keyword}
            placeholder='input todo keyword please'
            className='w-[200px] ml-2'
            onChange={(value) => {
              actions.setKeywords(value);
            }}
          />
        </div>
        <div className='buttons'>
          <Button
            onClick={() => {
              setCreateTodoVisible(true);
            }}
          >
            create todo
          </Button>
          <Button
            className='ml-2'
            onClick={() => {
              actions.completeAll();
            }}
          >
            complete all
          </Button>
        </div>
      </div>
      <div className='list'>
        {store.filteredTodos.map((todo, index) => {
          return (
            <TodoItemComponent
              onDelete={actions.deleteTodo}
              className='mt-2'
              key={todo.id}
              todo={todo}
            />
          );
        })}
      </div>
    </div>
  );
}

DevTools

Zustand OOP works seamlessly with Redux DevTools, allowing you to inspect and debug your state changes. Here are some examples of how it looks in action:

devtools:

todos app:

Computed Properties

Zustand OOP supports computed properties using getter methods:

@immutable
class AdvancedBearState {
  bears = 0;
  fish = 0;

  get bearsPerFish() {
    return this.fish === 0 ? 0 : this.bears / this.fish;
  }

  addBear() {
    this.bears++;
  }

  addFish() {
    this.fish++;
  }
}

export const AdvancedBearStore = create(new AdvancedBearState());

SWR

Declarative requests are truly convenient, and zustand-oop also supports SWR. Here's an example of how to use it:

import { createSWRAction } from 'zustand-oop';

class PetStore {
  status = ['available', 'pending', 'sold'];

  setStatus(status: string[]) {
    this.status = status;
  }

  pets = createSWRAction((status) => {
    return useSWR(`https://petstore.swagger.io/v2/pet/findByStatus?status=${status.join(',')}`)
  });
}

export const PetStore = create(() => new PetStore());

function PetList() {
  const [petstore, actions] = PetStore.useStore();

  actions.pets.useRequest(petstore.status);

  return <div>
    <div>
      <Checkbox.Group
        checked={petstore.status}
        onChange={(newStatus) => {
          actions.setStatus(newStatus);
        }}
      />
    </div>
    <Spin loading={petstore.pets.loading}>
      {(petstore.pets.data || []).map((pet) => {
        return <div key={pet.id}>{pet.name}</div>
      })}
    </Spin>
  </div>
}

Middleware Support

Zustand OOP supports Zustand middleware. Here's an example using the persist middleware:

import { persist, devtools, immutable, create } from "zustand-oop";

@immutable
class PersistentBearState {
  bears = 0;

  addBear() {
    this.bears++;
  }
}

export const PersistentBearStore = create(
  devtools(
    persist(new PersistentBearState(), {
      name: "bear-storage",
      deserializeClass: PersistentBearState,
    }),
    { name: "bear-storage" }
  )
);

TypeScript Support

Zustand OOP is written in TypeScript and provides full type support out of the box.

Best Practices

  • Organize your stores into logical classes
  • Use computed properties for derived state
  • Leverage TypeScript for better type safety and autocompletion

Contributing

We welcome contributions!

License

Zustand OOP is MIT licensed.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published