effector-next

A HOCs that brings Effector and Next.js together

Usage no npm install needed!

<script type="module">
  import effectorNext from 'https://cdn.skypack.dev/effector-next';
</script>

README

Effector Next

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

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

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

    next.config.js
    const { withEffectorReactAliases } = require("effector-next/tools");
    
    const enhance = withEffectorReactAliases();
    
    module.exports = enhance({});
    
  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

    .babelrc
    {
      "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"] }]
    }
    
  6. Configure what event will be triggered when the page is requested from the server using withStart

    pages/index.js
    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);
    

Example

  1. Declare our model

    models/index.js
    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,
    });
    
  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)

    pages/index.jsx
    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>
      );
    }
    
  3. Bind an event that will be called on the server when the page is requested

    pages/index.jsx
    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);
    

Configuration

The withFork accepts a config object as a parameter:

  • debug (optional, boolean) : enable debug logging
  • serializeIgnore (optional, array) : stores whose values ​​should not be sent to the client after serialization

Server payload

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

  • req : incoming request
  • res : serever response
  • cookies : parsed cookies
  • pathname : path section of URL
  • query : query string section of URL parsed as an object.

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"