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;
const initScope = initialData => fork({ values: initialData });
const 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 const 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 }>> = ({ 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.",
},
],
},
],
},
};