Search code examples
reactjswebpacknext.jsmobx

React HotReload CodeChanges made in MobX Store


I always have to restart the dev server for displaying changes that I made in my mobx store class, for example logging values into console, which is not really developer friendly. I am open for any kind of solution that could impact the hot-reload behaviour when changing the Store.ts code. Here is my implementation for nextJs.

_app.tsx

import { StoreProvider } from 'components/configurator/StoreProvider';

function App({ Component, pageProps }: AppProps) {
    return (
        <StoreProvider {...pageProps}>
            <Component {...pageProps} />
        </StoreProvider>
    );
}

export default App;

StoreProvider.tsx

import { createContext, useContext } from 'react';
import { CartStore } from 'shared/store';

let store;
export const StoreContext = createContext<CartStore>({} as CartStore);

export function useStore() {
    const context = useContext(StoreContext);
    if (context === undefined) {
        throw new Error('useStore must be used within StoreProvider');
    }

    return context;
}

export function StoreProvider({ children, initialState: initialData }) {
    const store = initializeStore(initialData);

    return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
}

function initializeStore(initialData = null) {
    const _store = store ?? new CartStore();

    // If your page has Next.js data fetching methods that use a Mobx store, it will
    // get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details
    if (initialData) {
        _store.hydrate(initialData);
    }

    // For SSG and SSR always create a new store
    if (typeof window === 'undefined') return _store;

    // Create the store once in the client
    if (!store) store = _store;

    return _store;
}

Store.ts

import { makeAutoObservable } from 'mobx';
import { enableStaticRendering } from 'mobx-react-lite';
import { ProductGroupData } from './APITypes';

// NextJS specific, don´t render server-side
enableStaticRendering(typeof window === 'undefined');

export interface ProductGroup extends ProductGroupData {}

export class CartStore {
    productGroups: ProductGroup[] = [];

    constructor() {
        makeAutoObservable(this);
    }

    hydrate = (data) => {
        if (!data) return;

        this.productGroups = data.productGroups;
    };

    updateProductGroups = (productGroup: ProductGroup) => {
        const packageAlreadyInStore = this.productGroups.find((x) => x.name === productGroup.name);

        if (!packageAlreadyInStore) {
            this.productGroups.push(productGroup);
        } else {
            this.productGroups = this.productGroups.filter((x) => x.name !== productGroup.name);
        }
    };
}


Solution

  • I figured it out for myself, now I am hydrating the StoreProvder in the _app.tsx by passing it from the pageProps.

    _app.tsx

    import 'styles/global.css';
    
    import type { AppProps } from 'next/app';
    
    import { StoreProvider } from 'components/configurator/StoreProvider';
    
    function App({ Component, pageProps }: AppProps) {
        return (
            <StoreProvider {...pageProps} hydrationData={pageProps.hydrationData}>
                <Component {...pageProps} />
            </StoreProvider>
        );
    }
    
    export default App;
    
    

    StoreProvider.tsx

    import { createContext, useContext } from 'react';
    import { CartStore } from 'shared/Store';
    
    let store;
    export const StoreContext = createContext<CartStore>({} as CartStore);
    
    export function useStore() {
        const context = useContext(StoreContext);
        if (context === undefined) {
            throw new Error('useStore must be used within StoreProvider');
        }
    
        return context;
    }
    
    export function StoreProvider({ children, hydrationData: hydrationData }) {
        const store = initializeStore(hydrationData);
    
        return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>;
    }
    
    function initializeStore(hydrationData = null) {
        const _store = store ?? new CartStore();
    
        // If your page has Next.js data fetching methods that use a Mobx store, it will
        // get hydrated here, check `pages/ssg.js` and `pages/ssr.js` for more details
        if (hydrationData) {
            _store.hydrate(hydrationData);
        }
    
        // For SSG and SSR always create a new store
        if (typeof window === 'undefined') return _store;
    
        // Create the store once in the client
        if (!store) store = _store;
    
        return _store;
    }