react-event-driven-store
v1.0.7
Published
`react-event-driven-store` is a lightweight and versatile state management solution for React applications, built around modularity, reactivity, and event-driven architecture. It provides developers with a simple yet powerful way to manage global state us
Downloads
516
Readme
React event driven store
react-event-driven-store
is a lightweight and versatile state management solution for React applications, built around modularity, reactivity, and event-driven architecture. It provides developers with a simple yet powerful way to manage global state using React's Context API and custom hooks.
Designed to be intuitive and flexible, this library allows you to easily define, manipulate, and subscribe to state across your application. With a clear and consistent API, developers can organize state logic, dispatch mutations, and listen for updates—all while ensuring optimal performance. Whether you're building a small project or a complex app, react-event-driven-store offers
a clean, efficient, and reactive approach to state management.
Installation
To install the react-event-driven-store
package, run the following command:
npm install react-event-driven-store
Usage
Setting up the Store
To set up a store, wrap your app with the EventStoreProvider
component. You can define modules that contain state, mutations (for state updates), and getters (for retrieving computed values). In this example, we're using a simple counter module.
import { EventStoreProvider, ModuleType } from "react-event-driven-store";
export const App = () => {
return (
<EventStoreProvider<[ModuleType<ICounter>]>
modules={[
{
moduleName: "counter",
state: {
counter: 0,
},
mutation: {
inc(value) {
this.counter = this.counter + value.payload;
},
dec() {
this.counter = this.counter - 1;
},
},
getters: {
getCounter() {
return {
counter: this.counter,
};
},
},
},
]}
>
{/* UI */}
</EventStoreProvider>
);
};
Using Mutations
In your components, you can utilize the useModuleMutation
hook to interact with the state through mutations. Here’s an example of how to use the mutate function to dispatch actions to a module:
import { useModuleMutation } from "react-event-driven-store";
export const HomePage = () => {
const { mutate } = useModuleMutation("counter");
return (
<>
<button
onClick={() =>
mutate({
payload: 1,
event: "INC",
commit: "inc",
})
}
>
Increment Counter
</button>
</>
);
};
Using Module Selector
The useModuleSelector
hook allows components to access specific data from your state modules and automatically subscribe to updates. This ensures that your component re-renders whenever the selected data changes. Here’s an example of how to use the useModuleSelector
in a component:
import { useModuleSelector } from "react-event-driven-store";
const SomeComponent = () => {
const { value } = useModuleSelector<{
counter: number;
}>({
getterName: "getCounter",
commit: ["INC"],
moduleName: "counter",
});
let { counter } = value;
console.log("*********", counter);
return (
<>
<span>Counter Value: {counter}</span>
</>
);
};
export { SomeComponent };
In this example, the SomeComponent uses the useModuleSelector hook to access the counter value from the counter module. The getterName specifies which getter function to call, and the commit array defines which events the component should listen to for updates.
Important Note: If the commit array is empty (i.e., commit: []), the component will not re-render when the state changes. In this case, you will only receive the initial value from the store upon mounting the component.
Whenever the counter value is updated through a mutation (e.g., when the increment or decrement actions are dispatched), and the appropriate events are specified in the commit array, the component will automatically re-render with the latest counter value, providing a reactive and seamless experience.
Using SelectorItem
In the example below, we define a SomeComponent that utilizes the SelectorItem wrapper, which is essentially a higher-order component for the useModuleSelector hook. This component simplifies the process of selecting state from a specific module.
import { SelectorItem } from "react-event-driven-store";
const SomeComponent = () => {
return (
<>
<span>SomeComponent</span>
<SelectorItem<{
counter: number;
}>
selectorConfig={{
getterName: "getCounter",
commit: ["INC"],
moduleName: "counter",
}}
>
{(value) => {
console.log("value", value.counter);
return <></>;
}}
</SelectorItem>
</>
);
};
export { SomeComponent };
Key Advantages of SelectorItem
The SelectorItem component acts as a wrapper around the useModuleSelector hook, accepting the same configuration interface. The main benefits of using SelectorItem include:
Encapsulation of Selector Logic: By encapsulating the selector logic within SelectorItem, it simplifies the component structure, making it easier to manage and maintain.
Reduced Re-renders: SelectorItem minimizes unnecessary re-renders by allowing updates to be localized only to specific parts of the UI that need to change in response to events. This leads to improved performance and a more efficient rendering process.
Event Emitter: Streamlined Component Communication
In modern applications, effective communication between components is crucial for maintaining a responsive and organized user interface. The Event Emitter pattern provides a powerful mechanism to facilitate this interaction without relying on shared state management.
Emitting Events
The SomeComponent
illustrates how to use the useEmitEvent hook to broadcast events throughout your application. By clicking the button, it emits an event named EMIT_AGE, passing along relevant data (in this case, an age value of 50). This decouples the emitting component from any listeners, promoting a more modular design.
import { useEmitEvent } from "react-event-driven-store";
const SomeComponent = () => {
const emitEvent = useEmitEvent();
return (
<>
<span>SomeComponent</span>
<button
onClick={() =>
emitEvent("EMIT_AGE", {
age: 50,
})
}
>
Emit event
</button>
</>
);
};
export { SomeComponent };
Listening for Events
On the other hand, the SomeComponentTwo showcases the use of the useOnEvent hook, which allows components to listen for specific events. In this example, it listens for the EMIT_AGE event and logs the data received when the event is emitted. This selective listening enhances performance by ensuring components only react to relevant changes.
import { useOnEvent } from "react-event-driven-store";
const SomeComponentTwo = () => {
useOnEvent("EMIT_AGE", (data) => {
console.log("on event", data);
});
return <></>;
};
export { SomeComponentTwo };
Key Benefits
- Decoupled Communication: Components can interact without being tightly coupled to a shared state, leading to better maintainability.
- Selective Listening: By allowing components to listen for specific events, you minimize unnecessary re-renders, improving application performance.
- Enhanced Modularity: This approach promotes modular design, enabling components to be reused across different contexts without modifications. By incorporating the Event Emitter pattern, your application can achieve more dynamic and efficient component interactions, creating a seamless experience for users while simplifying your codebase.
Note on Selectors and Event Emitters
It's important to highlight that selectors, which rely on specific events triggered through mutations (using commit lists), will not be activated by events emitted via the Event Emitter pattern. This is because selectors are designed to respond to state changes managed through mutations, while event emitters facilitate direct communication between components without altering the application state.
Therefore, if you need components to reactively update based on certain state changes, ensure those updates are managed through the defined mutation mechanisms rather than the event emitter.
Key Benefits
- Optimized Rendering: Components only re-render when the selected state changes, improving performance.
- Cleaner Code: Reduces the need for boilerplate code for managing state subscriptions.
- Ease of Use: Simple API that integrates seamlessly with your existing components.
Conclusion
The EventStateStoreProvider
offers a robust and flexible approach to managing state, events, and reactivity across your React components. By utilizing its features, you can streamline communication, encapsulate logic, and minimize unnecessary re-renders.
Key takeaways include:
Encapsulation and Flexibility: With useModuleMutation and useModuleSelector, components can interact with the state in a modular and organized way, allowing for better code separation and flexibility. The mutation system ensures that changes are committed predictably, while selectors provide reactive access to state values.
Efficient Re-rendering: The SelectorItem component acts as a wrapper for selectors, encapsulating selector logic and allowing precise control over which UI parts re-render. This helps in optimizing performance by minimizing re-renders to only the relevant components.
Event-Driven Communication: The built-in event emitter (useEmitEvent and useOnEvent) allows components to communicate without altering the state, which is useful for scenarios where direct component-to-component interaction is required. However, note that selectors dependent on mutation-based events will not react to emitted events.
By combining state management and event-driven design, EventStateStoreProvider simplifies the architecture of complex applications, reduces boilerplate code, and enhances maintainability. It brings together a cohesive approach to managing state and events, providing developers with the tools needed to build responsive and scalable React applications.