npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@lomray/react-mobx-manager

v4.0.1

Published

This package provides Mobx stores manager for react.

Downloads

622

Readme

Key features:

  • One way to escape state tree 🌲🌳🌴.
  • Ready to use with Suspense.
  • Support SSR.
  • Support render to stream.
  • 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 😎

Table of contents

Getting started

The React-mobx-manager package is distributed using npm, the node package manager.

npm i --save @lomray/react-mobx-manager @lomray/consistent-suspense

NOTE: this package use @lomray/consistent-suspense for generate stable id's inside Suspense.

Choose one of store id generating strategy (1 or 2 or 3):

  1. Configure your bundler to keep classnames and function names. Store id will be generated from class names (chose unique class names).
  • React: (craco or webpack config, terser options)
terserOptions.keep_classnames = true;
terserOptions.keep_fnames = true;
  • React Native: (metro bundler config: metro.config.js)
module.exports = {
  transformer: {
    minifierConfig: {
      keep_classnames: true,
      keep_fnames: true,
    },
  }
}
  1. Define id for each store.
import { makeObservable } from "mobx";

class MyStore {
  /**
   * Define unique store id
   */
  static id = 'Unique-store-id';

  constructor() {
    makeObservable(this, {})
  }
}
  1. Use Vite plugins.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import MobxManager from '@lomray/react-mobx-manager/plugins/vite/index';

// https://vitejs.dev/config/
export default defineConfig({
  /**
   * Store id's will be generated automatically, just chill
   */
  plugins: [react(), MobxManager()]
});

/**
 * Detect mobx store:
 - by makeObservable or makeAutoObservable
 - by @mobx-store jsdoc before class
 */

Usage

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 { ConsistentSuspenseProvider } from '@lomray/consistent-suspense';
import { Manager, StoreManagerProvider, MobxLocalStorage } from '@lomray/react-mobx-manager';
import App from './app';
import MyApiClient from './services/my-api-client';
import './index.css';

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 each store
});

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(
  <React.StrictMode>
    <ConsistentSuspenseProvider> {/** required **/}
      <StoreManagerProvider storeManager={storeManager} shouldInit>
        <App />
      </StoreManagerProvider>
    </ConsistentSuspenseProvider>
  </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 behavior for global application stores
   * Default: false
   */
  static isGlobal = 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.

Support SSR

Does this library support SSR? Short answer - yes, but we need some steps to prepare our framework.

Important Tips

  • Create global store only for e.g: application settings, logged user, theme, etc.
  • To get started, stick to the concept: Store for Component. Don't connect (through withStores) not global store to several components.

Documentation

Manager

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
  // storage: new CombinedStorage({ local: MobxAsyncStorage, cookie: CookieStorage }), // Define multiple storages
  /**
   * 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,
    /**
     * Configure store destroy timers
     */
    destroyTimers: {
      init: 500,
      touched: 10000, // NOTE: set to max request timeout
      unused: 1000,
    },
  }
});

// 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(SomeGlobalStore);
const store2 = storeManager.getStore(SomeStore, { contextId: 'necessary-context-id' });

/**
 * Get stores context's and relations
 */
const relations = storeManager.getStoresRelations();

/**
 * Manually create stores for component
 * NOTE: 'withStores' wrapper use this method, probably you won't need it
 * WARNING: Avoid using this method directly, it may cause unexpected behavior
 */
const stores = storeManager.createStores(['someStore', MyStore], 'parent-id', 'context-id', 'suspense-id', 'HomePage', { componentProp: 'test' });

/**
 * Mount/Unmount simple stores to component
 * WARNING: Avoid using this method directly, it may cause unexpected behavior
 */
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');

/**
 * Choose storage and attributes
 */
const storeClass2 = Manager.persistStore(class MyStore {}, 'my-store', {
  attributes: {
    local: ['someProp'], // thees attributes will be stored in local storage
    cookie: ['specificProp'], // thees attributes will be stored in cookie storage
  }
});

withStores

import { withStores } from '@lomray/react-mobx-manager';

const stores = { 
  myStore: MyStore, 
  anotherStore: AnotherStore,
  // assign static id to future store
  demoStore: { store: SomeStore, id: 'my-id' },
  // get parent store, do this only inside children components
  parentStore: { store: SomeParentStore, isParent: true },
};

/**
 * Create and connect 'stores' to component with custom context id
 * NOTE: In most cases, you don't need to pass a third argument (contextId). 
 */
withStores(Component, stores, { customContextId: 'optional-context-id' });

StoreManagerProvider

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>

useStoreManager

import { useStoreManager } from '@lomray/react-mobx-manager';

const MyComponent: FC = () => {
  /**
   * Get store manager inside your function component
   */
  const storeManager = useStoreManager();
}

useStoreManagerParent

import { useStoreManagerParent } from '@lomray/react-mobx-manager';

const MyComponent: FC = () => {
  /**
   * Get parent context id
   */
  const { parentId } = useStoreManagerParent();
}

Store

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 behavior for global application stores
   * Default: false
   */
  static isGlobal = true;
    
  /**
   * Store observable state
   */
  public state = {
    name: 'Matthew',
    username: 'meow',
  }

  /**
   * @private
   */
  private readonly someParentStore: ClassReturnType<typeof SomeParentStore>;

  /**
   * @constructor
   * 
   * getStore - get parent store or global 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 global 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 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 } };
  }
}

Lifecycles:

  • constructor
  • wakeup (restore state from persisted store)
  • init
  • onDestroy

Demo

Explore demo app to more understand.

React Native debug plugin

For debug state, you can use Reactotron debug plugin

Bugs and feature requests

Bug or a feature request, please open a new issue.

License

Made with 💚

Published under MIT License.