Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/revskill10/use-state

Reactive state machine for React
https://github.com/revskill10/use-state

react

Last synced: 12 days ago
JSON representation

Reactive state machine for React

Awesome Lists containing this project

README

        

# useState

Reactive state machine for React

# Installation

```js
npm install @revskill10/use-state
```

# Usage

```tsx
import { state$ } from '@revskill10/use-state';

export type ExampleProps = {
text?: String;
};
interface Message {
Decrement: {
amount: number;
};
Increment: {};
}
export function Example(props: ExampleProps) {
const [count, dispatch] = state$(0, [
{
messages: ['Increment'],
handler: (c) => {
c.set(c.get() + 1);
},
onChange: (c) => {
// eslint-disable-next-line no-console
console.log(`current count is ${c.get()}`);
},
},
{
messages: ['Decrement'],
handler: (c, payload) => {
if ('amount' in payload) {
c.set(c.get() - payload.amount ?? 1);
}
},
},
]);
return (
<>
{`${props.text} ${count}`}
dispatch('Increment', {})}
type="button"
id="increment-button"
>
Increment

dispatch('Decrement', { amount: 3 })}
type="button"
id="decrement-button"
>
Decrement

>
);
}
```

## Nested object example:

```tsx
import React from 'react';
import { state$ } from '@revskill10/use-state';

interface NestedExampleProps {
address: {
addressLine1: string | undefined;
addressLine2: string | undefined;
};
contact?: {
email: string | undefined;
phone: string | undefined;
};
}
interface Message {
ChangeAddress: {
addressLine1?: string;
addressLine2?: string;
};
}
export function NestedExample(props: NestedExampleProps) {
const [state, dispatch] = state$(props, [
{
messages: ['ChangeAddress'],
handler: (_state, payload) => {
if ('addressLine1' in payload) {
_state.address.addressLine1.set(payload.addressLine1);
}
if ('addressLine2' in payload) {
_state.address.addressLine2.set(payload.addressLine2);
}
},
},
]);

return (
<>

{state.address.addressLine1}


{state.address.addressLine2}


{/* // add form to change address line1 and address line 2 */}


Address Line 1:

dispatch('ChangeAddress', { addressLine1: e.target.value })
}
/>



Address Line 2:

dispatch('ChangeAddress', { addressLine2: e.target.value })
}
/>


>
);
}
```

## Todo App example:

```tsx
import { StateDispatch, state$ } from '@revskill10/use-state';

interface Todo {
completed: boolean;
id: number;
text: string;
}

interface AppState {
newTodoText?: string;
todos: Todo[];
}

interface TodoAppProps {
appState: AppState;
}

interface Message {
AddTodo: {
text: string;
};
ToggleTodo: {
id: number;
};
UpdateNewTodoText: {
text: string;
};
}
function TodoView({ state, dispatch }: StateDispatch) {
const handleAddTodo = () => {
if (state.newTodoText?.trim() !== '') {
dispatch('AddTodo', { text: state.newTodoText || '' });
}
};

const handleSubmit = (evt: React.FormEvent) => {
evt.preventDefault();
handleAddTodo();
};
return (


dispatch('UpdateNewTodoText', { text: e.target.value })
}
placeholder="Enter new todo"
/>
Add Todo

);
}
export function TodoApp(props: TodoAppProps) {
const [state, dispatch] = state$(props.appState, [
{
messages: ['AddTodo'],
handler: (_state, payload) => {
if ('text' in payload) {
const newTodo: Todo = {
id:
_state.todos.length > 0
? _state.todos[_state.todos.length - 1].id.get() + 1
: 1,
text: payload.text,
completed: false,
};
_state.todos.push(newTodo);
}
},
onChange: (_, { emit }) => {
emit('UpdateNewTodoText', { text: '' });
},
},
{
messages: ['ToggleTodo'],
handler: (_state, payload) => {
if ('id' in payload) {
const toggledTodo = _state.todos.find(
(todo) => todo.id.get() === payload.id
);
if (toggledTodo) {
toggledTodo.completed.set(!toggledTodo.completed.get());
}
}
},
},
{
messages: ['UpdateNewTodoText'],
handler: (_state, payload) => {
if ('text' in payload) {
_state.newTodoText.set(payload.text);
}
},
},
]);

return (


Todo List:



    {state.todos.map((todo) => (
  • dispatch('ToggleTodo', { id })}
    >
    {todo.text}

  • ))}



);
}
```

# API

- One important thing to remember, is the Message is global.
That means multiple separate components could listen to the same message and react independently.

- Async handler: You can use async await for handler.

```tsx
const [count, dispatch] = state$({ value: 0 }, [
{
messages: ['Increment'],
async handler: async (c) => {
c.loading.set(true);
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
c.loading.set(false);
c.value.set(c.value.get() + 1);
}
```