Skip to content

Commit 11184ea

Browse files
v 0.0.6: adding ReducerEmittor
1 parent 0a59d60 commit 11184ea

File tree

4 files changed

+407
-0
lines changed

4 files changed

+407
-0
lines changed

README.md

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
<div align="center">
2+
<h1 align="center">Emittor</h1>
3+
<h3>State Manager for 'ReactJs | NextJs'</h3>
4+
<h5>◦ Developed with the software and tools below.</h5>
5+
6+
<p align="center">
7+
<img src="https://img.shields.io/badge/Next.js-000000.svg?style&logo=Next.js&logoColor=white" alt="Next.js" />
8+
<img src="https://img.shields.io/badge/React-61DAFB.svg?style&logo=React&logoColor=black" alt="React" />
9+
<img src="https://img.shields.io/badge/TypeScript-3178C6.svg?style&logo=TypeScript&logoColor=white" alt="TypeScript" />
10+
</p>
11+
<img src="https://img.shields.io/github/license/immiProgrammer/emittor?style&color=5D6D7E" alt="GitHub license" />
12+
<img src="https://img.shields.io/github/last-commit/immiProgrammer/emittor?style&color=5D6D7E" alt="git-last-commit" />
13+
<img src="https://img.shields.io/github/commit-activity/m/immiProgrammer/emittor?style&color=5D6D7E" alt="GitHub commit activity" />
14+
<img src="https://img.shields.io/github/languages/top/immiProgrammer/emittor?style&color=5D6D7E" alt="GitHub top language" />
15+
</div>
16+
17+
---
18+
19+
## 📍 Overview
20+
21+
The custom emittor approach for state management in React offers a convenient way to manage and share states across multiple components without the need to wrap them with providers. By utilizing event-driven architecture, this method simplifies the handling of state updates, making it easy to work with event handlers and propagate changes throughout the application. With this approach, components can subscribe to state changes directly, ensuring seamless communication and synchronization between different parts of the UI. Additionally, it eliminates the boilerplate code associated with provider-based state management solutions, providing a more lightweight and flexible alternative for managing application state.
22+
23+
---
24+
25+
## 🚀 Getting Started
26+
27+
Install the package with npm:
28+
29+
```sh
30+
npm install emittor
31+
```
32+
33+
or yarn:
34+
35+
```sh
36+
yarn add emittor
37+
```
38+
39+
or pnpm:
40+
41+
```sh
42+
pnpm add emittor
43+
```
44+
45+
---
46+
47+
## 📖 Usage
48+
49+
First Create `Emittor` in separate file `/lib/emittor.ts`
50+
51+
```ts
52+
import { createEmittor } from 'emittor'
53+
54+
export const countEmittor = createEmittor(0)
55+
```
56+
57+
then:
58+
connect State with `Emittor` in client Components
59+
60+
```tsx
61+
"use client";
62+
import { countEmittor } from "./lib/emittor";
63+
64+
function Page() {
65+
// connect State with `Emittor` in client Components and use like useState Hook
66+
const [count, setCount] = useEmittor(countEmittor)
67+
...
68+
}
69+
```
70+
71+
This adjusted code snippet provides a simple and easy-to-understand usage example of the `emittor` package. It demonstrates using the `useEmittor` hook to manage state, similar to how `useState` is used in React.
72+
73+
> **Warning** </br>
74+
> Always create an emittor in a separate file</br>
75+
> Because in development mode, the whole file will be rendered again. That can cause unexpected bugs.
76+
77+
.
78+
79+
> **Note** </br>
80+
> create emittor anywhere in your project like [`createContext`](https://react.dev/reference/react/createContext) in React. </br>
81+
> and import `Emittor` where use or link (in components and other places)
82+
83+
</br>
84+
85+
Also, use a method that can change only the state without re-rendering this component when the state is changed.
86+
87+
```tsx
88+
"use client";
89+
90+
import { countEmittor } from "./lib/emittor"
91+
92+
function Page() {
93+
return (
94+
<button onClick={()=>countEmittor.setState(countEmittor.state+1)}>Add</button>
95+
)
96+
}
97+
98+
export default Page
99+
```
100+
101+
</br>
102+
103+
## Emittor In Multiple Components
104+
105+
The emittor package enables automatic state synchronization across React components. By simply integrating emittor, any component subscribing to state changes will update whenever the state changes anywhere in the application. This eliminates the need for manual state passing or provider wrapping, streamlining development and enhancing component responsiveness.
106+
107+
```tsx
108+
import { useEmittor } from "emittor"
109+
import { countEmittor } from "./lib/emittor"
110+
111+
function Page(){
112+
return(
113+
<>
114+
<View />
115+
<Button/>
116+
</>
117+
)
118+
}
119+
120+
// This component only re-renders when the state is changed.
121+
function View(){
122+
const [count, setCount] = useEmittor(countEmittor)
123+
return(
124+
<div>
125+
{count}
126+
</div>
127+
)
128+
}
129+
130+
function Button() {
131+
return (
132+
<button onClick={()=>countEmittor.setState(countEmittor.state+1)}>Add</button>
133+
)
134+
}
135+
136+
export default Page
137+
```
138+
139+
</br>
140+
141+
### Other Usage
142+
143+
You can also use the emitter in event listeners or other functions to modify state.
144+
145+
```ts
146+
countEmittor.getState()
147+
countEmittor.setState(10)
148+
// also use use directly
149+
countEmittor.state
150+
countEmittor.state = 10
151+
```
152+
153+
It will re-render those components where that emitter is used.
154+
</br>
155+
156+
## Usage of `ReducerEmittor`
157+
158+
First Create `ReducerEmittor` in separate file `/lib/emittor.ts`
159+
160+
```ts
161+
import { createReducerEmittor } from "emittor"
162+
163+
export const countREmittor = createReducerEmittor(0, {
164+
increment: e=> {
165+
e.setState(e.state+1)
166+
},
167+
decrement: e=> {
168+
e.setState(e.state-1)
169+
}
170+
})
171+
```
172+
173+
then use like this
174+
175+
```tsx
176+
"use client";
177+
import { useEmittor } from "emittor"
178+
import { countREmittor } from "./lib/emittor";
179+
180+
export default function Page() {
181+
const [count, setCount] = useEmittor(countREmittor)
182+
183+
return (
184+
<div className="flex gap-8">
185+
<button className="bg-slate-700 p-3">
186+
{count}
187+
</button>
188+
<button className="bg-slate-700 p-3" onClick={()=>countREmittor.reducers.increment()}>
189+
+
190+
</button>
191+
<button className="bg-slate-700 p-3" onClick={()=>countREmittor.reducers.decrement()}>
192+
-
193+
</button>
194+
</div>
195+
);
196+
}
197+
```
198+
199+
</br>
200+
201+
---
202+
203+
</br>
204+
205+
you can also use `Emittor` like `ReducerEmittor`
206+
</br>
207+
in `/lib/emittor.ts`
208+
209+
```ts
210+
import { createEmittor } from "emittor"
211+
212+
const emittor = createEmittor(0)
213+
214+
function increment(by:number){
215+
emittor.setState(emittor.state+by)
216+
}
217+
218+
function decrement(by:number){
219+
emittor.setState(emittor.state-by)
220+
}
221+
222+
export const countEmittor = {
223+
emittor,
224+
increment,
225+
decrement
226+
}
227+
```
228+
229+
use in Component
230+
231+
```tsx
232+
"use client";
233+
import { useEmittor } from "emittor"
234+
import { countEmittor } from "./lib/emittor";
235+
236+
export default function Page() {
237+
const [count, setCount] = useEmittor(countEmittor.emittor)
238+
239+
return (
240+
<div className="flex gap-8">
241+
<button className="bg-slate-700 p-3" onClick={()=>setCount(count+1)}>
242+
{count}
243+
</button>
244+
<button className="bg-slate-700 p-3" onClick={()=>countEmittor.increment(10)}>
245+
+
246+
</button>
247+
<button className="bg-slate-700 p-3" onClick={()=>countEmittor.decrement(5)}>
248+
-
249+
</button>
250+
</div>
251+
);
252+
}
253+
```
254+
255+
---
256+
257+
## 🤝 Contributing
258+
259+
Contributions are always welcome! Please follow these steps:
260+
261+
1. Fork the project repository. This creates a copy of the project on your account that you can modify without affecting the original project.
262+
2. Clone the forked repository to your local machine using a Git client like Git or GitHub Desktop.
263+
3. Create a new branch with a descriptive name (e.g., `new-feature-branch` or `bugfix-issue-123`).
264+
265+
```sh
266+
git checkout -b new-feature-branch
267+
```
268+
269+
4. Make changes to the project's codebase.
270+
5. Commit your changes to your local branch with a clear commit message that explains the changes you've made.
271+
272+
```sh
273+
git commit -m 'Implemented new feature.'
274+
```
275+
276+
6. Push your changes to your forked repository on GitHub using the following command
277+
278+
```sh
279+
git push origin new-feature-branch
280+
```
281+
282+
7. Create a new pull request to the original project repository. In the pull request, describe the changes you've made and why they're necessary.
283+
The project maintainers will review your changes and provide feedback or merge them into the main branch.
284+
285+
---
286+
287+
## 📄 License
288+
289+
This project is licensed under the `ℹ️ MIT` License.
290+
291+
---

src/createEmittor.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
type Callback<T> = (state: T) => void;
2+
3+
export class Emittor<T> {
4+
private callbacks: Callback<T>[] = [];
5+
private _state: T;
6+
7+
constructor(initialState: T) {
8+
this._state = initialState;
9+
}
10+
11+
exec(): void {
12+
// Executes all callbacks with the current state
13+
this.run(this._state)
14+
15+
} refresh = this.exec
16+
17+
run(state: T): void {
18+
// Executes all callbacks with the state
19+
this.callbacks.forEach(callback => {
20+
callback(state);
21+
});
22+
}
23+
24+
emit(state: T): void {
25+
// Set the new state if provided and execute callbacks
26+
this._state = state;
27+
this.exec();
28+
29+
} setState = this.emit
30+
31+
get state(): T {
32+
return this._state;
33+
}
34+
35+
set state(nState: T) {
36+
this.emit(nState);
37+
}
38+
39+
getState() {
40+
return this.state
41+
}
42+
43+
disconnect(callback: Callback<T>): void {
44+
// Remove the specified callback from the list
45+
const index = this.callbacks.indexOf(callback);
46+
if (index !== -1) {
47+
this.callbacks.splice(index, 1);
48+
}
49+
}
50+
51+
connect(callback: Callback<T>): void {
52+
// Add the callback to the list
53+
this.callbacks.push(callback);
54+
}
55+
}
56+
57+
export function createEmittor<T>(state: T) {
58+
return new Emittor(state)
59+
}
60+
61+
//* ---
62+
63+
type ReducerCallback<T> = (emittor: Emittor<T>) => unknown
64+
65+
export type Reducers<T, R extends string> = {
66+
[K in R]: ReducerCallback<T>
67+
}
68+
69+
type ReducerEmittorCallback<R extends string> = {
70+
[K in R]: () => void
71+
}
72+
73+
74+
export class ReducerEmittor<T, R extends string> extends Emittor<T> {
75+
reducers: ReducerEmittorCallback<R>
76+
constructor(initState: T, reducers: Reducers<T, R>) {
77+
super(initState)
78+
this.reducers = {} as ReducerEmittorCallback<R>
79+
Object.entries(reducers).map(([name, callback]) => {
80+
this.reducers[name as R] = () => (callback as ReducerCallback<T>)(this)
81+
})
82+
}
83+
}
84+
85+
export function createReducerEmittor<T, K extends string>(initState: T, reducers: Reducers<T, K>) {
86+
return new ReducerEmittor(initState, reducers)
87+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./createEmittor"
2+
export * from "./useEmittor"

0 commit comments

Comments
 (0)