Skip to content

Instantly share code, notes, and snippets.

@isBatak
Last active February 20, 2020 08:44
Show Gist options
  • Save isBatak/883b5ae602146848c04f8544a33c368b to your computer and use it in GitHub Desktop.
Save isBatak/883b5ae602146848c04f8544a33c368b to your computer and use it in GitHub Desktop.
Next.js withDatx
// utils/env.ts
export const isBrowser = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
export const isServer = !isBrowser;
// stores/index.ts
import { isServer } from '../utils/env';
import { AppStore } from './AppStore';
const __NEXT_MOBX_STORE__ = 'store';
export function getOrInitializeStore(initialState?: object): AppStore {
// Always make a new store if on the server, otherwise the state is shared between requests
if (isServer) {
return new AppStore(initialState);
}
// This will be true if the page constructor is called on the client
if (!window[__NEXT_MOBX_STORE__]) {
window[__NEXT_MOBX_STORE__] = new AppStore(initialState);
}
return window[__NEXT_MOBX_STORE__];
}
import React from 'react';
import { useObserver } from 'mobx-react';
import { withDatx, useStore } from '../lib/with-datx';
const IndexPage = () => {
const store = useStore();
const data = store.findAll(...);
return useObserver(() => (
<div>
{data.map(...)}
</div>
)
}
IndexPage.getInitialProps = async ({ store }) => {
await store.fetchAll(...);
return {}
}
export default withDatx(IndexPage)
// lib/with-datx.tsx
import { NextPage, NextPageContext } from 'next';
import App from 'next/app';
import React from 'react';
import { getOrInitializeStore } from '../stores';
import { AppStore } from '../stores/AppStore';
import '../utils/datx-config';
const StoreContext = React.createContext<AppStore | null>(null)
export const useStore = () => {
const store = React.useContext(storeContext)
if (!store) {
// this is especially useful in TypeScript so you don't need to be checking for null all the time
throw new Error('useStore must be used within a withDatx hoc.')
}
return store
}
// Extend NextPage type's context with 'store' (which is of type AppStore)
export type NextPageWithDatx<P = {}, IP = P> = NextPage<P, IP> & {
getInitialProps?: (ctx: NextPageContext & { store: AppStore }) => Promise<IP>;
};
// Inspired by https://github.com/zeit/next.js/tree/canary/examples/with-redux
export const withDatx = (PageComponent: NextPageWithDatx, options: { ssr: boolean } = { ssr: true }) => {
const WithDatx = ({ initialMobxState, ...props }) => {
const store: AppStore = getOrInitializeStore(initialMobxState);
return (
<StoreContext.Provider value={store}>
<PageComponent {...props} />
</StoreContext.Provider>
);
};
// Make sure people don't use this HOC on _app.js level
if (process.env.NODE_ENV !== 'production') {
const isAppHoc = PageComponent.prototype instanceof App;
if (isAppHoc) {
throw new Error('The withDatx HOC only works with PageComponents');
}
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName = PageComponent.displayName || PageComponent.name || 'Component';
WithDatx.displayName = `withDatx(${displayName})`;
}
const { ssr = true } = options;
if (ssr || PageComponent.getInitialProps) {
WithDatx.getInitialProps = async (context: NextPageContext) => {
// Get or Create the store with `undefined` as initialState
// This allows you to set a custom default initialState
const store = getOrInitializeStore();
// Provide the store to getInitialProps of pages
// Run getInitialProps from HOCed PageComponent
const pageProps: any =
typeof PageComponent.getInitialProps === 'function'
? await PageComponent.getInitialProps({ ...context, store })
: {};
// Pass props to PageComponent
return {
...pageProps,
initialMobxState: store.snapshot,
};
};
}
return WithDatx;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment