Skip to content
This repository has been archived by the owner on Nov 2, 2023. It is now read-only.

feat(request-registry-react): Allow to provide a process function #6

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
18 changes: 17 additions & 1 deletion utils/request-registry-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Features:
## Getting started

```
npm install --save request-registry registry-request-mobx
npm install --save request-registry registry-request-react
```

## Api
Expand All @@ -21,6 +21,8 @@ npm install --save request-registry registry-request-mobx
The useGetEndPoint can be used to load ajax data and handling the loading state in the same component:

```jsx
import { useGetEndPoint } from "request-registry-react";

const UserDetails = props => {
const endpointState = useGetEndPoint(userEndpoint, { id: props.id });
if (endpointState.state !== "DONE") {
Expand All @@ -39,6 +41,8 @@ The useGetEndPointSuspendable can be used in combination with `React.Suspense` t
ajax data:

```jsx
import { useGetEndPointSuspendable } from "request-registry-react";

const UserDetails = props => {
const { name } = useGetEndPointSuspendable(userEndpoint, { id: props.id });
return <div>{name}</div>;
Expand All @@ -60,6 +64,18 @@ const UserDetailsContainer = () => {
The `useGetEndPointLazy` can be used to load ajax data and handling the loading state in the same component
exactly like `useGetEndPoint` however the request will only be executed on client side (if the window object exists)

### preprocessing ajax values

It is possible to process the result **only once** the ajax data changed by using an optional processing function:

```jsx
const name = useGetEndPoint(userEndpoint, { id: props.id }, endpointState => {
if (endpointState.hasValue) {
return endpointState.value.name;
}
});
```

### endpointState

The return value of `useGetEndPoint` can have the following states:
Expand Down
43 changes: 31 additions & 12 deletions utils/request-registry-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// This is a bridge to allow react components to use request-registry
//
import { EndpointGetFunction } from "request-registry";
import { useEffect, useRef, useReducer, useCallback } from "react";
import { useEffect, useRef, useReducer, useCallback, useMemo } from "react";

type EndpointKeys<
TEndpointGetFunction
Expand All @@ -18,10 +18,18 @@ type EndpointResult<
/**
* Get the data from the given endpoint and caches the result as long as the component is mounted
*/
export function useGetEndPoint<TEndpoint extends EndpointGetFunction<any, any>>(
export function useGetEndPoint<
TEndpoint extends EndpointGetFunction<any, any>,
TProcessorFN extends (
data: EndpointState<EndpointResult<TEndpoint>>
) => any = (
data: EndpointState<EndpointResult<TEndpoint>>
) => EndpointState<EndpointResult<TEndpoint>>
>(
endpoint: TEndpoint,
keys: EndpointKeys<TEndpoint>
) {
keys: EndpointKeys<TEndpoint>,
processFunction?: TProcessorFN
): ReturnType<TProcessorFN> {
// Allow effects to access the latest key values
const latestKeys = useRef(keys);
// Helper to start ajax loading
Expand Down Expand Up @@ -63,7 +71,9 @@ export function useGetEndPoint<TEndpoint extends EndpointGetFunction<any, any>>(
return result;
});
}, [endpoint, endpoint.getCacheKey(keys)]);
return endpointState;
return processFunction
? useMemo(() => processFunction(endpointState), [endpointState])
: endpointState;
}

// Suspense will unmount the component while it is loading
Expand Down Expand Up @@ -123,11 +133,17 @@ export function useGetEndPointSuspendable<
* Will be executed only client side in the Browser
*/
export function useGetEndPointLazy<
TEndpoint extends EndpointGetFunction<any, any>
TEndpoint extends EndpointGetFunction<any, any>,
TProcessorFN extends (
data: EndpointState<EndpointResult<TEndpoint>>
) => any = (
data: EndpointState<EndpointResult<TEndpoint>>
) => EndpointState<EndpointResult<TEndpoint>>
>(
endpoint: TEndpoint,
keys: EndpointKeys<TEndpoint>
): EndpointState<EndpointResult<TEndpoint>> {
keys: EndpointKeys<TEndpoint>,
processFunction?: TProcessorFN
): ReturnType<TProcessorFN> {
const serverSideState: EndpointState<EndpointResult<TEndpoint>> = {
busy: true,
value: undefined,
Expand All @@ -136,11 +152,13 @@ export function useGetEndPointLazy<
promise: new Promise(() => {})
};
return typeof window === "undefined"
? serverSideState
: useGetEndPoint(endpoint, keys);
? processFunction
? processFunction(serverSideState)
: serverSideState
: useGetEndPoint(endpoint, keys, processFunction);
}

type EndpointState<TResult> =
type EndpointState<TResult> = Readonly<
| {
busy: true;
value: undefined;
Expand Down Expand Up @@ -168,7 +186,8 @@ type EndpointState<TResult> =
state: "DONE";
hasData: true;
promise: Promise<TResult>;
};
}
>;

const initialEndpointState = <TEndpoint extends EndpointGetFunction<any, any>>(
initialLoad: () => Promise<EndpointResult<TEndpoint>>
Expand Down
122 changes: 121 additions & 1 deletion utils/request-registry-react/test/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import * as React from "react";
import { useGetEndPoint, useGetEndPointSuspendable } from "../src";
import {
useGetEndPoint,
useGetEndPointSuspendable,
useGetEndPointLazy
} from "../src";
import { createGetEndpoint } from "request-registry";
import {
mockEndpoint,
Expand Down Expand Up @@ -115,6 +119,31 @@ describe("request-registry-react", () => {
// Make sure that the slow request which ended later is not shown in the result:
expect(container.innerHTML).toEqual("<div>Fast</div>");
});

it("allows to use a process function", () => {
const userEndpoint = createGetEndpoint<
{ id: string },
{ name: string }
>({
url: ({ id }) => `/user/${id}`
});
mockEndpoint(userEndpoint, async () => ({ name: "Alex" }));
const UserDetails = (props: { id: string }) => {
const value = useGetEndPoint(
userEndpoint,
{
id: props.id
},
data => data.value
);
if (!value) {
return <div>loading</div>;
}
return <div>{value.name}</div>;
};
const { container } = render(<UserDetails id="4" />);
expect(container.innerHTML).toEqual("<div>loading</div>");
});
});
describe("useGetEndPointSuspendable", () => {
it("renders loading state", () => {
Expand Down Expand Up @@ -162,4 +191,95 @@ describe("request-registry-react", () => {
expect(container.innerHTML).toEqual("<div>Alex</div>");
});
});
describe("useGetEndPointLazy", () => {
it("renders loading state", () => {
const userEndpoint = createGetEndpoint<
{ id: string },
{ name: string }
>({
url: ({ id }) => `/user/${id}`
});
mockEndpoint(userEndpoint, async () => ({ name: "Alex" }));
const UserDetails = (props: { id: string }) => {
const endpointState = useGetEndPointLazy(userEndpoint, {
id: props.id
});
if (endpointState.state !== "DONE") {
return <div>loading</div>;
}
return <div>{endpointState.value.name}</div>;
};
const { container } = render(<UserDetails id="4" />);
expect(container.innerHTML).toEqual("<div>loading</div>");
});

it("renders load data", async () => {
const userEndpoint = createGetEndpoint<
{ id: string },
{ name: string }
>({
url: ({ id }) => `/user/${id}`
});
mockEndpoint(userEndpoint, async () => ({ name: "Alex" }));
const UserDetails = (props: { id: string }) => {
const endpointState = useGetEndPointLazy(userEndpoint, {
id: props.id
});
if (endpointState.state !== "DONE") {
return <div>loading</div>;
}
return <div>{endpointState.value.name}</div>;
};
const { container } = render(<UserDetails id="4" />);
await userEndpoint({ id: "4" });

expect(container.innerHTML).toEqual("<div>Alex</div>");
});

it("rerenders data if cache is invalidated", async () => {
const runsEndpoint = createGetEndpoint<{}, { run: number }>({
url: () => `/runs`
});
let runs = 0;
mockEndpoint(runsEndpoint, async () => ({ run: runs++ }));
const Runs = () => {
const endpointState = useGetEndPointLazy(runsEndpoint, {});
if (endpointState.state !== "DONE") {
return <div>loading</div>;
}
return <div>{endpointState.value.run}</div>;
};
const { container } = render(<Runs />);
await wait();
expect(container.innerHTML).toEqual("<div>0</div>");
runsEndpoint.refresh();
await wait();
expect(container.innerHTML).toEqual("<div>1</div>");
});

it("allows to use a process function", () => {
const userEndpoint = createGetEndpoint<
{ id: string },
{ name: string }
>({
url: ({ id }) => `/user/${id}`
});
mockEndpoint(userEndpoint, async () => ({ name: "Alex" }));
const UserDetails = (props: { id: string }) => {
const value = useGetEndPointLazy(
userEndpoint,
{
id: props.id
},
data => data.value
);
if (!value) {
return <div>loading</div>;
}
return <div>{value.name}</div>;
};
const { container } = render(<UserDetails id="4" />);
expect(container.innerHTML).toEqual("<div>loading</div>");
});
});
});
2 changes: 1 addition & 1 deletion utils/request-registry-react/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"include": ["src", "types"],
"include": ["src", "types", "test"],
"compilerOptions": {
"target": "es5",
"module": "esnext",
Expand Down