Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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
- Host: GitHub
- URL: https://github.com/revskill10/use-state
- Owner: revskill10
- License: other
- Created: 2024-05-09T05:11:07.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2024-05-18T23:38:10.000Z (6 months ago)
- Last Synced: 2024-10-12T20:58:51.500Z (27 days ago)
- Topics: react
- Language: TypeScript
- Homepage: https://revskill10.github.io/use-state/
- Size: 467 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
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);
}
```