Skip to content

Commit

Permalink
Merge pull request #48 from risen228/feat/better-customize-and-serial…
Browse files Browse the repository at this point in the history
…ize-options

[Feature] Better customize, serialize options
  • Loading branch information
Evgeny Zakharov authored Sep 25, 2023
2 parents 721714d + c8a8b20 commit 2a27ae4
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 45 deletions.
64 changes: 50 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ export const createGIP = createGIPFactory({
// By default, the library just uses fork(), like below
// But you can fill the stores in scope with your values (cookies, for example)
createServerScope: () => fork(),

// The second argument in https://effector.dev/docs/api/effector/serialize
serializeOptions: { ... },

// You can define your custom logic using the "customize" function
// It's run after all events are settled but before Scope serialization
// So, here you can safely call allSettled
async customize({ scope, context }) {
// You can also return nothing (there will be no impact on props in this case)
return { /* Props */ };
},
});

/*
Expand All @@ -166,13 +177,8 @@ Page.getInitialProps = createGIP({
// - Client side on navigation (even if already called)
pageEvent: pageStarted,

// You can define your custom logic using the "customize" function
// It's run after all events are settled but before Scope serialization
// So, here you can safely call allSettled
async customize({ scope, context }) {
// You can also return nothing (there will be no impact on props in this case)
return { /* Props */ };
},
// The same as on factory level
async customize({ scope, context }) { ... },
});
```

Expand All @@ -198,6 +204,17 @@ export const pageStarted = createEvent<PageContext<Props, Params, Query>>();
export const createGSSP = createGSSPFactory({
// Will be called on the first request and each page navigation (always on the server side)
sharedEvents: [appStarted],

// The second argument in https://effector.dev/docs/api/effector/serialize
serializeOptions: { ... },

// You can define your custom logic using the "customize" function
// It's run after all events are settled but before Scope serialization
// So, here you can safely call allSettled
customize({ scope, context }) {
// You can omit the "props" field (there will be no impact on props in this case)
return { /* GSSP Result */ };
},
});

/*
Expand All @@ -209,13 +226,8 @@ export const getServerSideProps = createGSSP({
// Always called after shared events
pageEvent: pageStarted,

// You can define your custom logic using the "customize" function
// It's run after all events are settled but before Scope serialization
// So, here you can safely call allSettled
customize({ scope, context }) {
// You can omit the "props" field (there will be no impact on props in this case)
return { /* GSSP Result */ };
},
// The same as on factory level
customize({ scope, context }) { ... },
});
```

Expand Down Expand Up @@ -250,9 +262,13 @@ export const getStaticProps = createGSP({
// Will be called on each page generation (always on the server side)
pageEvent: pageStarted,

// The second argument in https://effector.dev/docs/api/effector/serialize
serializeOptions: { ... },

// You can define your custom logic using the "customize" function
// It's run after all events are settled but before Scope serialization
// So, here you can safely call allSettled
// Important: due to complex types, this method is not available on factory level like in GIP and GSSP
customize({ scope, context }) {
// You can omit the "props" field (there will be no impact on props in this case)
return { /* GSP Result */ };
Expand Down Expand Up @@ -398,6 +414,26 @@ The place depends on your architecture. But one thing is certain - **creating fa

For example, with [`Feature Sliced Design`](https://feature-sliced.design) you might consider creating a `layouts` layer, which can be used to create reusable page layouts and factories.

### How are multiple customize work?

GIP:

1. Execute factory-level `customize` and save result
2. Execute page-level `customize` and save result
3. Merge results (page-level priority is higher)
4. Merge `nextjs-effector` props inside and return the result from GIP

GSSP:

1. Execute factory-level `customize` and save result
2. It has `redirect` field? Return the result from GSSP
3. It has `notFound` field? Return the result from GSSP
4. Execute page-level `customize` and save result
5. It has `redirect` field? Return the result from GSSP
6. It has `notFound` field? Return the result from GSSP
7. Deep merge results (page-level priority is higher)
8. Merge `nextjs-effector` props inside and return the result from GSSP

### Why in GSSP are the shared events called on each request?

`getServerSideProps`, unlike the `getInitialProps`, is run only on the server side. The problem is that the `pageEvent` logic may depend on globally shared data. So, we need either to run shared events on each request (as we do now), or get this globally shared data in some other way, for example by sending it back from the client in a serialized form (sounds risky and hard).
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { allSettled, fork, Scope, serialize } from 'effector'
import { allSettled, fork, Scope, serialize, Store } from 'effector'
import { NextPageContext } from 'next'
import { INITIAL_STATE_KEY } from '../constants'
import { ContextNormalizers } from '../context-normalizers'
Expand All @@ -11,7 +11,9 @@ import { AnyProps, EmptyOrPageEvent, GetInitialProps } from '../types'
export interface CreateAppGIPConfig {
sharedEvents?: EmptyOrPageEvent[]
runSharedOnce?: boolean
createServerScope?: (context: NextPageContext) => Scope
createServerScope?: (context: NextPageContext) => Scope | Promise<Scope>
customize?: CustomizeGIP
serializeOptions?: Parameters<typeof serialize>[1]
}

export interface CustomizeGIPParams {
Expand All @@ -32,6 +34,8 @@ export function createGIPFactory({
sharedEvents = [],
runSharedOnce = true,
createServerScope = () => fork(),
customize: factoryCustomize,
serializeOptions
}: CreateAppGIPConfig = {}) {
/*
* When "runSharedOnce" is equals to "true",
Expand All @@ -44,7 +48,7 @@ export function createGIPFactory({

return function createGIP<P extends AnyProps = AnyProps>({
pageEvent,
customize,
customize: pageCustomize,
}: CreateGIPConfig<P> = {}): GetInitialProps<P> {
return async function getInitialProps(context) {
/*
Expand All @@ -59,7 +63,7 @@ export function createGIPFactory({

const normalizedContext = ContextNormalizers.getInitialProps(context)

const scope = state.clientScope ?? createServerScope(context)
const scope = state.clientScope ?? await createServerScope(context)

for (const event of events) {
await allSettled(event, { scope, params: normalizedContext })
Expand All @@ -73,22 +77,16 @@ export function createGIPFactory({
// eslint-disable-next-line require-atomic-updates
state.clientScope = scope
}

let initialProps = ({} as P)

/*
* Override with user's initial props when "customize" defined
*/
if (customize) {
const userProps = await customize({ scope, context })
if (userProps) initialProps = userProps
}

const factoryGipResult = await factoryCustomize?.({ scope, context }) ?? {} as P
const pageGipResult = await pageCustomize?.({ scope, context }) ?? {} as P
const initialProps: P = { ...factoryGipResult, ...pageGipResult }

/*
* Serialize after customize to include user operations
*/
const effectorProps = {
[INITIAL_STATE_KEY]: serialize(scope),
[INITIAL_STATE_KEY]: serialize(scope, serializeOptions),
}

return Object.assign(initialProps, effectorProps)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { AnyProps, EmptyOrPageEvent } from '../types'

export interface CreateAppGSSPConfig {
sharedEvents?: EmptyOrPageEvent<any, any>[]
createServerScope?: (context: GetServerSidePropsContext) => Scope
createServerScope?: (context: GetServerSidePropsContext) => Scope | Promise<Scope>
customize?: CustomizeGSSP
serializeOptions?: Parameters<typeof serialize>[1]
}

export interface CustomizeGSSPParams<
Expand Down Expand Up @@ -44,14 +46,16 @@ export interface CreateGSSPConfig<
export function createGSSPFactory({
sharedEvents = [],
createServerScope = () => fork(),
customize: factoryCustomize,
serializeOptions
}: CreateAppGSSPConfig = {}) {
return function createGSSP<
P extends AnyProps = AnyProps,
Q extends ParsedUrlQuery = ParsedUrlQuery,
D extends PreviewData = PreviewData
>({
pageEvent,
customize,
customize: pageCustomize,
}: CreateGSSPConfig<P, Q, D> = {}): GetServerSideProps<P, Q, D> {
return async function getServerSideProps(context) {
/*
Expand All @@ -61,34 +65,42 @@ export function createGSSPFactory({

const normalizedContext = ContextNormalizers.getServerSideProps(context)

const scope = createServerScope(context)
const scope = await createServerScope(context)

for (const event of events) {
await allSettled(event, { scope, params: normalizedContext })
}

let gsspResult: GetServerSidePropsResult<P> = { props: {} as P }
const factoryGsspResult = await factoryCustomize?.({ scope, context }) ?? { props: {} as P }

/*
* Override with user's GSSP result when "customize" defined
* Pass 404 and redirects as they are
*/
if (customize) {
const customGsspResult = await customize({ scope, context })
gsspResult = customGsspResult ?? { props: {} as P }
if ('redirect' in factoryGsspResult || 'notFound' in factoryGsspResult) {
return factoryGsspResult
}

const pageGsspResult = await pageCustomize?.({ scope, context }) ?? { props: {} as P }

/*
* Pass 404 and redirects as they are
*/
if ('redirect' in gsspResult || 'notFound' in gsspResult) {
return gsspResult
if ('redirect' in pageGsspResult || 'notFound' in pageGsspResult) {
return pageGsspResult
}

const gsspResult: GetServerSidePropsResult<P> = {
props: {
...factoryGsspResult.props,
...pageGsspResult.props
}
}

/*
* Serialize after customize to include user operations
*/
const effectorProps = {
[INITIAL_STATE_KEY]: serialize(scope),
[INITIAL_STATE_KEY]: serialize(scope, serializeOptions),
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { AnyProps, EmptyOrStaticPageEvent } from '../types'

export interface CreateAppGSPConfig {
sharedEvents?: EmptyOrStaticPageEvent<any, any>[]
createServerScope?: (context: GetStaticPropsContext) => Scope
createServerScope?: (context: GetStaticPropsContext) => Scope | Promise<Scope>
serializeOptions?: Parameters<typeof serialize>[1]
}

export interface CustomizeGSPParams<
Expand Down Expand Up @@ -46,6 +47,7 @@ export interface CreateGSPConfig<
export function createGSPFactory({
sharedEvents = [],
createServerScope = () => fork(),
serializeOptions
}: CreateAppGSPConfig = {}) {
return function createGSP<
P extends AnyProps = AnyProps,
Expand All @@ -64,7 +66,7 @@ export function createGSPFactory({

const normalizedContext = ContextNormalizers.getStaticProps(context)

const scope = createServerScope(context)
const scope = await createServerScope(context)

for (const event of events) {
await allSettled(event, { scope, params: normalizedContext })
Expand Down Expand Up @@ -93,7 +95,7 @@ export function createGSPFactory({
* Serialize after customize to include user operations
*/
const effectorProps = {
[INITIAL_STATE_KEY]: serialize(scope),
[INITIAL_STATE_KEY]: serialize(scope, serializeOptions),
}

/*
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ export type {
CustomizeGSPParams,
CustomizeGSSP,
CustomizeGSSPParams,
} from './fabrics'
} from './factories'
export {
createGIPFactory,
createGSPFactory,
createGSSPFactory,
} from './fabrics'
} from './factories'
export * from './types'
export { withEffector } from './with-effector'

1 comment on commit 2a27ae4

@vercel
Copy link

@vercel vercel bot commented on 2a27ae4 Sep 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nextjs-effector – ./

nextjs-effector-git-main-risen.vercel.app
nextjs-effector.vercel.app
nextjs-effector-risen.vercel.app

Please sign in to comment.