@mrdivinemaniac/react-context-store v0.0.1
react-hooked-state
This small library demonstrates a way to use React's context API to create a global store using the familiar and convenient hooks API.
Creating a Store
Just import createStore
from react-hooked-state
.
createStore
takes the initial value of the store as an argument.
After that, you can use useStore
returned from createStore
to implement a custom hook with the functionality that you want.
// store/user/index.js
import { useCallback } from "react";
import { createStore } from "react-hooked-state";
const initialState = {
loading: false,
error: null,
user: {},
};
const { Provider, useStore } = createStore(initialState);
export const UserStoreProvider = Provider;
export function useUserStore() {
const {
data,
setData, // Replaces data in the whole store
mergeData, // Merges with existing data in the store like setState in class components
} = useStore();
// An Action
const resetUserData = useCallback(() => {
setData(initialState);
}, []);
// Another Action
const setUser = useCallback(
(user) => {
mergeData({ user });
},
[mergeData]
);
// An Action which makes an async request
const loadUserData = useCallback(() => {
mergeData({ loading: true });
requestUserFromServer()
.then((user) => {
mergeData({ user, loading: false });
})
.catch((e) => {
mergeData({ error: e, loading: false });
});
}, [mergeData]);
const { loading, user, error } = data;
return {
user,
loadingUser: loading,
userLoadError: error,
loadUserData,
setUser,
resetUserData,
};
}
Creating Multiple Hooks
You can also create separate hooks for separate operations. The API is totally upto you!
Below is an example of creating a separate hook for just the actions.
// store/user/index.js (alternate)
import { useCallback } from "react";
import { createStore } from "react-hooked-state";
const { Provider, useStore } = createStore({
loading: false,
error: null,
user: {},
});
export const UserStoreProvider = Provider;
export function useUserStore() {
const { data, mergeData } = useStore();
const { loading, user, error } = data;
return {
user,
loadingUser: loading,
userLoadError: error,
};
}
export function useUserActions() {
const { data, mergeData } = useStore();
// An Action
const resetUserData = useCallback(() => {
setData(initialState);
}, []);
// Another Action
const setUser = useCallback(
(user) => {
mergeData({ user });
},
[mergeData]
);
// An Action which makes an async request
const loadUserData = useCallback(() => {
mergeData({ loading: true });
requestUserFromServer()
.then((user) => {
mergeData({ user, loading: false });
})
.catch((e) => {
mergeData({ error: e, loading: false });
});
}, [mergeData]);
return { resetUserData, setUser, loadUserData };
}
Important Information
It is important to wrap the actions in useCallback
in order to avoid unnecessary re-renders in child components.
Using a Store
Using the store is simple. Just wrap the Provider
returned from createStore
in any component whose descendants you wish to use the store in.
After that, you can directly import and use the hooks that you created for your stores.
If you have multiple stores and providers then you can use combineProviders
in order to merge all the providers into one.
import { combineProviders } from "react-hooked-state";
import { UserStoreProvider } from "./store/user";
import { SomeOtherStoreProvider } from "./store/some-other-store";
import { Profile } from "./components/profile";
const StoreProvider = combineProviders(
UserStoreProvider,
SomeOtherStoreProvider
);
export function App() {
return (
<StoreProvider>
<Profile />
</StoreProvider>
);
}
// components/Profile.js
import { useUserStore, useUserActions } from "../store/user";
function Profile() {
const { user, loadingUser, userLoadError } = useUserStore();
const { loadUserData } = useUserActions();
useEffect(() => {
loadUserData();
}, [loadUserData]);
if (loadingUser) return <div>Loading...</div>;
if (userLoadError)
return <div> Oops! There was an error loading the user </div>;
return (
<ul>
<li> Name: {user.name} </li>
<li> Email: {user.email} </li>
</ul>
);
}
Additional Notes
Examples
There are some examples inside the examples directory.
The Todo Example
This example demonstrates creation of multiple hook for the same store according to the use cases of the hooks.
yarn example:todo
OR
npm run example:todo
Using The Context Directly
The createStore
method also returns a Context
object along with Provider
and useStore
. You could also directly use useContext(Context)
instead of useStore
but that will take away some trivial error checking to see if the context has data in it.
Example:
// store/user/index.js
import { useCallback, useContext } from "react";
import { createStore } from "react-hooked-state";
const initialState = {
loading: false,
error: null,
user: {},
};
const { Provider, useStore, Context } = createStore(initialState);
export const UserStoreProvider = Provider;
export function useUserStore() {
const contextData = useContext(Context);
if (!contextData) throw new Error('No value supplied to context');
const {
data,
setData,
mergeData
} = contextData;
......
}
3 years ago