- One way to escape state tree 🌲🌳🌴.
- Manage your Mobx stores like a boss - debug like a hacker.
- Simple idea - simple implementation.
- Small package size.
- Support code splitting out of the box.
- Access stores from other stores.
- Can be a replacement for react context.
- And many other nice things 😎
- Getting started
- Usage
- Support SSR
- Important Tips
- Documentation
- React Native Debug Plugin
- Bugs and feature requests
- License
The React-mobx-manager package is distributed using npm, the node package manager.
npm i --save @lomray/react-mobx-manager
Optional: Configure your bundler to keep classnames and function names in production OR use id
for each store:
- React: (craco or webpack config, terser options)
terserOptions.keep_classnames = true;
terserOptions.keep_fnames = true;
- React Native: (metro bundler config)
transformer: {
minifierConfig: {
keep_classnames: true,
keep_fnames: true,
},
}
Import Manager, StoreManagerProvider
from @lomray/react-mobx-manager
into your index file and wrap <App/>
with <StoreManagerProvider/>
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { Manager, StoreManagerProvider, MobxLocalStorage } from '@lomray/react-mobx-manager';
import App from './app';
import MyApiClient from './services/my-api-client';
const apiClient = new MyApiClient();
const storeManager = new Manager({
storage: new MobxLocalStorage(), // optional: needs for persisting stores
storesParams: { apiClient }, // optional: we can provide our api client for access from the store
});
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<StoreManagerProvider storeManager={storeManager} shouldInit>
<App />
</StoreManagerProvider>
</React.StrictMode>,
);
Connect mobx store to the manager, and you're good to go!
import { withStores, Manager } from '@lomray/react-mobx-manager';
import { makeObservable, observable, action } from 'mobx';
import type { IConstructorParams, ClassReturnType } from '@lomray/react-mobx-manager';
/**
* Mobx user store
*
* Usually store like that are related to the global store,
* because they store information about the current user,
* which may be needed in different places of the application.
*
* You may also want to save the state of the store, for example,
* to local storage, so that it can be restored after page reload,
* in this case, just export wrap export with 'persist':
* export default Manager.persistStore(UserStore, 'user');
*/
class UserStore {
/**
* Required only if we don't configure our bundler to keep classnames and function names
* Default: current class name
*/
static id = 'user';
/**
* You can also enable 'singleton' behavior for global application stores
* Default: false
*/
static isSingleton = true;
/**
* Our state
*/
public name = 'Matthew'
/**
* Our API client
*/
private apiClient: MyApiClient;
/**
* @constructor
*/
constructor({ getStore, apiClient }: IConstructorParams) {
this.apiClient = apiClient;
// if we need, we can get a global store or store from the parent context
// this.otherStore = getStore(SomeOtherStore);
makeObservable(this, {
name: observable,
setName: action.bound,
});
}
/**
* Set user name
*/
public setName(name: string): void {
this.name = name;
}
/**
* Example async
* Call this func from component
*/
public getNameFromApi = async (userId: number) => {
const name = await this.apiClient.fetchName(userId);
this.setName(name);
}
}
/**
* Define stores for component
*/
const stores = {
userStore: UserStore
};
// support typescript
type TProps = StoresType <typeof stores>;
/**
* User component
*/
const User: FC<TProps> = ({ userStore: { name } }) => {
return (
<div>{name}</div>
)
}
/**
* Connect stores to component
*/
export default withStores(User, stores);
See app example for a better understanding.
Does this library support SSR? Short answer - yes, but we need some steps to prepare our framework.
- Look at After.js (razzle) based project for a better understanding.
- Look at NextJS example for a better understanding (needs writing a wrapper).
- Create singleton store only for global stores e.g, for application settings, logged user, etc.
- To get started, stick to the concept: Store for Component. Don't connect (through withStores) not singleton store to several components.
import { Manager, MobxLocalStorage, MobxAsyncStorage } from '@lomray/react-mobx-manager';
// import AsyncStorage from '@react-native-async-storage/async-storage';
// Params
const storeManager = new Manager({
/**
* Optional: needs for persisting stores when you use Manager.persistStore
* Available: MobxLocalStorage and MobxAsyncStorage
* Default: none
*/
storage: new MobxLocalStorage(), // React
// storage: new MobxAsyncStorage(AsyncStorage), // React Native
/**
* Optional: provide some params for access from store constructor
* E.g. we can provide our api client for access from the store
* Default: {}
*/
storesParams: { apiClient },
/**
* Initial stores state.
* E.g. in SSR case, restore client state from a server
* Default: {}
*/
initState: { storeId: { param: 'test' } },
/**
* Additional manager options
*/
options: {
/**
* Disable persisting stores
* E.g., it should be 'true' on a server-side (SSR)
* Default: false
*/
shouldDisablePersist: false,
/**
* Remove the initial store state after initialization
* Default: true
*/
shouldRemoveInitState: true,
/**
* Enable this option if your application support server-side rendering (on both side, client and server)
* Default: false
*/
isSSR: false,
}
});
// Methods
/**
* Optional: Call this method to load persisting data from persist storage
* E.g., you may want manually to do this before the app render
* Default: StoreManagerProvider does this automatically with 'shoudInit' prop
*/
await storeManager.init();
/**
* Get all-created stores
*/
const managerStores = storeManager.getStores();
/**
* Get specific store
*/
const store = storeManager.getStore(SomeSingletonStore);
const store2 = storeManager.getStore(SomeStore, { contextId: 'necessary-context-id' });
/**
* Get stores context's and relations
*/
const relations = storeManager.getStoresRelations();
/**
* Generate unique context id
*/
const contextId = storeManager.createContextId();
/**
* Manually create stores for component
* NOTE: 'withStores' wrapper use this method, probably you won't need it
*/
const stores = storeManager.createStores(['someStore', MyStore], 'parent-id', 'context-id', 'HomePage');
/**
* Mount/Unmount simple stores to component
*/
const unmount = storeManager.mountStores(stores);
/**
* Get all-stores state
*/
const storesState = storeManager.toJSON();
/**
* Get all persisted store's state
*/
const persistedStoresState = storeManager.toPersistedJSON();
/**
* Get only persisted stores id's
*/
const persistedIds = Manager.getPersistedStoresIds();
/**
* Get store observable props
*/
const observableProps = Manager.getObservableProps(store);
/**
* Static method for access to manager from anywhere
* NOTE: Be careful with this, especially with SSR on server-side
*/
const manager = Manager.get();
/**
* Enable persisting state for store
*/
const storeClass = Manager.persistStore(class MyStore {}, 'my-store');
import { withStores } from '@lomray/react-mobx-manager';
/**
* Create and connect 'stores' to component
* NOTE: In most cases, you don't need to pass a third argument (contextId).
*/
withStores(Component, stores, 'optional-context-id');
const stores = { myStore: MyStore, anotherStore: AnotherStore };
import { StoreManagerProvider } from '@lomray/react-mobx-manager';
/**
* Wrap your application for a pass-down store manager, context id, and init persisted state
*
* shouldInit - default: false, enable initialize peristed state
* fallback - show loader while the manager has initialized
*/
<StoreManagerProvider storeManager={storeManager} shouldInit fallback={<Loader />}>
{/* your components */}
</StoreManagerProvider>
import { useStoreManagerContext } from '@lomray/react-mobx-manager';
const MyComponent: FC = () => {
/**
* Get store manager inside your function component
*/
const storeManager = useStoreManagerContext();
}
import { useStoreManagerParentContext } from '@lomray/react-mobx-manager';
const MyComponent: FC = () => {
/**
* Get parent context id
*/
const { parentId } = useStoreManagerParentContext();
}
import { makeObservable, observable, action } from 'mobx';
class MyStore {
/**
* Required only if we don't configure our bundler to keep classnames and function names
* Default: current class name
*/
static id = 'user';
/**
* You can also enable 'singleton' behavior for global application stores
* Default: false
*/
static isSingleton = true;
/**
* Store observable state
*/
public state = {
name: 'Matthew',
username: 'meow',
}
/**
* @private
*/
private someParentStore: ClassReturnType<typeof SomeParentStore>;
/**
* @constructor
*
* getStore - get parent store or singleton store
* storeManager - access to store manager
* apiClient - your custom param, see 'storesParams' in Manager
*/
constructor({ getStore, storeManager, apiClient, componentProps }: IConstructorParams) {
this.apiClient = apiClient;
this.someParentStore = getStore(SomeParentStore);
// In case when store is't singletone you can get access to component props
console.log(componentProps);
makeObservable(this, {
state: observable,
});
}
/**
* Define this method if you want to do something after initialize the store
* State restored, store ready for usage
* Optional.
* @private
*/
private init(): void {
// do something
}
/**
* Define this method if you want to do something when a component with this store is mount
* @private
*/
private onMount(): void {
// do something
}
/**
* Define this method if you want to do something when a component with this store is unmount
* @private
*/
private onDestroy(): void {
// do something
}
/**
* Custom method for return store state
* Optional.
* Default: @see Manager.toJSON
*/
public toJSON(): Record<string, any> {
return { state: { username: this.state.username } };
}
}
For debug state, you can use Reactotron debug plugin
Bug or a feature request, please open a new issue.
Made with 💚
Published under MIT License.