Skip to content

Commit fa60248

Browse files
committed
feat: Add beforeNext prop for step validation, enhance onStepChange with step context, and introduce reset function to useSteps.
1 parent 4a55dfb commit fa60248

3 files changed

Lines changed: 125 additions & 48 deletions

File tree

README.md

Lines changed: 123 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,29 @@ Example:
2626
import { Steps, StepsProvider, useSteps } from "react-step-builder";
2727

2828
const App = () => {
29-
return (
30-
<StepsProvider>
31-
<MySteps />
32-
</StepsProvider>
33-
);
29+
return (
30+
<StepsProvider>
31+
<MySteps />
32+
</StepsProvider>
33+
);
3434
};
3535

3636
const MySteps = () => {
37-
const { next, prev } = useSteps();
38-
39-
return (
40-
<Steps>
41-
<div>
42-
<h1>Step 1</h1>
43-
</div>
44-
<div>
45-
<h1>Step 2</h1>
46-
</div>
47-
<div>
48-
<h1>Step 3</h1>
49-
</div>
50-
</Steps>
51-
);
37+
const { next, prev } = useSteps();
38+
39+
return (
40+
<Steps>
41+
<div>
42+
<h1>Step 1</h1>
43+
</div>
44+
<div>
45+
<h1>Step 2</h1>
46+
</div>
47+
<div>
48+
<h1>Step 3</h1>
49+
</div>
50+
</Steps>
51+
);
5252
};
5353

5454
export default App;
@@ -64,28 +64,29 @@ A component whose each direct sibling is treated as a step. **Do not add anythin
6464

6565
```jsx
6666
<Steps>
67-
<Step1 />
68-
<Step2 />
69-
<NotAStep />
67+
<Step1 />
68+
<Step2 />
69+
<NotAStep />
7070
</Steps>
7171
```
7272

7373
✅ Correct:
7474

7575
```jsx
7676
<Steps>
77-
<Step1 />
78-
<Step2>
79-
<NotAStep />
80-
</Step2>
77+
<Step1 />
78+
<Step2>
79+
<NotAStep />
80+
</Step2>
8181
</Steps>
8282
```
8383

8484
This reason for this method is due to React's _composition over inheritance_ principle. It also allows you to manage your state easily in the parent component.
8585

86-
| Property | Type | Description |
87-
| -------------- | ------------ | ---------------------------------------------------------- |
88-
| `onStepChange` | `() => void` | Runs on every step change. Does not run on initial render. |
86+
| Property | Type | Description |
87+
| -------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
88+
| `onStepChange` | `(context?: { from: number; to: number }) => void` | Runs on every step change. Receives the previous and next step numbers. Does not run on initial render. |
89+
| `beforeNext` | `() => boolean \| Promise<boolean>` | A guard function called before moving to the next step. Return `false` to block navigation. Supports async validation. |
8990

9091
<br/>
9192
<hr />
@@ -99,18 +100,19 @@ A special hook that accesses the state of `<Steps />` component and exposes meth
99100

100101
These are the properties inside `stepsState` object.
101102

102-
| Property | Type | Description |
103-
| ---------- | ------------------------ | --------------------------------------------------- |
104-
| `total` | `number` | Total number of steps |
105-
| `current` | `number` | Current step number |
106-
| `progress` | `number` | Progress of the current step, value between 0 and 1 |
107-
| `next` | `() => void` | Function to move to the next step |
108-
| `prev` | `() => void` | Function to move to the previous step |
109-
| `jump` | `(step: number) => void` | Function to jump to the given step |
110-
| `isFirst` | `boolean` | If the step is the first |
111-
| `isLast` | `boolean` | If the step is the last |
112-
| `hasPrev` | `boolean` | If the step has any previous step |
113-
| `hasNext` | `boolean` | If the step has any next step |
103+
| Property | Type | Description |
104+
| ---------- | ------------------------ | -------------------------------------------------------------------------------------------------- |
105+
| `total` | `number` | Total number of steps |
106+
| `current` | `number` | Current step number |
107+
| `progress` | `number` | Progress of the current step, value between 0 and 1 |
108+
| `next` | `() => Promise<boolean>` | Move to the next step. Returns `true` if navigation succeeded, `false` if blocked by `beforeNext`. |
109+
| `prev` | `() => void` | Move to the previous step |
110+
| `jump` | `(step: number) => void` | Jump to the given step |
111+
| `reset` | `() => void` | Reset to the first step |
112+
| `isFirst` | `boolean` | If the step is the first |
113+
| `isLast` | `boolean` | If the step is the last |
114+
| `hasPrev` | `boolean` | If the step has any previous step |
115+
| `hasNext` | `boolean` | If the step has any next step |
114116

115117
<br/>
116118
<hr />
@@ -120,13 +122,88 @@ These are the properties inside `stepsState` object.
120122

121123
The component that renders `<Steps />` should be wrapped with `StepsProvider` component. `useSteps` can only be called in a component that is rendered in the DOM tree under `StepsProvider`.
122124

123-
| Property | Type | Description |
124-
| -------------- | ------------ | --------------------------------------- |
125-
| `startsFrom` | `number` | The default step number to be rendered. |
125+
| Property | Type | Description |
126+
| ------------ | -------- | --------------------------------------- |
127+
| `startsFrom` | `number` | The default step number to be rendered. |
126128

127129
> Step numbers start from 1 and goes up to the count of direct siblings given to the `Steps` component. If the number is out of range, first step is rendered by default.
128130
129131
<br />
130132
<hr />
131133
<br />
132-
Example project: https://codesandbox.io/s/react-step-builder-v3-5625v?file=/src/App.tsx
134+
135+
### Step Change Callback
136+
137+
`onStepChange` runs on every step change and optionally receives a context object with `from` and `to` step numbers.
138+
139+
```jsx
140+
const MySteps = () => {
141+
const { next, prev } = useSteps();
142+
143+
const handleStepChange = (context) => {
144+
console.log(`Moved from step ${context.from} to step ${context.to}`);
145+
};
146+
147+
return (
148+
<Steps onStepChange={handleStepChange}>
149+
<div>Step 1</div>
150+
<div>Step 2</div>
151+
</Steps>
152+
);
153+
};
154+
```
155+
156+
<br />
157+
<hr />
158+
<br />
159+
160+
### Step Validation
161+
162+
Use `beforeNext` to validate before allowing navigation to the next step. The function can be synchronous or asynchronous.
163+
164+
```jsx
165+
const MySteps = () => {
166+
const { next } = useSteps();
167+
168+
// Sync validation
169+
return (
170+
<Steps beforeNext={() => formIsValid()}>
171+
<div>Step 1</div>
172+
<div>Step 2</div>
173+
</Steps>
174+
);
175+
};
176+
```
177+
178+
```jsx
179+
// Async validation
180+
<Steps beforeNext={async () => {
181+
const isValid = await validateOnServer();
182+
return isValid;
183+
}}>
184+
```
185+
186+
`next()` returns a `Promise<boolean>` indicating whether navigation was allowed:
187+
188+
```jsx
189+
const handleNext = async () => {
190+
const moved = await next();
191+
if (!moved) {
192+
showValidationErrors();
193+
}
194+
};
195+
```
196+
197+
<br />
198+
<hr />
199+
<br />
200+
201+
### Reset
202+
203+
Use `reset()` to return to the first step:
204+
205+
```jsx
206+
const { reset } = useSteps();
207+
208+
<button onClick={reset}>Start Over</button>;
209+
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "react-step-builder",
33
"description": "Headless, unopinionated, multi-step interface builder.",
44
"author": "Samet Mutevelli <mutevellisamet@gmail.com> (https://sametmutevelli.com)",
5-
"version": "3.0.3",
5+
"version": "3.1.0",
66
"private": false,
77
"main": "dist/index.js",
88
"types": "dist/index.d.ts",

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const App = () => {
88

99
return (
1010
<div className="steps_wrapper">
11-
<h1>React Step Builder v3.0.3</h1>
11+
<h1>React Step Builder v3.1.0</h1>
1212
<StepsComponent onStepChange={onStepChange} startsFrom={1} />
1313
</div>
1414
);

0 commit comments

Comments
 (0)