Home

Awesome

Effector Next

Note: we recommend using @effector/next package.

A HOCs that brings Effector and Next.js together

npm version

Installation

npm install effector-next

or yarn

yarn add effector-next

effector-next requires effector, effector-react to be installed

effector/babel-plugin is necessary if you do not want to manually name the units

Settings

  1. To load the initial state on the server, you must attach withFork wrapper to your _document component

    <details> <summary>pages/_document.jsx</summary>
    import Document from "next/document";
    import { withFork } from "effector-next";
    
    const enhance = withFork({ debug: false });
    
    export default enhance(Document);
    
    </details>
  2. To get the initial state on the client and drop it into the application, you must attach withHydrate wrapper to your _app component

    <details> <summary>pages/_app.jsx</summary>
    import { withHydrate } from "effector-next";
    import App from "next/app";
    
    const enhance = withHydrate();
    
    export default enhance(App);
    
    </details>
  3. To bind events/stores on the server to the scope, add aliases from effector-react to effector-react/ssr in next.config.js

    <details> <summary>next.config.js</summary>
    const { withEffectorReactAliases } = require("effector-next/tools");
    
    const enhance = withEffectorReactAliases();
    
    module.exports = enhance({});
    
    </details>
  4. Replace imports from "effector" to "effector-next"

    - import { createEvent, forward } from "effector"
    + import { createEvent, forward } from "effector-next"
    
  5. Connect the effector/babel-plugin

    <details> <summary>.babelrc</summary>
    {
      "presets": ["next/babel"],
      "plugins": ["effector/babel-plugin"]
    }
    

    If you are using effector version > 21.3.0, you also need to configure the babel plugin:

    {
      "presets": ["next/babel"],
      "plugins": ["effector/babel-plugin", { "importName": ["effector-next"] }]
    }
    
    </details>
  6. Configure what event will be triggered when the page is requested from the server using withStart

    <details> <summary>pages/index.js</summary>
    import React from "react";
    import { withStart } from "effector-next";
    import { useStore } from "effector-react";
    
    import { pageLoaded } from "../model";
    
    const enhance = withStart(pageLoaded);
    
    function HomePage() {
      return (
        <div>
          <h1>Hello World</h1>
        </div>
      );
    }
    
    export default enhance(HomePage);
    
    </details>

Example

  1. Declare our model

    <details> <summary>models/index.js</summary>
    import { forward, createEvent, createStore, createEffect } from "effector-next";
    
    export const pageLoaded = createEvent();
    export const buttonClicked = createEvent();
    
    const effect = createEffect({
      handler(name) {
        return Promise.resolve({ name });
      },
    });
    
    export const $data = createStore(null);
    
    $data.on(effect.done, (_, { result }) => result);
    
    forward({
      from: pageLoaded.map(() => "nameFromPageLoaded"),
      to: effect,
    });
    
    forward({
      from: buttonClicked.map(() => "nameFromButtonClicked"),
      to: effect,
    });
    
    </details>
  2. Connect the page to the store (all units must be wrapped in hooks - this is necessary in order to associate units with scope on the server)

    <details> <summary>pages/index.jsx</summary>
    import React from "react";
    import { useStore, useEvent } from "effector-react";
    
    import { $data, buttonClicked } from "../models";
    
    export default function HomePage() {
      const data = useStore($data);
      const handleClick = useEvent(buttonClicked);
    
      return (
        <div>
          <h1>HomePage</h1>
          <h2>Store state: {JSON.stringify({ data })}</h2>
          <button onClick={handleClick}>click to change store state</button>
        </div>
      );
    }
    
    </details>
  3. Bind an event that will be called on the server when the page is requested

    <details> <summary>pages/index.jsx</summary>
    import React from "react";
    import { useStore, useEvent } from "effector-react";
    +import { withStart } from "effector-next";
    
    -import { $data, buttonClicked } from "../models";
    +import { $data, pageLoaded, buttonClicked } from "../models";
    
    +const enhance = withStart(pageLoaded);
    
    -export default function HomePage() {
    +function HomePage() {
      const data = useStore($data);
      const handleClick = useEvent(buttonClicked);
    
      return (
        <div>
          <h1>HomePage</h1>
          <h2>Store state: {JSON.stringify({ data })}</h2>
          <button onClick={handleClick}>click to change store state</button>
        </div>
      );
    }
    
    +export default enhance(HomePage);
    
    </details>

Configuration

The withFork accepts a config object as a parameter:

Server payload

When the unit passed to withStart is called, the object will be passed as a payload:

Release process

  1. Check out the draft release.
  2. All PRs should have correct labels and useful titles. You can review available labels here.
  3. Update labels for PRs and titles, next manually run the release drafter action to regenerate the draft release.
  4. Review the new version and press "Publish"
  5. If required check "Create discussion for this release"