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

Commit

Permalink
Merge pull request #16 from contiamo/fix/delete
Browse files Browse the repository at this point in the history
Fix error handling and resource deletion
  • Loading branch information
Tejas Kumar authored Aug 3, 2018
2 parents 6a0d85f + 6d9a648 commit 1df1c92
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 37 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"jest": "^23.1.0",
"lint-staged": "^7.2.0",
"prettier": "^1.13.5",
"rollup": "^0.61.2",
"rollup": "^0.63.5",
"rollup-plugin-typescript2": "^0.16.1",
"ts-jest": "^22.4.6",
"tslint": "^5.10.0",
Expand Down
32 changes: 20 additions & 12 deletions src/Get.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import RestfulReactProvider, { RestfulReactConsumer, RestfulReactProviderProps }
*/
export type ResolveFunction<T> = (data: any) => T;

export interface GetDataError<S> {
message: string;
data: S;
}

/**
* An enumeration of states that a fetchable
* view could possibly have.
*/
export interface States {
export interface States<S> {
/** Is our view currently loading? */
loading: boolean;
/** Do we have an error in the view? */
error?: string;
error?: GetComponentState<S>["error"];
}

/**
Expand All @@ -41,7 +46,7 @@ export interface Meta {
/**
* Props for the <Get /> component.
*/
export interface GetComponentProps<T = {}> {
export interface GetComponentProps<T = {}, S = {}> {
/**
* The path at which to request data,
* typically composed by parent Gets or the RestfulProvider.
Expand All @@ -54,7 +59,7 @@ export interface GetComponentProps<T = {}> {
* @param data - data returned from the request.
* @param actions - a key/value map of HTTP verbs, aliasing destroy to DELETE.
*/
children: (data: T | null, states: States, actions: Actions<T>, meta: Meta) => React.ReactNode;
children: (data: T | null, states: States<S>, actions: Actions<T>, meta: Meta) => React.ReactNode;
/** Options passed into the fetch call. */
requestOptions?: RestfulReactProviderProps["requestOptions"];
/**
Expand Down Expand Up @@ -85,10 +90,10 @@ export interface GetComponentProps<T = {}> {
* are implementation details and should be
* hidden from any consumers.
*/
export interface GetComponentState<T> {
export interface GetComponentState<T, S = {}> {
data: T | null;
response: Response | null;
error: string;
error: GetDataError<S> | null;
loading: boolean;
}

Expand All @@ -101,8 +106,8 @@ class ContextlessGet<T> extends React.Component<GetComponentProps<T>, Readonly<G
public readonly state: Readonly<GetComponentState<T>> = {
data: null, // Means we don't _yet_ have data.
response: null,
error: "",
loading: !this.props.lazy,
error: null,
};

public static defaultProps: Partial<GetComponentProps<{}>> = {
Expand Down Expand Up @@ -156,19 +161,22 @@ class ContextlessGet<T> extends React.Component<GetComponentProps<T>, Readonly<G

public fetch = async (requestPath?: string, thisRequestOptions?: RequestInit) => {
const { base, path, resolve } = this.props;
this.setState(() => ({ error: "", loading: true }));
this.setState(() => ({ error: null, loading: true }));

const request = new Request(`${base}${requestPath || path || ""}`, this.getRequestOptions(thisRequestOptions));
const response = await fetch(request);

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

if (!response.ok) {
this.setState({ loading: false, error: `Failed to fetch: ${response.status} ${response.statusText}` });
this.setState({
loading: false,
error: { message: `Failed to fetch: ${response.status} ${response.statusText}`, data },
});
throw response;
}

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

this.setState({ loading: false, data: resolve!(data) });
return data;
};
Expand Down
60 changes: 42 additions & 18 deletions src/Mutate.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import * as React from "react";
import RestfulReactProvider, { RestfulReactConsumer, RestfulReactProviderProps } from "./Context";
import { GetComponentState } from "./Get";

/**
* An enumeration of states that a fetchable
* view could possibly have.
*/
export interface States {
export interface States<T = {}> {
/** Is our view currently loading? */
loading: boolean;
/** Do we have an error in the view? */
error?: string;
error?: GetComponentState<T>["error"];
}

/**
Expand All @@ -26,41 +27,58 @@ export interface Meta {
/**
* Props for the <Mutate /> component.
*/
export interface MutateComponentProps {
export interface MutateComponentCommonProps {
/**
* The path at which to request data,
* typically composed by parents or the RestfulProvider.
*/
path: string;
path?: string;
/**
* What HTTP verb are we using?
*/
verb: "POST" | "PUT" | "PATCH" | "DELETE";
/**
* An escape hatch and an alternative to `path` when you'd like
* to fetch from an entirely different URL.
*
*/
base?: string;
/** Options passed into the fetch call. */
requestOptions?: RestfulReactProviderProps["requestOptions"];
}

export interface MutateComponentWithDelete extends MutateComponentCommonProps {
verb: "DELETE";
/**
* A function that recieves a mutation function, along with
* some metadata.
*
* @param actions - a key/value map of HTTP verbs, aliasing destroy to DELETE.
*/
children: (mutate: (body?: string | {}) => Promise<Response>, states: States, meta: Meta) => React.ReactNode;
children: (mutate: (resourceId?: string | {}) => Promise<Response>, states: States, meta: Meta) => React.ReactNode;
}

export interface MutateComponentWithOtherVerb extends MutateComponentCommonProps {
verb: "POST" | "PUT" | "PATCH";
/**
* An escape hatch and an alternative to `path` when you'd like
* to fetch from an entirely different URL.
* A function that recieves a mutation function, along with
* some metadata.
*
* @param actions - a key/value map of HTTP verbs, aliasing destroy to DELETE.
*/
base?: string;
/** Options passed into the fetch call. */
requestOptions?: RestfulReactProviderProps["requestOptions"];
children: (mutate: (body?: string | {}) => Promise<Response>, states: States, meta: Meta) => React.ReactNode;
}

export type MutateComponentProps = MutateComponentWithDelete | MutateComponentWithOtherVerb;

/**
* State for the <Mutate /> component. These
* are implementation details and should be
* hidden from any consumers.
*/
export interface MutateComponentState {
export interface MutateComponentState<S = {}> {
response: Response | null;
error: string;
error: GetComponentState<S>["error"];
loading: boolean;
}

Expand All @@ -73,15 +91,16 @@ class ContextlessMutate extends React.Component<MutateComponentProps, MutateComp
public readonly state: Readonly<MutateComponentState> = {
response: null,
loading: false,
error: "",
error: null,
};

public mutate = async (body?: string | {}, mutateRequestOptions?: RequestInit) => {
const { base, path, verb: method, requestOptions: providerRequestOptions } = this.props;
this.setState(() => ({ error: "", loading: true }));
const { base, path, verb, requestOptions: providerRequestOptions } = this.props;
this.setState(() => ({ error: null, loading: true }));

const request = new Request(`${base}${path || ""}`, {
method,
const requestPath = verb === "DELETE" ? `${base}${path || ""}/${body}` : `${base}${path || ""}`;
const request = new Request(requestPath, {
method: verb,
body: typeof body === "object" ? JSON.stringify(body) : body,
...(typeof providerRequestOptions === "function" ? providerRequestOptions() : providerRequestOptions),
...mutateRequestOptions,
Expand All @@ -93,10 +112,15 @@ class ContextlessMutate extends React.Component<MutateComponentProps, MutateComp
...(mutateRequestOptions ? mutateRequestOptions.headers : {}),
},
});

const response = await fetch(request);

if (!response.ok) {
this.setState({ loading: false, error: `Failed to fetch: ${response.status} ${response.statusText}` });
const responseData = await response.json();
this.setState({
loading: false,
error: { data: responseData, message: `Failed to fetch: ${response.status} ${response.statusText}` },
});
throw response;
}

Expand Down
7 changes: 4 additions & 3 deletions src/Poll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface States<T> {
/**
* Is there an error? What is it?
*/
error?: PollState<T>["error"];
error: PollState<T>["error"];
}

/**
Expand Down Expand Up @@ -129,7 +129,7 @@ interface PollState<T> {
/**
* Do we currently have an error?
*/
error?: GetComponentState<T>["error"];
error: GetComponentState<T>["error"];
/**
* Index of the last polled response.
*/
Expand All @@ -146,6 +146,7 @@ class ContextlessPoll<T> extends React.Component<PollProps<T>, Readonly<PollStat
lastResponse: null,
polling: !this.props.lazy,
finished: false,
error: null,
};

public static defaultProps = {
Expand Down Expand Up @@ -207,7 +208,7 @@ class ContextlessPoll<T> extends React.Component<PollProps<T>, Readonly<PollStat
response.headers.get("content-type") === "application/json" ? await response.json() : await response.text();

if (!this.isResponseOk(response)) {
const error = `${response.status} ${response.statusText}`;
const error = { message: `${response.status} ${response.statusText}`, data: response };
this.setState({ loading: false, lastResponse: response, data: responseBody, error });
throw new Error(`Failed to Poll: ${error}`);
}
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3436,9 +3436,9 @@ rollup-pluginutils@2.3.0:
estree-walker "^0.5.2"
micromatch "^2.3.11"

rollup@^0.61.2:
version "0.61.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.61.2.tgz#30a953736964683a5b8ea952b137a393d84e6302"
rollup@^0.63.5:
version "0.63.5"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.63.5.tgz#5543eecac9a1b83b7e1be598b5be84c9c0a089db"
dependencies:
"@types/estree" "0.0.39"
"@types/node" "*"
Expand Down

0 comments on commit 1df1c92

Please sign in to comment.