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

Commit

Permalink
Enhancements:
Browse files Browse the repository at this point in the history
- Better error handling, now passing response (#6)
- Renamed host prop to base (#2)
  • Loading branch information
Tejas Kumar committed Jul 9, 2018
1 parent e3d144d commit 2dc755a
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 59 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ To install and use this library, simply `yarn add restful-react`, or `npm i rest

### Global Configuration

API endpoints usually sit alongside a host, global URL. As a convenience, the `RestfulProvider` allows top-level configuration of your requests, that are then passed down the React tree to `Get` components.
API endpoints usually sit alongside a base, global URL. As a convenience, the `RestfulProvider` allows top-level configuration of your requests, that are then passed down the React tree to `Get` components.

Consider,

Expand All @@ -63,7 +63,7 @@ import { RestfulProvider } from "restful-react";
import App from "./App.jsx";

const MyRestfulApp = () => (
<RestfulProvider host="https://dog.ceo/api">
<RestfulProvider base="https://dog.ceo/api">
<App />
</RestfulProvider>
);
Expand Down Expand Up @@ -96,7 +96,7 @@ Here's a full overview of the API available through the `RestfulProvider`, along
// Interface
interface RestfulProviderProps<T> {
/** The backend URL where the RESTful resources live. */
host: string;
base: string;
/**
* A function to resolve data return from the backend, most typically
* used when the backend response needs to be adapted in some way.
Expand All @@ -109,7 +109,7 @@ interface RestfulProviderProps<T> {
}

// Usage
<RestfulProvider host="String!" resolve={data => data} requestOptions={{}} />;
<RestfulProvider base="String!" resolve={data => data} requestOptions={{}} />;
```

Here's some docs about the [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request) type of request options.
Expand All @@ -119,15 +119,15 @@ Here's some docs about the [RequestInit](https://developer.mozilla.org/en-US/doc
`Get` components can be composed together and request URLs at an accumulation of their collective path props. Consider,

```jsx
// Assuming we're using a RestfulProvider with host={HOST} somewhere,
// Assuming we're using a RestfulProvider with base={HOST} somewhere,
<Get path="/cats">
{data => {
return (
<div>
<h1>Here are my cats!</h1>
{data.map(cat => <img alt={cat.name} src={cat.photoUrl} />)}

{/* Request HOST/cats/persian */}
{/* Request BASE/cats/persian */}
<Get path="/persian">
{persianCats => {
return (
Expand Down Expand Up @@ -207,7 +207,7 @@ const Movies = ({ dispatch }) => (
<li>
{movie.name}

{/* Will send a DELETE request to HOST/movies/:movie.id */}
{/* Will send a DELETE request to BASE/movies/:movie.id */}
<button
onClick={_ =>
actions
Expand Down Expand Up @@ -397,7 +397,7 @@ interface Poll<T> {
/**
* We can request foreign URLs with this prop.
*/
host?: GetComponentProps<T>["host"];
base?: GetComponentProps<T>["base"];
/**
* Any options to be passed to this request.
*/
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,16 @@
"preversion": "npm run build"
},
"lint-staged": {
".(md)": [
"*.md": [
"prettier --write",
"doctoc",
"git add"
],
".(tsx)": [
"*.json": [
"prettier --write",
"git add"
],
"*.(tsx|ts)": [
"tslint --fix",
"prettier --write",
"git add"
Expand Down
4 changes: 2 additions & 2 deletions src/Context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ResolveFunction } from ".";

export interface RestfulReactProviderProps<T = any> {
/** The backend URL where the RESTful resources live. */
host: string;
base: string;
/**
* A function to resolve data return from the backend, most typically
* used when the backend response needs to be adapted in some way.
Expand All @@ -17,7 +17,7 @@ export interface RestfulReactProviderProps<T = any> {
}

const { Provider, Consumer: RestfulReactConsumer } = React.createContext<RestfulReactProviderProps>({
host: "",
base: "",
resolve: (data: any) => data,
requestOptions: {},
});
Expand Down
18 changes: 8 additions & 10 deletions src/Poll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ interface PollProps<T> {
/**
* We can request foreign URLs with this prop.
*/
host?: GetComponentProps<T>["host"];
base?: GetComponentProps<T>["base"];
/**
* Any options to be passed to this request.
*/
Expand Down Expand Up @@ -156,16 +156,14 @@ class ContextlessPoll<T> extends React.Component<PollProps<T>, Readonly<PollStat
return;
}

console.log("scheduling feth");

// If we should keep going,
const { host, path, requestOptions, resolve, interval } = this.props;
const response = await fetch(`${host}${path}`, requestOptions);
const { base, path, requestOptions, resolve, interval } = this.props;
const response = await fetch(`${base}${path}`, requestOptions);

const responseBody =
response.headers.get("content-type") === "application/json" ? await response.json() : await response.text();

await this.setState(() => ({
this.setState(() => ({
loading: false,
lastResponse: response,
data: resolve ? resolve(responseBody) : responseBody,
Expand All @@ -183,7 +181,7 @@ class ContextlessPoll<T> extends React.Component<PollProps<T>, Readonly<PollStat

stop = async () => {
this.keepPolling = false;
await this.setState(() => ({ polling: false, finished: true })); // let everyone know we're done here.}
this.setState(() => ({ polling: false, finished: true })); // let everyone know we're done here.}
};

componentDidMount() {
Expand All @@ -204,11 +202,11 @@ class ContextlessPoll<T> extends React.Component<PollProps<T>, Readonly<PollStat

render() {
const { lastResponse: response, data, polling, loading, error, finished } = this.state;
const { children, host, path } = this.props;
const { children, base, path } = this.props;

const meta: Meta = {
response,
absolutePath: `${host}${path}`,
absolutePath: `${base}${path}`,
};

const states: States<T> = {
Expand All @@ -232,7 +230,7 @@ function Poll<T>(props: PollProps<T>) {
return (
<RestfulReactConsumer>
{contextProps => (
<RestfulProvider {...contextProps} host={`${contextProps.host}${props.path}`}>
<RestfulProvider {...contextProps} base={`${contextProps.base}${props.path}`}>
<ContextlessPoll
{...contextProps}
{...props}
Expand Down
88 changes: 51 additions & 37 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,10 @@ export interface GetComponentProps<T> {
/**
* An escape hatch and an alternative to `path` when you'd like
* to fetch from an entirely different URL..
*
* @deprecated Deprecated in favor of a `base` prop (https://github.com/contiamo/restful-react/issues/4)
*/
host?: string;
base?: string;
}

/**
Expand Down Expand Up @@ -122,15 +124,26 @@ class ContextlessGet<T> extends React.Component<GetComponentProps<T>, Readonly<G
}

componentDidUpdate(prevProps: GetComponentProps<T>) {
// If the path or host prop changes, refetch!
const { path, host } = this.props;
if (prevProps.path !== path || prevProps.host !== host) {
// If the path or base prop changes, refetch!
const { path, base } = this.props;
if (prevProps.path !== path || prevProps.base !== base) {
this.shouldFetchImmediately() && this.fetch("get")();
}
}

setError = (erroredResponse: Response) => {
this.setState(() => ({
response: erroredResponse,
error: erroredResponse.statusText,
loading: false,
}));
return null;
};

fetch = (method?: RequestMethod) => {
const { host, path, requestOptions: options } = this.props;
const { base, path, requestOptions: options } = this.props;
this.setState(() => ({ error: "", loading: true }));

switch (method) {
case "post":
case "put":
Expand All @@ -145,36 +158,38 @@ class ContextlessGet<T> extends React.Component<GetComponentProps<T>, Readonly<G
}
};

await this.setState(() => ({ loading: true }));
const response = await fetch(`${host}${path}`, {
...options,
...requestOptions,
headers: new Headers({
...(isJSON() && { "content-type": "application/json" }),
...(options || {}).headers,
...(requestOptions || {}).headers,
}),
method,
body: data,
});

if (!response.ok) {
await this.setState(() => ({ loading: false }));
throw response;
}
try {
const response = await fetch(`${base}${path}`, {
...options,
...requestOptions,
headers: new Headers({
...(isJSON() && { "content-type": "application/json" }),
...(options || {}).headers,
...(requestOptions || {}).headers,
}),
method,
body: data,
});

await this.setState(() => ({ loading: false }));
const responseData: Promise<T> =
response.headers.get("content-type") === "application/json" ? response.json() : response.text();
return responseData;
if (!response.ok) {
throw response;
}

this.setState(() => ({ loading: false }));
const responseData: Promise<T> =
response.headers.get("content-type") === "application/json" ? response.json() : response.text();
return responseData;
} catch (erroredResponse) {
return this.setError(erroredResponse);
}
};

default:
return async (requestPath?: string, requestOptions?: Partial<RequestInit>) => {
const { resolve } = this.props;
const foolProofResolve = resolve || (data => data);
try {
await this.setState({ loading: true });
const response = await fetch(`${host}${requestPath || path || ""}`, { ...options, ...requestOptions });
const response = await fetch(`${base}${requestPath || path || ""}`, { ...options, ...requestOptions });

if (!response.ok) {
throw `Failed to fetch: ${response.status}${response.statusText}`;
Expand All @@ -185,11 +200,10 @@ class ContextlessGet<T> extends React.Component<GetComponentProps<T>, Readonly<G
? await response.json()
: await response.text();

await this.setState({ data: foolProofResolve(data), loading: false });
this.setState({ data: foolProofResolve(data), loading: false });
return data;
} catch (error) {
await this.setState(() => ({ error, loading: false }));
return null;
} catch (erroredResponse) {
return this.setError(erroredResponse);
}
};
}
Expand All @@ -204,32 +218,32 @@ class ContextlessGet<T> extends React.Component<GetComponentProps<T>, Readonly<G
};

render() {
const { children, wait, path, host } = this.props;
const { children, wait, path, base } = this.props;
const { data, error, loading, response } = this.state;

if (wait && data === null) {
return <></>; // Show nothing until we have data.
}

return children(data, { loading, error }, this.actions, { response, absolutePath: `${host}${path}` });
return children(data, { loading, error }, this.actions, { response, absolutePath: `${base}${path}` });
}
}

/**
* The <Get /> component _with_ context.
* Context is used to compose path props,
* and to maintain the host property against
* and to maintain the base property against
* which all requests will be made.
*
* We compose Consumers immediately with providers
* in order to provide new `host` props that contain
* in order to provide new `base` props that contain
* a segment of the path, creating composable URLs.
*/
function Get<T>(props: GetComponentProps<T>) {
return (
<RestfulReactConsumer>
{contextProps => (
<RestfulProvider {...contextProps} host={`${contextProps.host}${props.path}`}>
<RestfulProvider {...contextProps} base={`${contextProps.base}${props.path}`}>
<ContextlessGet
{...contextProps}
{...props}
Expand Down

0 comments on commit 2dc755a

Please # to comment.