Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/optional-initial-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@use-funnel/core': minor
---

feat(core): make `initial.context` optional when all context properties are optional

- `createFunnelSteps()` now defaults `TContext` to `{}` when no type argument is provided
- `useFunnel({ initial: { step: 'A' } })` is now valid when the context type has only optional properties (e.g. `{ foo?: string }` or `{}`)
7 changes: 1 addition & 6 deletions docs/src/pages/docs/context-guide.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ function MyFunnelApp() {
id: "how-to-define-step-contexts",
initial: {
step: "EmailInput",
context: {}
}
});

Expand Down Expand Up @@ -81,7 +80,6 @@ function MyFunnelApp() {
id: "how-to-define-step-contexts",
initial: {
step: "EmailInput",
context: {}
},
steps: {
EmailInput: { guard: EmailInput_guard },
Expand Down Expand Up @@ -112,7 +110,7 @@ function MyFunnelApp() {
You can create a <Keyword>step</Keyword> that changes a specific key of the initial <Keyword>context</Keyword> from optional to required when transitioning to the next <Keyword>step</Keyword> simply.

```tsx
declare function createFunnelSteps<FormState>(): {
declare function createFunnelSteps<FormState = {}>(): {
extends: (name: string | string[], options?: { requiredKeys: string | string[] }) => this;
build: () => Record<string, FunnelStepOption>;
}
Expand Down Expand Up @@ -147,7 +145,6 @@ function MyFunnelApp() {
id: "create-funnel-steps",
initial: {
step: "EmailInput",
context: {}
},
steps
});
Expand Down Expand Up @@ -190,7 +187,6 @@ function MyFunnelApp() {
},
initial: {
step: "EmailInput",
context: {}
}
});

Expand Down Expand Up @@ -235,7 +231,6 @@ function MyFunnelApp() {
},
initial: {
step: "EmailInput",
context: {}
}
});

Expand Down
7 changes: 1 addition & 6 deletions docs/src/pages/docs/context-guide.ko.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ function MyFunnelApp() {
id: "how-to-define-step-contexts",
initial: {
step: "이메일입력",
context: {}
}
});

Expand Down Expand Up @@ -81,7 +80,6 @@ function MyFunnelApp() {
id: "step-by-step",
initial: {
step: "이메일입력",
context: {}
},
steps: {
이메일입력: { guard: 이메일입력_guard },
Expand Down Expand Up @@ -112,7 +110,7 @@ function MyFunnelApp() {
간단하게 다음 <Keyword>step</Keyword>으로 넘어갈 때 초기 <Keyword>context</Keyword>의 특정 키를 선택(optional)에서 필수(required)로 변경하는 <Keyword>step</Keyword>을 만들 수 있어요.

```tsx
declare function createFunnelSteps<FormState>(): {
declare function createFunnelSteps<FormState = {}>(): {
extends: (name: string | string[], options?: { requiredKeys: string | string[] }) => this;
build: () => Record<string, FunnelStepOption>;
}
Expand Down Expand Up @@ -149,7 +147,6 @@ function MyFunnelApp() {
steps: steps,
initial: {
step: "이메일입력",
context: {}
}
});

Expand Down Expand Up @@ -191,7 +188,6 @@ function MyFunnelApp() {
},
initial: {
step: "이메일입력",
context: {}
}
});

Expand Down Expand Up @@ -238,7 +234,6 @@ function MyFunnelApp() {
},
initial: {
step: "이메일입력",
context: {}
}
});

Expand Down
1 change: 0 additions & 1 deletion docs/src/pages/docs/features.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ const funnel = useFunnel<{
id: "strongly-typed",
initial: {
step: "A",
context: {}
}
});

Expand Down
1 change: 0 additions & 1 deletion docs/src/pages/docs/features.ko.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ const funnel = useFunnel<{
id: "strongly-typed",
initial: {
step: "A",
context: {}
}
});

Expand Down
2 changes: 0 additions & 2 deletions docs/src/pages/docs/get-started.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ function MyFunnelApp() {
id: "my-funnel-app",
initial: {
step: "EmailInput",
context: {},
},
});
// ...
Expand All @@ -105,7 +104,6 @@ function MyFunnelApp() {
id: 'my-funnel-app',
initial: {
step: "EmailInput",
context: {},
},
});
// ...
Expand Down
2 changes: 0 additions & 2 deletions docs/src/pages/docs/get-started.ko.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ function MyFunnelApp() {
id: "my-funnel-app",
initial: {
step: "이메일입력",
context: {}
}
});
// ...
Expand All @@ -104,7 +103,6 @@ function MyFunnelApp() {
id: "my-funnel-app",
initial: {
step: "이메일입력",
context: {}
}
});
// ...
Expand Down
2 changes: 0 additions & 2 deletions docs/src/pages/docs/migration.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const funnel = useFunnel({
steps,
initial: {
step: 'A',
context: {}
}
});
```
Expand Down Expand Up @@ -83,7 +82,6 @@ const funnel = useFunnel({
steps,
initial: {
step: 'A',
context: {}
}
});

Expand Down
4 changes: 0 additions & 4 deletions docs/src/pages/docs/migration.ko.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ const funnel = useFunnel({
steps,
initial: {
step: 'A',
context: {}
}
});
```
Expand Down Expand Up @@ -83,7 +82,6 @@ const funnel = useFunnel({
steps,
initial: {
step: 'A',
context: {}
}
});

Expand Down Expand Up @@ -178,7 +176,6 @@ const funnel = useFunnel({
steps,
initial: {
step: intialStep as keyof typeof steps,
context: {}
}
});
```
Expand Down Expand Up @@ -215,7 +212,6 @@ const funnel = useFunnel({
steps,
initial: {
step: 'A',
context: {}
}
});

Expand Down
1 change: 0 additions & 1 deletion docs/src/pages/docs/overlay.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export const Example = () => {
id: "overlay-example",
initial: {
step: "SchoolInput",
context: {},
},
});

Expand Down
1 change: 0 additions & 1 deletion docs/src/pages/docs/overlay.ko.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export const Example = () => {
id: "overlay-example",
initial: {
step: "학교입력",
context: {},
}
});
return (
Expand Down
2 changes: 0 additions & 2 deletions docs/src/pages/docs/sub-funnel.en.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export function Example() {
id: 'main-funnel',
initial: {
step: 'A',
context: {},
},
});
return (
Expand Down Expand Up @@ -88,7 +87,6 @@ export function BFunnel({ a, onNext }: Props) {
id: 'b-funnel',
initial: {
step: 'B1',
context: {},
},
});
return (
Expand Down
2 changes: 0 additions & 2 deletions docs/src/pages/docs/sub-funnel.ko.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export function Example() {
id: "main-funnel",
initial: {
step: "A",
context: {}
}
});
return (
Expand Down Expand Up @@ -88,7 +87,6 @@ export function BFunnel({ a, onNext }: Props) {
id: "b-funnel",
initial: {
step: "B1",
context: {}
}
});
return (
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export type FunnelStateByContextMap<TStepContextMap extends AnyStepContextMap> =
[key in keyof TStepContextMap & string]: FunnelState<key, TStepContextMap[key]>;
}[keyof TStepContextMap & string];

export type FunnelInitialStateByContextMap<TStepContextMap extends AnyStepContextMap> = {
[key in keyof TStepContextMap & string]: Partial<TStepContextMap[key]> extends TStepContextMap[key]
? { step: key; context?: TStepContextMap[key] }
: { step: key; context: TStepContextMap[key] };
}[keyof TStepContextMap & string];

export type FunnelTransition<
TStepContextMap extends AnyStepContextMap,
TStepKey extends keyof TStepContextMap & string,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/stepBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@ class SimpleFunnelStepBuilder<
}
}

export function createFunnelSteps<TContext>() {
export function createFunnelSteps<TContext = {}>() {
return new SimpleFunnelStepBuilder<TContext>();
}
21 changes: 15 additions & 6 deletions packages/core/src/useFunnel.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo } from 'react';
import {
AnyFunnelState,
AnyStepContextMap,
FunnelHistory,
FunnelInitialStateByContextMap,
FunnelStateByContextMap,
FunnelStep,
FunnelStepByContextMap,
Expand All @@ -16,7 +16,7 @@ import { useStateStore, useStateSubscriberStore, useUpdatableRef } from './utils

export interface UseFunnelOptions<TStepContextMap extends AnyStepContextMap> {
id: string;
initial: FunnelStateByContextMap<TStepContextMap>;
initial: FunnelInitialStateByContextMap<TStepContextMap>;
steps?: {
[TStepName in keyof TStepContextMap]: FunnelStepOption<TStepContextMap[TStepName]>;
};
Expand Down Expand Up @@ -56,14 +56,23 @@ export function createUseFunnel<TRouteOption extends RouteOption, TFunnelOption
? Record<TStepKeys, TStepContext>
: _TStepContextMap = string extends keyof _TStepContextMap ? Record<TStepKeys, TStepContext> : _TStepContextMap,
>(options: UseFunnelOptions<TStepContextMap> & _TFunnelOption): UseFunnelResults<TStepContextMap, TRouteOption> {
const normalizedInitial = useMemo(
() =>
({
...options.initial,
context: options.initial.context ?? {},
}) as FunnelStateByContextMap<TStepContextMap>,
[options.initial],
);
const optionsRef = useUpdatableRef(options);
const normalizedInitialRef = useUpdatableRef(normalizedInitial);
const router = useFunnelRouter({
...optionsRef.current,
id: optionsRef.current.id,
initialState: optionsRef.current.initial,
initialState: normalizedInitial,
});
const currentState = (router.history[router.currentIndex] ??
options.initial) as FunnelStateByContextMap<TStepContextMap>;
normalizedInitial) as FunnelStateByContextMap<TStepContextMap>;
const currentStateRef = useUpdatableRef(currentState);

const cleanUpRef = useUpdatableRef(router.cleanup);
Expand Down Expand Up @@ -107,7 +116,7 @@ export function createUseFunnel<TRouteOption extends RouteOption, TFunnelOption
};
const context = parseStepContext(step, newContext);
return context == null
? optionsRef.current.initial
? normalizedInitialRef.current
: ({
step,
context,
Expand Down Expand Up @@ -135,7 +144,7 @@ export function createUseFunnel<TRouteOption extends RouteOption, TFunnelOption
const validContext = parseStepContext(currentState.step, currentState.context);
return {
...(validContext == null
? optionsRef.current.initial
? normalizedInitialRef.current
: {
step: currentState.step,
context: validContext,
Expand Down