10.0.5 • Published 2 years ago

react-state-editor v10.0.5

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

react-state-editor

Developer-friendly React state editor

State types limitation

Deprecated object keys

  • $, map$

Create store context

// store.ts
import {
  createStoreContext,
  createUseStorePathfinder,
} from 'react-state-editor';

export type User = {
  id: number;
  name: string;
};

export type MyInState = {
  users: User[];
};
export type MyOutState = {
  users: User[];
  userLabels: string[];
};

export const MyContext = createStoreContext<MyOutState>('MyContext');

export const useMy = createUseStorePathfinder(MyContext);

Get store pathfinder

Short way using predefined hook

import { useMy } from './store';
// ...
const my = useMy();

Alternative way

import { useStorePathfinder } from 'react-state-editor';
import { MyContext } from './store';
// ...
const my = useStorePathfinder(MyContext);

Store provider

// MyStoreProvider.tsx
import React, { ReactNode, useMemo } from 'react';
import { useStore } from 'react-state-editor';
import { MyContext, MyInState, MyOutState } from './store';

export type MyStoreProviderProps = {
  children: ReactNode;
};
export const MyStoreProvider = ({ children }: MyStoreProviderProps) => {
  const store = useStore<MyInState, MyOutState>(
    () => ({
      /* initial state */
      users: [
        { id: 0, name: 'Sméagol' },
        { id: 1, name: 'Gollum' },
      ],
    }),
    useComputeState,
    { defaultCacheLocation: 'store' }
  );
  return <MyContext.Provider value={store}>{children}</MyContext.Provider>;
};

const useComputeState = (s: MyInState): MyOutState => {
  const userLabels = useMemo(
    () => s.users.map((u) => `${s.name} (${s.id})`),
    [s.users]
  );
  return { ...s, userLabels };
};

Demo

// Demo.tsx
export const Demo = () => {
  return (
    <MyStoreProvider>
      <UserList />
      <hr />
      <UserListEdit />
      <hr />
      <UserNames />
      <hr />
      <ValueListDemo />
      <hr />
      <UsersInfo />
    </MyStoreProvider>
  );
};

Custom mutation hooks

Mutations should return input state

// mutations.tsx
import { createUseStoreReduce } from 'react-state-editor';
import { MyContext, MyInState } from './store';

export const useAddUser = createUseStoreReduce(
  MyContext,
  (s, name: string): MyInState => ({
    ...s,
    users: [...s.users, { id: Math.random(), name }],
  })
);

export const useRemoveUser = createUseStoreReduce(
  MyContext,
  (s, id: number): MyInState => ({
    ...s,
    users: s.users.filter((u) => u.id !== id),
  })
);
// UserListEdit.tsx
import { useAddUser, useRemoveUser } from './mutations';

export const UserListEdit = () => {
  const addUser = useAddUser();
  const removeUser = useRemoveUser();
  return (
    <div>
      <button onClick={() => addUser(`A hobbit`)}>Add user</button>
      <button onClick={() => removeUser(0)}>Remove user</button>
    </div>
  );
};

Custom editors

// StringEditor.tsx
import React, { memo, useCallback } from 'react';
import { StateEditorProps, useStoreState } from 'react-state-editor';

export type StringEditorCustomProps = {
  isSomeOption: (value: string) => boolean;
};

// Reusable string editor (independent of concrete store)
// Optionally can wrapped with React.memo
export const StringEditor = memo(function TextEditor({
  $, // Pathfinder object (comes from StateEditorProps<string>)
  isSomeOption,
  ...props
}: StringEditorCustomProps & StateEditorProps<string>) {
  const [state, setState] = useStoreState($); // Use state via pathfinder

  const onChange = useCallback(
    (e: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
      const { value } = e.target;
      if (isSomeOption(value)) {
        setState(value);
      }
    },
    [setState, validate]
  );
  return <input value={state} onChange={onChange} />;
});

Use store state

// UserItem.tsx
import React from 'react';
import { StateEditorProps, useStoreState } from 'react-state-editor';
import { useMy } from './store';
import { StringEditor } from './StringEditor';

export type UserItemProps = { index: number };
export const UserItem = ({ index }: UserItemProps) => {
  const my = useMy(); // Get root pathfinder
  const user$ = my.users[index]; // Get user pathfinder

  const [user, setUser] = useStoreState(user$);
  // Alternative
  // const user = useStoreSelect(user$);
  // const setUser = useStoreSet(user$);

  const reduceUser = useStoreReduce(user$);

  return (
    <div>
      <StringEditor $={user$.name} isSomeOption={(name) => name.length > 20} />
      <button
        onClick={() => reduceUser((u) => ({ ...u, name: u.name + '-x' }))}
      >
        {user.name}
      </button>
    </div>
  );
};

Use array mapping

<array pathfinder>.map$ is special property for mapping item fields

// UserNames.tsx
import React, { useMemo } from 'react';
import { ValueList } from './ValueList';
import { useMy } from './store';

export function UserNames() {
  const my = useMy(); // Get root pathfinder

  // Reacts on names change only (!!!)
  const names = useStoreSelect(my.users.map$.name);
  const joinedNames = useMemo(() => names.join(', '), [names]);

  return <div>User names: {joinedNames}</div>;
}

Use arrays (use unique field as key)

// UserList.tsx
import { ArrayView } from 'react-state-editor';
import { UserItem } from './UserItem';
import { useMy } from './store';

export const UserList = () => {
  const my = useMy(); // Get root pathfinder
  return <ArrayView $={my.users.map$.id}>{usersItem}</ArrayView>;
};
const usersItem = (id: string, i: number) => <UserItem key={id} index={i} />;

Use arrays (use index as key)

WARNING: Usage of unique field as key is preferable See Keys

// UserList.tsx
import { ArrayViewIndexKey } from 'react-state-editor';
import { UserItem } from './UserItem';
import { useMy } from './store';

export const UserList = () => {
  const my = useMy(); // Get root pathfinder
  return <ArrayViewIndexKey $={my.users}>{usersItem}</ArrayViewIndexKey>;
};
const usersItem = (i: number) => <UserItem index={i} />;

Use arrays (map indexes)

// UserList.tsx
import { useStoreSelect, mapIndexes } from 'react-state-editor';
import { UserItem } from './UserItem';
import { useMy } from './store';

export const UserList = () => {
  const my = useMy(); // Get root pathfinder

  // Reacts on every user property change (!!!)
  const users = useStoreSelect(my.users);
  // ... Some another usage of users

  return (
    <>
      {mapIndexes(users.length, (i) => (
        <UserItem key={i} index={i} />
      ))}
    </>
  );
};

Use any value

// UsersInfo.tsx
import { useStoreSelect, mapIndexes } from 'react-state-editor';
import { UserItem } from './UserItem';
import { useMy } from './store';

export const UsersInfo = () => {
  const my = useMy(); // Get root pathfinder
  return <View $={my}>{usersInfo}</View>;
};
const usersInfo = (my: MyOutState) => <div>Total {my.users.length} users</div>;
10.0.5

2 years ago

9.0.3

2 years ago

10.0.0

2 years ago

10.0.1

2 years ago

10.0.2

2 years ago

10.0.3

2 years ago

10.0.4

2 years ago

7.0.0

2 years ago

7.0.2

2 years ago

7.0.1

2 years ago

8.0.0

2 years ago

9.0.2

2 years ago

9.0.1

2 years ago

9.0.0

2 years ago

5.0.6

2 years ago

5.0.5

2 years ago

5.0.4

2 years ago

5.0.3

2 years ago

5.0.2

2 years ago

5.0.1

2 years ago

5.0.0

2 years ago

6.0.1

2 years ago

6.0.0

2 years ago

4.2.1

2 years ago

4.0.1

2 years ago

4.0.0

2 years ago

3.0.0

2 years ago

2.0.0

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago