Global state management based on React-hooks, with built-in support for both synchronous & queued-asynchronus (FIFO) actions.
"react-hooka" is React-hook based state management library. "react-hooka" supports both synchronous & queued-asynchronous (FIFO) actions.
All asynchronous state update operations are guaranteed to change the application state in the order of asynchronous-operation invocation.
The "react-hooka" is designed to have multiple independent slices within the global store. Each slice is an independent sandbox which contain "state" as well as "actions". Operations performed in context of a given "slice" does not affect other "slices" within the store.
React-components which subscribes to a given "slice" are only re-rendered when the subscribed slice-state gets any update. React-components using a slice have option to optout re-renders on slice-state updates if the need be.
npm install react-hooka
Store refers to a global object holding application state and available action on the state in a React application.
Under the hood, store maintains multiple slices, and each of the slice independently manages its state using the actions available on its state.
Slice is an independent subset of the store. A slice holds a subset of application state and defined actions on its state.
Any operation in context of a given slice does not impact other slices within the store. Only the components using (subscribed to) a given slice are notified of the state updates and may perform rerender if required.
Actions are methods responsible to update the state of a slice. Each action is identified by an "Action-Identifier" which must be unique string value within the slice.
Actions methods receives "currentState" as parameter which represents the current snapshot of slice-state.
Actions must return "updatedState". The state returned by the Action is replaced into the slice-state by the "react-hooka".
Note - v2.0.0: State returned by the Action is replaced, not merged into the existing state.
Actions are of two kinds:
Synchronous action performs state update in synchronous fashion and are blocking in nature. All state update operations which do not perform I/O or Network operations are cadidates of synchronous actions.
// Synchronous Action named "SYNCHRONUS_ACTION_IDENTIFIER"
{
SYNCHRONUS_ACTION_IDENTIFIER: (currentState) => {
try {
const updatedState = {...currentState, n1: 100 };
return updatedState;
} catch (error) {
throw new Error(error.message);
}
}
}
Asynchronous action performs state update in asynchronous fashion and are non-blocking in nature. All Asynchronous operations are expected to return a "promise". All state update operations which perform I/O or Network oprations are cadidates of asynchronous actions.
// Asynchronous Action named "ASYNCHRONUS_ACTION_IDENTIFIER"
{
ASYNCHRONUS_ACTION_IDENTIFIER: (currentState) => {
return new Promise((resolve, reject) => {
(async () => {
try {
setTimeout(() => {
const updatedState = {...currentState, n1: 100 };
resolve(updatedState);
}, 100);
} catch (error) {
reject(new Error(error.message));
}
})();
});
}
}
Using react-hooka is straight forward, which requires following steps:
Initialise store-slice (state and actions belonging to a slice) using "initStoreSlice" method.
"initStoreSlice" method takes 3 parameters:
- sliceIdentifier: Apllication wide unique string value to identify store-slice.
- actions: An object holding all the "actions" to be performed on the slice-state. All actions within the "actions" object need to have string "Action-Identifier" which should be unique within the slice.
- initialState: An object having initial state of the slice.
import { initStoreSlice } from "react-hooka";
const actions = {
SYNCHRONUS_ACTION_IDENTIFIER: (currentState) => {
try {
const updatedState = {...currentState, n1: 100 };
return updatedState;
} catch (error) {
throw new Error(error.message);
}
},
ASYNCHRONUS_ACTION_IDENTIFIER: (currentState) => {
return new Promise((resolve, reject) => {
(async () => {
try {
setTimeout(() => {
const updatedState = {...currentState, n2: 200 };
resolve(updatedState);
}, 100);
} catch (error) {
reject(new Error(error.message));
}
})();
});
}
};
const initialState = {
n1: 10,
n2: 20,
n3: 30
};
initStoreSlice("TEST_STORE", actions, initialState);
Use "react-hooka" in React component for state management.
- Import useStore hook from "react-hooka".
import { useStore } from "react-hooka";
- Use the useStore hook with the slice-name of the slice in context.
const {state, dispatch, dispatchAsync} = useStore("TEST_STORE");
state represents the current state of the slice-state.
dispatch method can be used to perform synchronous (blocking) operation on slice-state.
dispatchAsync method can be used to perform asynchronous (non-blocking) operation on slice-state.
OPTIONAL OPTOUT from rerendering of React-components on slice-state updates.
There might be cases when React-components are only performing actions but not displaying the state (such as a FORM only adding new items to state but not displaying existing items.). In such cases React-components can optout rerenders by passing "false" to the 2nd parameter shouldTriggerRerender of useStore hook.
By default, value of shouldTriggerRerender is true, thus all slice-state updates will trigger rerender on React-components which are using useStore hook.
const { state, dispatch, dispatchAsync } = useStore("TEST_STORE", false);
OR
// Not using "state" at all.
const { dispatch, dispatchAsync } = useStore("TEST_STORE", false);
Delete a store-slice if needed.
If a store-slice is no more needed, it can be deleted from the global-store. deleteStoreSlice method of "react-hooka" can be used to delete a store-slice.
import { deleteStoreSlice } from "react-hooka";
deleteStoreSlice("SLICE_NAME");
1. Configure store slice:
// FILE: testStore.js
import { initStoreSlice } from "react-hooka";
const init = () => {
const actions = {
UPDATE1: (currentState) => {
try {
const updatedState = {...currentState, n1: 100 };
return updatedState;
} catch (error) {
throw new Error(error.message);
}
},
UPDATE2: (currentState) => {
try {
const updatedState = {...currentState, n2: 200 };
return updatedState;
} catch (error) {
throw new Error(error.message);
}
},
UPDATE3: (currentState) => {
try {
const updatedState = {...currentState, n3: 300 };
return updatedState;
} catch (error) {
throw new Error(error.message);
}
},
UPDATE1_ASYNC: (currentState) => {
return new Promise((resolve, reject) => {
(async () => {
try {
setTimeout(() => {
const updatedState = {...currentState, n1: 1000 };
resolve(updatedState);
}, 100);
} catch (error) {
reject(new Error(error.message));
}
})();
});
},
UPDATE2_ASYNC: (currentState) => {
return new Promise((resolve, reject) => {
(async () => {
try {
setTimeout(() => {
const updatedState = {...currentState, n2: 2000 };
resolve(updatedState);
}, 500);
} catch (error) {
reject(new Error(error.message));
}
})();
});
},
UPDATE3_ASYNC: (currentState) => {
return new Promise((resolve, reject) => {
(async () => {
try {
setTimeout(() => {
const updatedState = {...currentState, n3: 3000 };
resolve(updatedState);
}, 800);
} catch (error) {
reject(new Error(error.message));
}
})();
});
}
};
const initialState = {
n1: 10,
n2: 20,
n3: 30
};
initStoreSlice("TEST_STORE", actions, initialState);
};
export default init;
2. Initialize store slice:
// FILE: index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import initTestStore from "./store/testStore";
// Initialising "TEST_STORE"
initTestStore();
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
3. Managing state in component:
// FILE: App.js
import React from "react";
import { useStore } from "react-hooka";
import "./styles.css";
export default function App() {
const { state, dispatch, dispatchAsync } = useStore("TEST_STORE");
const SyncUpdate = () => {
dispatch("UPDATE1");
dispatch("UPDATE2");
dispatch("UPDATE3");
};
const AsyncUpdate = () => {
dispatchAsync("UPDATE1_ASYNC");
dispatchAsync("UPDATE2_ASYNC");
dispatchAsync("UPDATE3_ASYNC");
};
return (
<div className="App">
<p>1. {state.n1}</p>
<p>2. {state.n2}</p>
<p>3. {state.n3}</p>
<p><button onClick={SyncUpdate}>Synchronous Update</button></p>
<p><button onClick={AsyncUpdate}>Asynchronous Update</button></p>
</div>
);
}
- State update with Synchronous and Asynchronous actions - https://codesandbox.io/s/global-statehook-sync-async-actions-rkipy
- Optout component rerender on state update - https://codesandbox.io/s/global-statehook-optout-rerender-zuvo6
- Using multiple state-slices on one component - https://codesandbox.io/s/global-statehook-multiple-state-slices-9nec4?file=/src/index.js
- Using multiple state-slices through multiple components - https://codesandbox.io/s/global-statehook-multiple-state-slices-multiple-components-x7e98?file=/src/App.js
Abhay Kumar
This project is licensed under the MIT License.