Appearance
useStatefulRequest
The useStatefulRequest
is a React hook that let's you configure an HTTP request.
Usage
typescript
import { useStatefulRequest } from "saint-bernard";
const App = () => {
useStatefulRequest<Users>({
initialState: []
})
return (
<p>Data received.</p>
);
};
initialLoading
Sometimes, it is useful to start right away with a loading state that is true
. By default, whenever running the request
function, the loading
state is set to true
, and whenever the response from the server is received, it is set to false
. However, the initial loading state is always set to false
, unless you configure this property to be true
.
typescript
import { useStatefulRequest } from "saint-bernard";
const App = () => {
useStatefulRequest<Users>({
initialState: [],
initialLoading: true
})
return (
<p>Data received.</p>
);
};
loading
If you want to know when your request is running, and when it is not (meaning, the browser has received and decoded the request from the server), you can use the loading
state that is returned from the useStatefulRequest
hook.
typescript
import { useStatefulRequest } from "saint-bernard";
const App = () => {
const {
loading
} = useStatefulRequest<Users>({
initialState: []
})
if (loading) {
return (
<p>Please wait while we are fetching your data...</p>
);
}
return (
<p>Data received.</p>
);
};
setLoading
If for any reason you need to manually set the loading state yourself, you can use the setLoading
setter which is a simple Dispatch<SetStateAction<boolean>>
behind the scene (which is a useState
setter).
typescript
import { useStatefulRequest } from "saint-bernard";
const App = () => {
const {
setLoading
} = useStatefulRequest<Users>({
initialState: []
})
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 10_000);
}, [setLoading]);
return (
<p>Data received.</p>
);
};
INFO
The setLoading
function has a stable reference since it uses the useCallback
hook internally, some linter like eslint
might force you to add this function as the dependency of your useCallback
or useEffect
, it won't affect performance since your hooks won't run again thanks to this stable reference.
cancel
Canceling a request can be useful, especially when some request takes a long time, letting the user being able to cancel a request, or change his mind, is a great value added to the user experience. You can extract the cancel
function from the useStatefulRequest
hook and use it as a listener for your HTML elements.
typescript
import { useStatefulRequest } from "saint-bernard";
const App = () => {
const {
cancel
} = useStatefulRequest<Users>({
initialState: []
})
return (
<button onClick={cancel}>
Cancel the request
</button>
);
};
abortControllerRef
Behind the scene, the cancel
function simply calls the abortControllerRef.current.abort()
method on the AbortController
class that is stored as a MutableRefObject
. You can use it, reassign it if you want. We don't recommend using it directly since this is used internally by this library to cancel properly your requests.
typescript
import { useEffect } from "react";
import { useStatefulRequest } from "saint-bernard";
const App = () => {
const {
abortControllerRef
} = useStatefulRequest<Users>({
initialState: []
});
useEffect(() => {
abortControllerRef.current = new AbortController();
}, []);
return (
<p>Received data.</p>
);
};
request
This is the function that allow you to send the request. By default, when called, the useStatefulRequest
will not trigger any HTTP request until this function gets called. You can use any HTTP client of your choice, or even use a fake HTTP call to mock your API endpoints as long as you return either a State
or an ExpectedError
.
typescript
import { useEffect, useCallback } from "react";
import { ExpectedError, useStatefulRequest, GET } from "saint-bernard";
import { z } from "zod";
const usersSchema = z.array(z.object({
id: z.number()
}));
type Users = z.infer<typeof usersSchema>;
const App = () => {
const {
request
} = useStatefulRequest<Users>({
initialState: []
});
const getUsers = useCallback(() => {
request(async ({ signal }) => {
const response = await GET
.withUrl("https://jsonplaceholder.typicode.com/users")
.withHeader("Content-Type", "application/json")
.withSignal(signal)
.send();
if (!response.ok) {
return new ExpectedError("Bad response from the server.");
}
const json = await response.json();
const validation = usersSchema.safeParse(json);
if (!validation.success) {
return new ExpectedError("Malformed response from the server.");
}
return validation.data;
});
}, [request]);
useEffect(() => {
getUsers();
}, [getUsers]);
return (
<p>Received data.</p>
);
};
state
The state represent both the data and the errors as a union of all possible cases and takes the type that has been provided as a generic argument in order to reflect the correct union of all possible types.
This has been done in order to prevent impossible states to be possible, like displaying the state while the data hasn't arrived yet, or displaying the state while there is an error.
Several functions help you discriminate the data from the errors, and although they are not mandatory, we recommend you to use them in order to enhance your developer experience.
typescript
import { ExpectedError, useStatefulRequest, isError, match } from "saint-bernard";
import { z } from "zod";
const usersSchema = z.array(z.object({
id: z.number()
}));
type Users = z.infer<typeof usersSchema>;
const App = () => {
const {
state
} = useStatefulRequest<Users>({
initialState: []
});
if (isError(state)) {
return match(state, {
NetworkError: () => (
<p>There has been a network error</p>
),
CancelError: () => (
<p>The request has been canceled.</p>
),
UnexpectedError: error => (
<p>An unexpected error occurred: {error.message}.</p>
),
ExpectedError: error => (
<p>Error: {error.message}.</p>
)
});
}
return (
<p>There is {state.length} users.</p>
);
};
reset
Sometimes, it can be great to offer an alternative to retrying to run a request, instead it could be great to simply cancel everything and reset the state.
For that matter, you can use the reset
function. It essentially reset the state to its initial state, provided when creating the request.
typescript
import { useStatefulRequest } from "saint-bernard";
const App = () => {
const {
reset
} = useStatefulRequest<Users>({
initialState: []
});
return (
<button onClick={reset}>Reset</button>
);
};