A RetroSearch Logo

Home - News ( United States | United Kingdom | Italy | Germany ) - Football scores

Search Query:

Showing content from https://github.com/effector/next/tree/v0.4.1 below:

GitHub - effector/next at v0.4.1

This is minimal compatibility layer for effector + Next.js - it only provides one special EffectorNext provider component, which allows to fully leverage effector's Fork API, while handling some special parts of Next.js SSR and SSG flow.

So far there are no plans to extend the API, e.g., towards better DX - there are already packages like nextjs-effector. This package aims only at technical nuances.

npm add effector effector-react @effector/next

Also, you can use Yarn or PNPM to install dependencies.

To serialize and transfer state of effector stores between the network boundaries all stores must have a Stable IDentifier - sid.

Sid's are added automatically via either built-in babel plugin or our experimental SWC plugin.

{
  "presets": ["next/babel"],
  "plugins": ["effector/babel-plugin"]
}

Read the docs

Read effector SWC plugin documentation

1. EffectorNext provider setup

Add provider to the pages/_app.tsx and provide it with server-side values

import { EffectorNext } from "@effector/next";

export default function App({ Component, pageProps }: AppProps) {
  return (
    <main>
      <EffectorNext values={pageProps.values}>
        <Layout>
          <Component />
        </Layout>
      </EffectorNext>
    </main>
  );
}

Notice, that EffectorNext should get serialized scope values via props.

2. Server-side computations

Start your computations in server handlers using Fork API

import { fork, allSettled, serialize } from "effector";

import { pageStarted } from "../src/my-page-model";

export async function getStaticProps() {
  const scope = fork();

  await allSettled(pageStarted, { scope, params });

  return {
    props: {
      // notice serialized effector's scope here!
      values: serialize(scope),
    },
  };
}

Notice, that serialized scope values are provided via the same page prop, which is used in the _app for values in EffectorNext.

You're all set. Just use effector's units anywhere in components code via useUnit from effector-react.

1. Setup EffectorNext provider as Client Component

New app directory considers all components as Server Components by default.

Because of that EffectorNext provider won't work as it is, as it uses client-only createContext API internally - you will get a compile error in Next.js

The official way to handle this - is to re-export such components as modules with "use client" directive.

To do so, create effector-provider.tsx file at the top level of your app directory and copy-paste following code from snippet there:

// app/effector-provider.tsx
"use client";

import type { ComponentProps } from "react";
import { EffectorNext } from "@effector/next";

export function EffectorAppNext({
  values,
  children,
}: ComponentProps<typeof EffectorNext>) {
  return <EffectorNext values={values}>{children}</EffectorNext>;
}

You should use this version of provider in the app directory from now on.

We will bundle the package using the "use client" directive once Server-Components are out of React Canary and there is more information about directive usage.

2. Setup provider in the Root Layout

To use client components with effector units anywhere in the tree - add EffectorAppNext provider (which was created at previous step) at your Root Layout

If you are using multiple Root Layouts - each one of them should also have the EffectorAppNext provider.

// app/layout.tsx
import { EffectorAppNext } from "project-root/app/effector-provider";

export function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <EffectorAppNext>{/* rest of the components tree */}</EffectorAppNext>
      </body>
    </html>
  );
}
3. Server-side computations

Server computations work in a similiar way to pages directory, but inside Server Components of the app pages.

In this case you will need to add the EffectorAppNext provider to the tree of this Server Component and provide it with serialized scope.

// app/some-path/page.tsx
import { EffectorAppNext } from "project-root/app/effector-provider";

export default async function Page() {
  const scope = fork();

  await allSettled(pageStarted, { scope, params });

  const values = serialize(scope);

  return (
    <EffectorAppNext values={values}>
      {/* rest of the components tree */}
    </EffectorAppNext>
  );
}

This will automatically render this subtree with effector's state and also will automatically "hydrate" client scope with new values.

You're all set. Just use effector's units anywhere in components code via useUnit from effector-react.

Most of effector dev-tools options require direct access to the scope of the app. At the client you can get current scope via getClientScope function, which will return Scope in the browser and null at the server.

Example of @effector/redux-devtools-adapter integration

import type { AppProps } from "next/app";
import { EffectorNext, getClientScope } from "@effector/next";
import { attachReduxDevTools } from "@effector/redux-devtools-adapter";

const clientScope = getClientScope();

if (clientScope) {
  /**
   * Notice, that we need to check for the client scope first
   *
   * It will be `null` at the server
   */
  attachReduxDevTools({
    scope: clientScope,
    name: "playground-app",
    trace: true,
  });
}

function App({
  Component,
  pageProps,
}: AppProps<{ values: Record<string, unknown> }>) {
  const { values } = pageProps;

  return (
    <EffectorNext values={values}>
      <Component />
    </EffectorNext>
  );
}

export default App;

There are a few special nuances of Next.js behaviour, that you need to consider.

Effector's serialize: "ignore" is not recommended

Effector's createStore has serialize setting, which allows you to either setup custom rule for store's value serialization or mark it as ignore to completely strip this value from serialization.

Normally in typical SSR application you could use it to calculate some server-only value at the server, use it for render, and then just ignore it when serializing.

// typical custom ssr example
// some-module.ts
export const $serverOnlyValue = createStore(null, { serialize: "ignore" });

// request handler

export async function renderApp(req) {
  const scope = fork();

  await allSettled(appStarted, { scope, params: req });

  // serialization boundaries
  const appContent = renderToString(
    // scope object can be used for the render directly
    <Provider value={scope}>
      <App />
    </Provider>
  );
  const stateScript = `<script>self.__STATE__ = ${serialize(scope)}</script>`; // does not contain value of `$serverOnlyValue`

  return htmlResponse(appContent, stateScript);
}

But with Next.js serialization boundary happens much earlier, before server renders even started.

It happens because of unique Next.js features like running page logic only at server even for client transitions.

This feature requires, that response of every server data-fetching function is serializable to json string.

That means, that using serialize: "ignore" will just hide this store value from server render too.

// some-module.ts
export const $serverOnlyValue = createStore(null, { serialize: "ignore" })

// some-component

export function Component() {
 const value = useUnit($serverOnlyValue)

 return value ? <>{value}<> : <>No value</>
}

// pages/some-page
export async function getServerSideProps(req) {
 const scope = fork()

  await allSettled(appStarted, { scope, params: req })

  // scope.getState($serverOnlyValue) is not null at this point

  return {
   props: {
    values: serialize(scope)
    // scope.getState($serverOnlyValue) is stripped off here :(
    // Component will always render "No value"
   }
  }
}

Because of that it is not recommended to use serialize: "ignore" if the store is somehow needed for render.

You can use custom serialization config instead

const $date = createStore<null | Date>(null, {
  serialize: {
    write: (dateOrNull) => (dateOrNull ? dateOrNull.toISOString() : dateOrNull),
    read: (isoStringOrNull) =>
      isoStringOrNull ? new Date(isoStringOrNull) : isoStringOrNull,
  },
});

Docs

ESM dependencies and library duplicates in the bundle

Since Next.js 12 ESM imports are prioritized over CommonJS imports. While CJS-only dependencies are still supported, it is not recommended to use them.

It may lead to duplicated instances of the library in the bundle, which in case of @effector/next or effector leads to weird bugs like missing context provider errors.

You can check for library duplicates in the bundle either automatically with statoscope.tech Webpack Plugin, which have special rule for this purpose.

You can also check it manually via Debug -> Sources -> Webpack -> _N_E -> node_modules tab in the browser developer tools. Duplicated modules will be presented here in both mjs and cjs kinds.

  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"

RetroSearch is an open source project built by @garambo | Open a GitHub Issue

Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo

HTML: 3.2 | Encoding: UTF-8 | Version: 0.7.4