Skip to content

Commit c09b971

Browse files
author
dannyb
committed
V2.0.0 - prod stable - fits to nextjs production build regarding errors
1 parent 7a076af commit c09b971

File tree

5 files changed

+2981
-2939
lines changed

5 files changed

+2981
-2939
lines changed

package.json

+35-35
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
{
2-
"version": "1.2.3",
3-
"name": "next-server-action-hook",
4-
"description": "A React hook for handling Next.js server actions on the client side with built-in loading and error states.",
5-
"author": "Danny Braunstein",
6-
"license": "MIT",
7-
"scripts": {
8-
"test": "jest",
9-
"build": "tsc"
10-
},
11-
"dependencies": {
12-
"@babel/preset-typescript": "^7.23.3",
13-
"@testing-library/react-hooks": "^8.0.1",
14-
"@types/jest": "^29.5.11",
15-
"@types/testing-library__react-hooks": "^4.0.0",
16-
"react": "^18.2.0",
17-
"react-dom": "^18.2.0",
18-
"ts-jest": "^29.1.2"
19-
},
20-
"devDependencies": {
21-
"jest": "^29.7.0",
22-
"jest-environment-jsdom": "^29.7.0",
23-
"react-test-renderer": "^18.2.0",
24-
"typescript": "^5.3.3"
25-
},
26-
"repository": {
27-
"type": "git",
28-
"url": "https://github.com/dannyblv/next-server-action-hook.git"
29-
},
30-
"main": "dist/index.js",
31-
"types": "dist/index.d.ts",
32-
"files": [
33-
"/dist/*"
34-
]
35-
}
1+
{
2+
"version": "2.0.0",
3+
"name": "next-server-action-hook",
4+
"description": "A React hook for handling Next.js server actions on the client side with built-in loading and error states.",
5+
"author": "Danny Braunstein",
6+
"license": "MIT",
7+
"scripts": {
8+
"test": "jest",
9+
"build": "tsc"
10+
},
11+
"dependencies": {
12+
"@babel/preset-typescript": "^7.24.7",
13+
"@testing-library/react-hooks": "^8.0.1",
14+
"@types/jest": "^29.5.12",
15+
"@types/testing-library__react-hooks": "^4.0.0",
16+
"react": "^18.3.1",
17+
"react-dom": "^18.3.1",
18+
"ts-jest": "^29.2.3"
19+
},
20+
"devDependencies": {
21+
"jest": "^29.7.0",
22+
"jest-environment-jsdom": "^29.7.0",
23+
"react-test-renderer": "^18.3.1",
24+
"typescript": "^5.5.4"
25+
},
26+
"repository": {
27+
"type": "git",
28+
"url": "https://github.com/dannyblv/next-server-action-hook.git"
29+
},
30+
"main": "dist/index.js",
31+
"types": "dist/index.d.ts",
32+
"files": [
33+
"/dist/*"
34+
]
35+
}

readme.md

+110-107
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,111 @@
1-
# Next.js Server Action Hook
2-
<img src="https://github.com/dannyblv/next-server-action-hook/actions/workflows/node.js.yml/badge.svg" alt="CI status" /> <a href="https://www.npmjs.com/package/next-server-action-hook" title="View this project on NPM"><img src="https://img.shields.io/npm/v/next-server-action-hook" alt="NPM version" /></a> <img src="https://img.shields.io/npm/dw/next-server-action-hook" alt="Weekly downloads" />
3-
4-
This package offers a React hook for managing server actions in a Next.js client-side environment. It leverages the useTransition hook for efficient loading state and error management.
5-
6-
## Playground
7-
https://codesandbox.io/p/devbox/next-js-server-action-hook-y32wh8?file=%2Fapp%2Fform.tsx%3A20%2C26
8-
9-
## Installation
10-
```bash
11-
npm install next-server-action-hook
12-
```
13-
or
14-
```bash
15-
yarn add next-server-action-hook
16-
```
17-
18-
## Usage
19-
Showcase of handling a form submission with a server action
20-
```ts
21-
// page.ts
22-
import Form from "./form";
23-
24-
const FormPage = () => {
25-
const handleSubmit = async (formData: FormData) => {
26-
"use server";
27-
28-
const name = formData.get("name");
29-
30-
// validation and error example
31-
if (!name) {
32-
throw new Error("Name is required");
33-
}
34-
35-
// your spot to handle the server stuff ...
36-
return name as string;
37-
};
38-
39-
return <Form action={handleSubmit} />;
40-
};
41-
42-
export default FormPage;
43-
```
44-
45-
```ts
46-
// form.tsx (client)
47-
"use client";
48-
import useServerAction from "next-server-action-hook";
49-
50-
const Form = ({
51-
action,
52-
}: {
53-
action: (formData: FormData) => Promise<string>;
54-
}) => {
55-
const [run, { error, isLoading, data: name }, clearError] =
56-
useServerAction(action);
57-
58-
return (
59-
<>
60-
{isLoading && <div>Loading...</div>}
61-
{error && <div>{error.message}</div>}
62-
{name && <div>Hey {name}!</div>}
63-
64-
<h1>Form</h1>
65-
66-
<form action={run}>
67-
<label htmlFor="name">Name:</label>
68-
<input
69-
type="text"
70-
id="name"
71-
name="name"
72-
onChange={() => clearError()}
73-
/>
74-
<button type="submit">Submit</button>
75-
</form>
76-
</>
77-
);
78-
};
79-
80-
export default Form;
81-
```
82-
83-
In the given example, `useServerAction` is utilized to manage the `handleSubmit` server action.
84-
The `run` function, when invoked it initiates the states `isLoading`, `error`, and `data` - are dynamically updated based on the status and outcome of the promise operation,
85-
**providing real-time feedback that can be used to control the rendering of the component.**
86-
87-
## API
88-
89-
```ts
90-
useServerAction(action: () => Promise<any>): [
91-
run: (...args: any[]) => Promise<{ data?: any; error?: any }>,
92-
state: { isLoading: boolean; error?: any; data?: any },
93-
clearError: () => void
94-
]
95-
```
96-
97-
- `action`: The server action to handle. This should be a function that returns a Promise.
98-
- `run`: A function that calls the server action with the provided arguments and returns a Promise that resolves to an object with data and error properties.
99-
- `state`: An object with `isLoading`, `error`, and `data` properties.
100-
- `clearError`: A function that clears the error state.
101-
102-
## Updates
103-
- to v1.2.0 breaking
104-
- `loading` is now `isLoading`.
105-
- `clearError` is now the 3rd item in the returned array.
106-
107-
## License
1+
# Next.js Server Action Hook
2+
<img src="https://github.com/dannyblv/next-server-action-hook/actions/workflows/node.js.yml/badge.svg" alt="CI status" /> <a href="https://www.npmjs.com/package/next-server-action-hook" title="View this project on NPM"><img src="https://img.shields.io/npm/v/next-server-action-hook" alt="NPM version" /></a> <img src="https://img.shields.io/npm/dw/next-server-action-hook" alt="Weekly downloads" />
3+
4+
This package offers a React hook for managing server actions in a Next.js client-side environment. It leverages the useTransition hook for efficient loading state and error management.
5+
6+
## Playground
7+
https://codesandbox.io/p/devbox/next-js-server-action-hook-y32wh8?file=%2Fapp%2Fform.tsx%3A20%2C26
8+
9+
## Installation
10+
```bash
11+
npm install next-server-action-hook
12+
```
13+
or
14+
```bash
15+
yarn add next-server-action-hook
16+
```
17+
18+
## Usage
19+
Showcase of handling a form submission with a server action
20+
```ts
21+
// page.ts
22+
import Form from "./form";
23+
24+
const FormPage = () => {
25+
const handleSubmit = async (formData: FormData) => {
26+
"use server";
27+
28+
const name = formData.get("name");
29+
30+
// validation and error example
31+
if (!name) {
32+
throw new Error("Name is required");
33+
}
34+
35+
// your spot to handle the server stuff ...
36+
return name as string;
37+
};
38+
39+
return <Form action={handleSubmit} />;
40+
};
41+
42+
export default FormPage;
43+
```
44+
45+
```ts
46+
// form.tsx (client)
47+
"use client";
48+
import useServerAction from "next-server-action-hook";
49+
50+
const Form = ({
51+
action,
52+
}: {
53+
action: (formData: FormData) => Promise<string>;
54+
}) => {
55+
const [run, { error, hasError, data: name }, clearError] =
56+
useServerAction(action);
57+
58+
return (
59+
<>
60+
{isLoading && <div>Loading...</div>}
61+
{hasError && <div>Ooops something went wrong</div>}
62+
{name && <div>Hey {name}!</div>}
63+
64+
<h1>Form</h1>
65+
66+
<form action={run}>
67+
<label htmlFor="name">Name:</label>
68+
<input
69+
type="text"
70+
id="name"
71+
name="name"
72+
onChange={() => clearError()}
73+
/>
74+
<button type="submit">Submit</button>
75+
</form>
76+
</>
77+
);
78+
};
79+
80+
export default Form;
81+
```
82+
83+
In the given example, `useServerAction` is utilized to manage the `handleSubmit` server action.
84+
The `run` function, when invoked it initiates the states `isLoading`, `hasError`, and `data` - are dynamically updated based on the status and outcome of the promise operation,
85+
**providing real-time feedback that can be used to control the rendering of the component.**
86+
87+
## API
88+
89+
```ts
90+
useServerAction(action: () => Promise<any>): [
91+
run: (...args: any[]) => Promise<{ data?: any; }>,
92+
state: { isLoading: boolean; hasError?: boolean; data?: any },
93+
clearError: () => void
94+
]
95+
```
96+
97+
- `action`: The server action to handle. This should be a function that returns a Promise.
98+
- `run`: A function that calls the server action with the provided arguments and returns a Promise that resolves to an object with data property.
99+
- `state`: An object with `isLoading`, `hasError`, and `data` properties.
100+
- `clearError`: A function that clears the error state.
101+
102+
## Updates
103+
- to v2.0.0 breaking
104+
- `run` now returns an object with a `data` property.
105+
- as nextjs production build doesn't expose the error object, the `hasError` property is now used to determine if an error occurred.
106+
- to v1.2.0 breaking
107+
- `loading` is now `isLoading`.
108+
- `clearError` is now the 3rd item in the returned array.
109+
110+
## License
108111
MIT

src/index.test.ts

+54-54
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,54 @@
1-
import { renderHook, act } from '@testing-library/react-hooks';
2-
import useServerAction from './';
3-
4-
describe('useServerAction', () => {
5-
it('handles successful server action (Happy path)', async () => {
6-
const mockAction = jest.fn().mockResolvedValue('mockData');
7-
const { result, waitForNextUpdate } = renderHook(() => useServerAction(mockAction));
8-
9-
await act(async () => {
10-
result.current[0]();
11-
await waitForNextUpdate();
12-
});
13-
14-
expect(result.current[1].isLoading).toBe(false);
15-
expect(mockAction).toHaveBeenCalled();
16-
expect(result.current[1].data).toBe('mockData');
17-
expect(result.current[1].error).toBeUndefined();
18-
});
19-
20-
it('handles server action error', async () => {
21-
const mockError = new Error('Mock Error');
22-
const mockAction = jest.fn().mockRejectedValue(mockError);
23-
const { result, waitForNextUpdate } = renderHook(() => useServerAction(mockAction));
24-
25-
await act(async () => {
26-
result.current[0]();
27-
await waitForNextUpdate();
28-
});
29-
30-
expect(result.current[1].isLoading).toBe(false);
31-
expect(mockAction).toHaveBeenCalled();
32-
expect(result.current[1].error).toBe(mockError);
33-
expect(result.current[1].data).toBeUndefined();
34-
});
35-
36-
it('clears error', async () => {
37-
const mockError = new Error('Mock Error');
38-
const mockAction = jest.fn().mockRejectedValue(mockError);
39-
const { result, waitForNextUpdate } = renderHook(() => useServerAction(mockAction));
40-
41-
await act(async () => {
42-
result.current[0]();
43-
await waitForNextUpdate();
44-
});
45-
46-
expect(result.current[1].error).toBe(mockError);
47-
48-
act(() => {
49-
result.current[2]();
50-
});
51-
52-
expect(result.current[1].error).toBeUndefined();
53-
});
54-
});
1+
import { renderHook, act } from '@testing-library/react-hooks';
2+
import useServerAction from './';
3+
4+
describe('useServerAction', () => {
5+
it('handles successful server action (Happy path)', async () => {
6+
const mockAction = jest.fn().mockResolvedValue('mockData');
7+
const { result, waitForNextUpdate } = renderHook(() => useServerAction(mockAction));
8+
9+
await act(async () => {
10+
result.current[0]();
11+
await waitForNextUpdate();
12+
});
13+
14+
expect(result.current[1].isLoading).toBe(false);
15+
expect(mockAction).toHaveBeenCalled();
16+
expect(result.current[1].data).toBe('mockData');
17+
expect(result.current[1].hasError).toBeFalsy();
18+
});
19+
20+
it('handles server action error', async () => {
21+
const mockError = new Error('Mock Error');
22+
const mockAction = jest.fn().mockRejectedValue(mockError);
23+
const { result, waitForNextUpdate } = renderHook(() => useServerAction(mockAction));
24+
25+
await act(async () => {
26+
result.current[0]();
27+
await waitForNextUpdate();
28+
});
29+
30+
expect(result.current[1].isLoading).toBe(false);
31+
expect(mockAction).toHaveBeenCalled();
32+
expect(result.current[1].hasError).toBeTruthy();
33+
expect(result.current[1].data).toBeUndefined();
34+
});
35+
36+
it('clears error', async () => {
37+
const mockError = new Error('Mock Error');
38+
const mockAction = jest.fn().mockRejectedValue(mockError);
39+
const { result, waitForNextUpdate } = renderHook(() => useServerAction(mockAction));
40+
41+
await act(async () => {
42+
result.current[0]();
43+
await waitForNextUpdate();
44+
});
45+
46+
expect(result.current[1].hasError).toBeTruthy();
47+
48+
act(() => {
49+
result.current[2]();
50+
});
51+
52+
expect(result.current[1].hasError).toBeFalsy();
53+
});
54+
});

0 commit comments

Comments
 (0)