Integrate Next.js with effector
To do this, we will use the native fork method to create a scope This hook does that and memoize our data. It also merges server and client states:
import { fork, Scope, serialize } from "effector";
import { useMemo } from "react";
let scope;
function initScope(initialData) {
return fork({ values: initialData });
}
function initializeScope(preloadedData) {
let _scope = scope ?? initScope(preloadedData);
// After navigating to a page with an initial scope state, merge that state
// with the current state in the scope, and create a new scope
if (preloadedData && scope) {
_scope = initScope({
...serialize(scope, { onlyChanges: true }),
...preloadedData,
});
// Reset the current scope
scope = undefined;
}
// For SSG and SSR always create a new scope
if (typeof window === "undefined") {
return _scope;
}
// Create the scope once in the client
if (!scope) {
scope = _scope;
}
return _scope;
}
export function useScope(initialState): Scope {
return useMemo(() => initializeScope(initialState), [initialState]);
}
In the app.tsx
file, we used our useScope
and wrap the application in a provider from effector-react/scope
.
import { AppProps } from "next/app";
import { FC } from "react";
import { Provider } from "effector-react/scope";
import { fork, Scope, serialize } from "effector";
import { useScope } from "../useScope"; // use your path to file
const App: FC<AppProps<{ initialState: InitialStateType }>> = ({ Component, pageProps }) => {
const scope = useScope(pageProps.initialState);
return (
<Provider key={scope?.graphite?.id || "0"} value={scope}>
<Component {...pageProps} />
</Provider>
);
};
export default App;
Next, you need to add .babelrc to the project root with the following content:
{
"plugins": [
[
"effector/babel-plugin", {
"reactSsr": true,
}
]
]
}
You also need to add an alias to next.config.js to avoid build errors, when under the hood it imports, then cjs then mjs files.
config.resolve.alias.effector = path.resolve(__dirname, "./node_modules/effector/effector.cjs.js");
config.resolve.alias["effector-react/ssr"] = path.resolve(
__dirname,
"./node_modules/effector-react/ssr.js",
);
config.resolve.alias["effector-react/scope"] = path.resolve(
__dirname,
"./node_modules/effector-react/scope.js",
);
config.resolve.alias["effector-react"] = path.resolve(
__dirname,
"./node_modules/effector-react/ssr.js",
);
For the convenience of development, we add the following rules to our eslint:
module.exports = {
root: true,
extends: [
"effector",
"plugin:effector/react",
"plugin:effector/scope",
"plugin:effector/future",
"plugin:effector/patronum",
"plugin:effector/recommended",
],
rules: {
"no-restricted-imports": [
"error",
{
paths: [
{
name: "effector-react",
message: "Please import from 'effector-react/scope' instead.",
},
],
},
],
},
};