MobX and Next.js (Fast Refresh)

Viewed 1266

I can't seem to find a way to set initial checkbox state from MobX store after a fast refresh from Next.js. As soon as i refresh the page, it renders with the state i set before.

For example: i check the checkbox (which has the checked/onChange routed to MobX) -> Page refresh -> The input persists to be checked, while the state is set to false.

I've tried all the other ways to pass the observer HOC (Observer, useObserver), disabling hydration, reworking store, but to no avail.

Here's the code:

store/ThemeStore.ts

import { makeAutoObservable } from 'mobx';

export type ThemeHydration = {
    darkTheme: boolean;
};

class ThemeStore {
    darkTheme = false;

    constructor() {
        makeAutoObservable(this);
    }

    setDarkTheme(value: boolean) {
        this.darkTheme = value;

    }

    hydrate(data?: ThemeHydration) {
        if (data) {
            this.darkTheme = data.darkTheme;
        }
    }
}

export default ThemeStore;

pages/index.tsx

import React, { useEffect } from "react";
import { reaction } from "mobx";

import styles from "@/styles/homepage.module.scss";
import { observer } from "mobx-react";
import { useStore } from "@/stores";

const HomePage = observer(function () {
  const { themeStore } = useStore();

  useEffect(() => {
    const re = reaction(
      () => themeStore.darkTheme,
      (value) => {
        const body = document.body;

        if (value) {
          body.classList.remove("theme-light");
          body.classList.add("theme-dark");
        } else {
          body.classList.remove("theme-dark");
          body.classList.add("theme-light");
        }
      },
      { fireImmediately: true }
    );
    return () => {
      re();
    };
  }, []);

  return (
    <div className={styles.container}>
      <main className={styles.main}>
          <input
            type="checkbox"
            defaultChecked={themeStore.darkTheme}
            onChange={(e) => {
              themeStore.setDarkTheme(e.target.checked);
            }}
          />
      </main>
    </div>
  );
});

export default HomePage;

stores/index.tsx

import React, { ReactNode, createContext, useContext } from "react";
import { enableStaticRendering } from "mobx-react";

import RootStore, { RootStoreHydration } from "./RootStore";

enableStaticRendering(typeof window === "undefined");

export let rootStore = new RootStore();

export const StoreContext = createContext<RootStore | undefined>(undefined);

export const useStore = () => {
  const context = useContext(StoreContext);
  if (context === undefined) {
    throw new Error("useRootStore must be used within RootStoreProvider");
  }

  return context;
};

function initializeStore(initialData?: RootStoreHydration): RootStore {
  const _store = rootStore ?? new RootStore();

  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 (!rootStore) rootStore = _store;

  return _store;
}

export function RootStoreProvider({
  children,
  hydrationData,
}: {
  children: ReactNode;
  hydrationData?: RootStoreHydration;
}) {
  const store = initializeStore(hydrationData);

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

Thanks in advance!

0 Answers
Related