Giter Site home page Giter Site logo

contiamo / restful-react Goto Github PK

View Code? Open in Web Editor NEW
1.9K 20.0 109.0 9.5 MB

A consistent, declarative way of interacting with RESTful backends, featuring code-generation from Swagger and OpenAPI specs 🔥

License: MIT License

TypeScript 99.32% JavaScript 0.54% Handlebars 0.14%
openapi openapi3 swagger rest react typescript

restful-react's Introduction

restful-react

npm

⚠️ restful-react has been deprecated: We are focusing on a new open api generator that generates react-query components: https://github.com/fabien0102/openapi-codegen ⚠️

Building React apps that interact with a RESTful API presents a set of questions, challenges and potential gotchas. This project aims to remove such pitfalls, and provide a pleasant developer experience when crafting such applications.

It can be considered a thin wrapper around the fetch API in the form of React components and hooks.

When used in a setup with OpenAPI / Swagger specs and Typescript, restful-react ensures a reliable and always up to date contract between backend and frontend. It generates components and types from your specs and can be integrated quite comfortably into your development workflows (featuring for example the import of OpenAPI specs from your github repos).

restful-react is very well tested, production ready and powers all of our projects at Contiamo.

Overview

At its core, restful-react exposes a hook, called useGet. This component retrieves data, either on mount or later, and then handles error states, loading states, and other cases for you. As such, you get a component that gets stuff and then does stuff with it. Here's a quick overview what it looks like.

Edit restful-react demos

import React from "react";
import { useGet } from "restful-react";

const MyComponent = () => {
  const { data: randomDogImage } = useGet({
    path: "https://dog.ceo/api/breeds/image/random",
  });

  return <img alt="Here's a good boye!" src={randomDogImage && randomDogImage.message} />;
};

export default MyComponent;

and on React Native, Edit restful-react basic demo on Expo

import { AppRegistry, Image } from "react-native";
import React from "react";

import { useGet } from "restful-react";

const App = () => {
  const { data: randomDogImage } = useGet({
    path: "https://dog.ceo/api/breeds/image/random",
  });
  return (
    <>
      {randomDogImage && (
        <Image
          style={{ width: 250, height: 250 }}
          source={{
            uri: randomDogImage.message,
          }}
        />
      )}
    </>
  );
};

AppRegistry.registerComponent("react-native-app", () => App);

Getting Started

To install and use this library, install it by running yarn add restful-react, or npm i restful-react --save and you should be good to go. Don't forget to import { useGet } from "restful-react" or similar wherever you need it!

Features

restful-react ships with the following features that we think might be useful.

Global Configuration

REST 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 useGet hooks.

Consider,

Edit restful-react demos

// index.js

import React from "react";
import { RestfulProvider } from "restful-react";

import App from "./App.jsx";

const MyRestfulApp = () => (
  <RestfulProvider base="https://dog.ceo/api">
    <App />
  </RestfulProvider>
);

export default MyRestfulApp;

Meanwhile, in ./App.jsx,

// App.jsx

import React from "react";
import { useGet } from "restful-react";

const MyComponent = () => {
  const { data: randomDogImage } = useGet({
    // Inferred from RestfulProvider in index.js
    path: "breeds/image/random",
  });

  return <img alt="Here's a good boye!" src={randomDogImage && randomDogImage.message} />;
};

export default MyComponent;

Naturally, the request will be sent to the full path https://dog.ceo/api/breeds/image/random. The full API of the RestfulProvider is outlined below. Each configuration option is composable and can be overridden by Get components further down the tree.

RestfulProvider API

Here's a full overview of the API available through the RestfulProvider, along with its defaults.

// Interface
export interface RestfulReactProviderProps<T = any> {
  /** The backend URL where the RESTful resources live. */
  base: string;
  /**
   * The path that gets accumulated from each level of nesting
   * taking the absolute and relative nature of each path into consideration
   */
  parentPath?: string;
  /**
   * A function to resolve data return from the backend, most typically
   * used when the backend response needs to be adapted in some way.
   */
  resolve?: ResolveFunction<T>;
  /**
   * Options passed to the fetch request.
   */
  requestOptions?: ((url: string, method: string, requestBody?: string) => Partial<RequestInit>) | Partial<RequestInit>;
  /**
   * Trigger on each error.
   * For `Get` and `Mutation` calls, you can also call `retry` to retry the exact same request.
   * Please note that it's quite hard to retrieve the response data after a retry mutation in this case.
   * Depending of your case, it can be easier to add a `localErrorOnly` on your `Mutate` component
   * to deal with your retry locally instead of in the provider scope.
   */
  onError?: (err: any, retry: () => Promise<T | null>, response?: Response) => void;
  /**
   * Trigger on each request.
   */
  onRequest?: (req: Request) => void;
  /**
   * Trigger on each response.
   */
  onResponse?: (req: Response) => void;
  /**
   * Query parameters passed to each request.
   */
  queryParams?: { [key: string]: any };
  /**
   * Query parameter stringify options applied for each request.
   */
  queryParamStringifyOptions?: IStringifyOptions;
}

// Usage
<RestfulProvider
  base="String!"
  resolve={data => data}
  requestOptions={(url, method, requestBody) => ({ headers: { Authorization: authToken } })}
/>;

Here's some docs about the RequestInit type of request options.

Loading and Error States

useGet hooks return an object with loading and error states, to allow for state handling. Consider,

Edit restful-react demos

import React from "react";
import { useGet } from "restful-react";

const MyComponent = () => {
  const { data: randomDogImage, loading } = useGet({
    path: "https://dog.ceo/api/breeds/image/random",
  });

  return loading ? <h1>Loading...</h1> : <img alt="Here's a good boye!" src={randomDogImage.message} />;
};

export default MyComponent;

Lazy Fetching

It is possible to use a useGet hook and defer the fetch to a later stage. This is done with the lazy boolean property. This is great for displaying UI immediately, and then allowing parts of it to be fetched as a response to an event: like the click of a button, for instance. Consider,

Edit restful-react demos

import React from "react";
import { useGet } from "restful-react";

const MyComponent = () => {
  const { data: randomDogImage, loading, refetch } = useGet({
    path: "https://dog.ceo/api/breeds/image/random",
    lazy: true,
  });

  return !randomDogImage && loading ? (
    <h1>Loading!</h1>
  ) : (
    <div>
      <div>
        <h1>Welcome to my image getter!</h1>
        <button onClick={() => refetch()}>Get a good boye!</button>
      </div>
      <div>{randomDogImage && <img alt="Here's a good boye!" src={randomDogImage.message} />}</div>
    </div>
  );
};

export default MyComponent;

The above example will display your UI, and then load good boyes on demand.

Response Resolution

Sometimes, your backend responses arrive in a shape that you might want to adapt, validate, or restructure. Other times, maybe your data consistently arrives in a { data: {} } shape, with data containing the stuff you want.

At the RestfulProvider level, or on the useGet level, a resolve prop will take the data and do stuff to it, providing the final resolved or unwrapped data to the children. Consider,

Edit restful-react demos

import React from "react";
import { useGet } from "restful-react";

const MyComponent = () => {
  const { data: imageUrl } = useGet({
    path: "https://dog.ceo/api/breeds/image/random",
    resolve: image => image && image.message,
  });

  return imageUrl && <img alt="Here's a good boye!" src={imageUrl} />;
};

export default MyComponent;

Debouncing Requests

Some requests fire in response to a rapid succession of user events: things like autocomplete or resizing a window. For this reason, users sometimes need to wait until all the keystrokes are typed (until everything's done), before sending a request.

restful-react exposes a debounce prop on Get that does exactly this.

Here's an example:

const SearchThis = props => {
  const { data } = useGet({
    path: "/hello/world",
    debounce: true,
  });

  return (
    <div>
      <h1>Here's all the things I search</h1>
      <ul>
        {data.map(thing => (
          <li>{thing}</li>
        ))}
      </ul>
    </div>
  );
};

Debounce also accepts a number, which tells useGet how long to wait until doing the request.

const SearchThis = props => {
  const { data } = useGet({
    path: "/hello/world",
-    debounce: true,
+    debounce: 200 /* ms */,
  })

  return <div>
        <h1>Here's all the things I search</h1>
        <ul>
          {data.map(thing => (
            <li>{thing}</li>
          ))}
        </ul>
      </div>
}

It uses lodash's debounce function under the hood, so you get all the benefits of it out of the box like so!

const SearchThis = props => {
  const { data } = useGet({
    path: "/hello/world",
-    debounce: 200,
+    debounce: { wait: 200, options: { leading: true, maxWait: 300, trailing: false } } /* ms */,
  })

  return <div>
        <h1>Here's all the things I search</h1>
        <ul>
          {data.map(thing => (
            <li>{thing}</li>
          ))}
        </ul>
      </div>
}

TypeScript Integration

One of the most powerful features of restful-react is that each component exported is strongly typed, empowering developers through self-documenting APIs.

Using restful-react in VS Code

Query Parameters

All components in this library support query params (https://my.site/?query=param) via a queryParams prop. Each useGet, useMutate and Poll instance is generic, having a type signature of useGet<TData, TError, TQueryParams>. If described, the queryParams prop is fully type-safe in usage and provides autocomplete functionality.

Autocompletion on QueryParams

Please note that the above example was built using our OpenAPI generator in order to infer the type of component from the specification and automatically generate the entire type-safe component in a very quick and easy way.

Mutations with useMutate

restful-react exposes an additional hook called useMutate. These components allow sending requests with other HTTP verbs in order to mutate backend resources.

Edit restful-react demos

import React from "react";
import { useGet, useMutate } from "restful-react";

const base = "https://jsonplaceholder.typicode.com";

const ListItem = ({ id, children }) => {
  const { mutate: del, loading } = useMutate({
    verb: "DELETE",
    path: `/posts/`,
    base,
  });

  return (
    <li key={id}>
      {loading ? (
        "Deleting..."
      ) : (
        <button onClick={() => del(id).then(() => alert("Deleted successfully. Pretend it got removed from the DOM."))}></button>
      )}
      &nbsp;{children}
    </li>
  );
};

const MyHugeList = () => {
  const { data: posts } = useGet({
    path: "/posts",
    base,
  });
  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts &&
          posts.map(post => (
            <ListItem key={post.id} id={post.id}>
              {post.title}
            </ListItem>
          ))}
      </ul>
    </div>
  );
};
export default MyHugeList;

useMutate is strongly typed, and provides intelligent autocompletion out of the box, complete with other available HTTP verbs.

Mutate

Each mutation returns a promise that can then be used to update local component state, dispatch an action, or do something else depending on your use case.

Mocks

No backend support yet for your amazing feature? Need to isolate an edge case? You can easily provide a mock to useMutate and useGet to bypass the classic flow.

/!\ If mock option is provided, no requests will be send to the server. /!\

import React from "react";
import { useGet, useMutate } from "restful-react";

const base = "https://jsonplaceholder.typicode.com";

// Mock the `mutate` handler
const { mutate: del, loading } = useMutate({
  verb: "DELETE",
  path: `/posts/`,
  base,
  // This will avoid any server call in favor of mock response
  mock: {
    mutate: id => console.log(`The item ${id} was deleted`),
  },
});

// Mock the `loading`, so it's easy to isolate the loading state
const { data: posts } = useGet({
  path: "/posts",
  base,
  // This will avoid any server call in favor of mock response
  mock: {
    loading: true,
  },
});

// Mock the `error`, so it's easy to isolate the error state
const { data: posts } = useGet({
  path: "/posts",
  base,
  // This will avoid any server call in favor of mock response
  mock: {
    error: "oh no!",
  },
});

Polling with Poll

restful-react also exports a Poll render props component that will poll a backend endpoint over a predetermined interval until a stop condition is met. Consider,

import { Poll } from "restful-react"

<Poll path="/deployLogs" resolve={data => data && data.data}>
  {(deployLogs: DeployLog[], { loading }) =>
    loading ? (
      <PageSpinner />
    ) : (
      <DataTable
        columns={["createdAt", "deployId", "status", "sha", "message"]}
        orderBy="createdAt"
        data={deployLogs}
        formatters={{
          createdAt: (d: DeployLog["createdAt"]) => title(formatRelative(d, Date.now())),
          sha: (i: DeployLog["sha"]) => i && i.slice(0, 7),
        }}
      />
    )
  }
</Poll>

Poll supports:

  • an interval prop that will poll at a specified interval (defaults to polling 1 second), and

  • an until prop that accepts a condition expressed as a function that returns a boolean value. When this condition is met, polling will stop.

    The signature of this function is (data: T, response: ResponseInit) => boolean. As a developer, you have access to the returned data, along with the response object in case you'd like to stop polling if response.ok === false, for example.

Below is a more convoluted example that employs nearly the full power of the Poll component.

<Poll path="/status" until={(_, response) => response && response.ok} interval={0} lazy>
  {(_, { loading, error, finished, polling }, { start }) => {
    return loading ? (
      <Progress error={error} />
    ) : (
      <Button
        loading={editorLoading || polling}
        condensed
        icon="ExternalLink"
        color="ghost"
        onClick={() => {
          if (finished) {
            return window.open(editor.url);
          }
          requestEditor();
          start();
        }}
      >
        {finished ? "Launch Editor" : "Request Editor"}
      </Button>
    );
  }}
</Poll>

Note from the previous example, Poll also exposes more states: finished, and polling that allow better flow control, as well as lazy-start polls that can also be programmatically stopped at a later stage.

Long Polling

At Contiamo, we have a powerful Long Polling specification in place that allows us to build real-time apps over HTTPS, as opposed to WebSockets. At a glance the specification can be distilled into:

  • Web UI sends a request with a Prefer header that contains:
    • a time, in seconds, to keep requests open (60s), and
    • a polling index that is a server-sent hash ahpiegh.
    • all together, the client sends a request with a header Prefer: wait=60s;index=939192.
  • The backend server responds, either with:
    • an empty response with status 304 Not Modified
    • a successful response with data and a new polling index.

The polling index allow the client and the server to stay in sync: the client says "the last stuff I got was at this index". The server says "oh, let me get you up to speed and send you a new index".

Visually, this is represented as below.

Contiamo Poll.

To get this functionality in restful-react, this means specifying a wait prop on your Poll component, provided your server implements this specification as well.

Polling and Code Generation

By default we generate a Poll component when the prefer header is specified in the OpenAPI/Swagger specs (more information about this design decision here -> https://github.com/contiamo/restful-react#long-polling).

We do not generate an equivalent hook version. Polling is quite trivial in a react hook, so we usually just use useEffect when we need some polling feature.

Example:

// Poll data if no completedAt
useEffect(() => {
  if (error) {
    return onError();
  } else if (data && !data.completedAt) {
    const timerId = window.setTimeout(() => refetch(), 1000);
    return () => window.clearTimeout(timerId);
  } else {
    return;
  }
}, [data, refetch, error]);

Code Generation from OpenAPI / Swagger specs

restful-react is able to generate React hooks with appropriate type-signatures (TypeScript) from any valid OpenAPI v3 or Swagger v2 specification, either in yaml or json formats.

Usage

Type-safe React data fetchers can be generated from an OpenAPI specification using the following command:

  • restful-react import --file MY_OPENAPI_SPEC.yaml --output my-awesome-generated-types.tsx

This command can be invoked by either:

  • Installing restful-react globally and running it in the terminal: npm i -g restful-react, or
  • Adding a script to your package.json like so:
      "scripts": {
        "start": "webpack-dev-server",
        "build": "webpack -p",
+       "generate-fetcher": "restful-react import --file MY_SWAGGER_DOCS.json --output FETCHERS.tsx"
      }

Your components can then be generated by running npm run generate-fetcher. Optionally, we recommend linting/prettifying the output for readability like so:

      "scripts": {
        "start": "webpack-dev-server",
        "build": "webpack -p",
        "generate-fetcher": "restful-react import --file MY_SWAGGER_DOCS.json --output FETCHERS.tsx",
+       "postgenerate-fetcher": "prettier FETCHERS.d.tsx --write"
      }

Validation of the OpenAPI specification

To enforce the best quality as possible of specification, we have integrated the amazing OpenAPI linter from IBM. We strongly encourage you to setup your custom rules with a .validaterc file, you can find all useful information about this configuration here.

To activate this, add a --validation flag to your restful-react call.

API Versioning

The generated file will include an exported constant SPEC_VERSION that will contain to the OpenAPI info.version property's value.

Import from URL

Adding the --url flag to restful-react import instead of using the --file flag will attempt to fetch the spec from that endpoint.

  • restful-react import --url https://api.mine.com/openapi.json --output my-awesome-generated-types.tsx

Import from GitHub

Adding the --github flag to restful-react import instead of using the --file flag allows us to create React components from an OpenAPI spec remotely hosted on GitHub. (how is this real life 🔥 )

To generate components from remote specifications, you'll need to follow the following steps:

  1. Visit your GitHub settings.

  2. Click Generate New Token and choose the following:

    Token Description: (enter anything)
    Scopes:
        [X] repo
            [X] repo:status
            [X] repo_deployment
            [X] public_repo
            [X] repo:invite
            [X] security_events
    
  3. Click Generate token.

  4. Copy the generated string.

  5. Open a terminal and run restful-react import --github username:repo:branch:path/to/openapi.yaml --output MY_FETCHERS.tsx, substituting things where necessary.

  6. You will be prompted for a token.

  7. Paste your token.

  8. You will be asked if you'd like to save it for later. This is entirely up to you and completely safe: it is saved in your home directory.

  9. You're done! 🎉

Note: For CI environment, you can also provide the github token with the environment variable called GITHUB_TOKEN

Transforming an Original Spec

In some cases, you might need to augment an existing OpenAPI specification on the fly, for code-generation purposes. Our CLI makes this quite straightforward:

  restful-react import --file myspec.yaml --output mybettercomponents.tsx --transformer path/to/my-transformer.js

The function specified in --transformer is pure: it imports your --file, transforms it, and passes the augmented OpenAPI specification to restful-react's generator. Here's how it can be used:

// /path/to/my-transformer.js

/**
 * Transformer function for restful-react.
 *
 * @param {OpenAPIObject} schema
 * @return {OpenAPIObject}
 */
module.exports = inputSchema => ({
  ...inputSchema,
  // Place your augmentations here
  paths: Object.entries(schema.paths).reduce(
    (mem, [path, pathItem]) => ({
      ...mem,
      [path]: Object.entries(pathItem).reduce(
        (pathItemMem, [verb, operation]) => ({
          ...pathItemMem,
          [verb]: {
            ...fixOperationId(path, verb, operation),
          },
        }),
        {},
      ),
    }),
    {},
  ),
});

Advanced configuration

restful-react supports the concept of "schema stitching" in a RESTful ecosystem as well. We are able to tie multiple backends together and generate code using a single configuration file, restful-react.config.js

To activate this "advanced mode", replace all flags from your restful-react call with the config flag: --config restful-react.config.js (or any filename that you want).

⚠️ Note: using a config file makes use of all of the options contained therein, and ignores all other CLI flags.

Config File Format
interface RestfulReactConfig {
  [backend: string]: {
    // classic configuration
    output: string;
    file?: string;
    github?: string;
    transformer?: string;
    validation?: boolean;
    skipReact?: boolean;

    // advanced configuration
    customImport?: string;
    customProps?: {
      base?: string;
    };
    pathParametersEncodingMode?: "uriComponent" | "rfc3986";
    customGenerator?: (data: {
      componentName: string;
      verb: string;
      route: string;
      description: string;
      genericsTypes: string;
      operation: OperationObject;
      paramsInPath: string[];
      paramsTypes: string;
    }) => string;
  };
}
Config File Example
// restful-react.config.js
/**
 * Restful-react configuration.
 *
 * @type {import("restful-react/dist/bin/config").RestfulReactAdvancedConfiguration}
 */
module.exports = {
  myFirstBackend: {
    output: "src/queries/myFirstBackend.tsx",
    file: "specs/my-first-backend.yaml",
    customProps: {
      base: `"http://my-first-backend.com"`,
    },
  },
  configurableBackend: {
    output: "src/queries/configurableBackend.tsx",
    github: "contiamo:restful-react:master:docs/swagger.json",
    customImport: `import { getConfig } from "../components/Config.tsx";`,
    customProps: {
      base: `{getConfig("backendBasePath")}`,
    },
  },
};
// package.json
{
  "scripts": {
    "gen": "restful-react import --config restful-react.config.js",
    "gen-first": "restful-react import --config restful-react.config.js myFirstBackend"
  }
}
Custom generator

To support even more advanced usecases (like a promise base API, mock generator or anything else that can infer from your specs), you can define your own template in customGenerator. This function will be call for each route with some useful computed values (see the types above) and the resulted string will be added to the generated file.

You can see a concrete usage inside the examples folder and try yourself in this repository with the following command:

  • yarn build
  • yarn example:advanced petstore-custom-fetch

You can inspect the result inside /examples/petstoreFromFileSpecWithCustomFetch.tsx

Only generating custom code (no react hooks/components)

In some cases you might want to use the familiar restful-react to generate code for non-react environments (e.g. promise-based fetchers for nodejs or other frameworks). In this case, you can disable react code generation altogether by passing the --skipReact flag or, if you are using a configuration file, setting skipReact: true.

When set, only your custom generators will be executed.

Contributing

All contributions are welcome – especially:

  • documentation,
  • bug reports and issues,
  • code contributions.

Code

If you'd like to actively develop or help maintain this project then there are existing tests against which you can test the library with. Typically, this looks like

  • git clone [email protected]:contiamo/restful-react.git
  • cd restful-react
  • yarn install
  • yarn test --watch

From there, you should be able to start developing without problems.

How to publish to npm

Just update the version in package.json!

As soon as your branch will be merged to master, a new npm version will be automatically published for you.

@without-cli npm package

If for any reasons you don't want to use our CLI to generate restful-react components, we provide a without-cli version of the package.

Just npm install restful-react@without-cli to have this light version.

This version will follow latest but without the cli part (more details into publish-without-cli.js).

Next Steps

We're actively developing this at Contiamo to meet our use cases as they arise. If you have a use case that you'd like to implement, do it! Open an issue, submit a Pull Request, have fun! We're friendly.

restful-react's People

Contributors

abhinavrastogi avatar abonifacio avatar aisensiy avatar alfdocimotvp avatar amacleay-cohere avatar cbek avatar chilijung avatar cmaus-cs avatar dcruzin avatar dependabot[bot] avatar disrupted avatar fabien0102 avatar greenkeeper[bot] avatar jakewtaylor avatar leechsien avatar manishalexin avatar micha-f avatar mpotomin avatar peterszerzo avatar qw-in avatar s-thom avatar seanlaff avatar south-paw avatar stereobooster avatar tejasq avatar torkelrogstad avatar u9r52sld avatar vkbansal avatar vxsx avatar ysgk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

restful-react's Issues

Add `TBody` to `Mutate` to be able to type the body

Is your feature request related to a problem? Please describe.
Actually we can't type the body when we are using the Mutate component.

Describe the solution you'd like

// My `TBody`
interface HamburgerBody {
  size?: "small" | "normal" | "large" | "xlarge";
  haveOnions?: boolean;
  meatType?: "pork" | "beaf" | "dog"
}

<Mutate<TData, TError, HamburgerBody> verb="POST" path="/find-the-best-burger">
  {findTheBestBurger => (
    <button onClick={() => findTheBestBurger({size: "xlarge", haveOnions: true})}>
       I'm starving
     </button>)}
</Mutate>

Additional context
This will be very very useful and powerful in combinaison of the open-api generator (#28)

Update docs

Currently, there are a whole bunch of breaking changes in next that are undocumented from #7. If the documentation is updated, we can do a new major release! 🎉

ImportSpec error is not handle

We should not destructure the param here -> https://github.com/contiamo/restful-react/blob/master/src/scripts/import-open-api.ts#L213

This can cause this kind of error:

> restful-react import --file ../documentation/docs/reference/api/idp.swagger.json --output src/components/queries/idp.tsx --transformer schema-transformer.js

/Users/lucasroesler/Code/contiamo/idp/ui/node_modules/restful-react/lib/bin/restful-react-import.js:249
                var openapi = _a.openapi;
                                 ^

TypeError: Cannot read property 'openapi' of undefined
    at /Users/lucasroesler/Code/contiamo/idp/ui/node_modules/restful-react/lib/bin/restful-react-import.js:249:34
    at /Users/lucasroesler/Code/contiamo/idp/ui/node_modules/call-me-maybe/index.js:13:28
    at processTicksAndRejections (internal/process/next_tick.js:74:9)
    at process.runNextTicks [as _tickCallback] (internal/process/next_tick.js:5

If something we have an error, the program break before the actual error handler 😅

Add cache

Is your feature request related to a problem? Please describe.
Add simplest caching mechanism (LRU with expiration time for cache) for useGet. Similar to https://github.com/CharlesStover/fetch-suspense/blob/master/src/fetch-suspense.ts

Describe the solution you'd like
By default cache is disabled (for backward compatibility). If TTL option provided (and it is more than 0) than cache results for that time. Time is provided in seconds.

Describe alternatives you've considered
none

Additional context
N/A

<Get /> may try to update state when component unmounts

Describe the bug

fetch method in Get.tsx could resolve the promise when component gets unmounted. This would call this.setState when component is not longer rendered what makes react to throw an error.

To Reproduce

In a test:

  1. mount Get fetching for some resource.
  2. create a nock instance that delays the response long enough to unmount the component before it resolves the request.
  3. unmount the component and check the output. The error should be there.

Expected behavior

setState should not be called after component unmounts.

A solution here could be creating cancelable promises when fetching. Then keep track of pending requests in the instance of the component and cancelling the all the requests when component unmounts. fetch should not call setState when the request is cancelled.

Screenshots
unmounted

Poll doesn't support function for requestOptions same way as Get does

Describe the bug
Poll doesn't support function for requestOptions same way as Get does

To Reproduce
Steps to reproduce the behavior:

  1. Create a RestfulProvider with requestOptions as a function returning some header
  2. Use Get and Poll inside
  3. Open devtools and see that headers on poll requests are not sent

Expected behavior
Behaviour should be the same for Poll and Get

Additional context
I found this issue while using a version of safari that requred credentials: 'same-origin' in request options and it took me a while to figure out

Improve API for nested <Get>s

Is your feature request related to a problem? Please describe.
I think that it is

  • not obvious from looking at code that path="string" will be appended to the path(s) of parent <Get>(s)
  • not safe to assume that components will only make requests that fit with the nesting of paths

Describe the solution you'd like
I propose to update the API so that

<Get path="/persian"> // makes a request to /persian
<Get path={(parentPaths: string[]) => `${parentPath.join("/")}/persian`} > // makes a request to /cats/persian

where parentPaths is an array of the normalized paths (stripped of trailing /) of the parent <Get>s.

Describe alternatives you've considered

  1. parentPaths could also be a string of the concatenated parent paths. I believe however that the use of an array is more flexible if we go with nested <Get>s affecting each other.
  2. Alternatively or additionally we could introduce a <Path> component that affects the nesting while <Get>s don't affect each other's path at all. This would allow us to do things like this, too:
<Path path="/author/123">
  <Get path={ (path: string) => `${path}/bio`>
  <Get path={ (path: string) => `${path}/books`>
</Path>

Add `displayName` for Context component

Is your feature request related to a problem? Please describe.

Add displayName for Context component to improve DX

SomeContext.Consumer.displayName = "SomeContext.Consumer";
SomeContext.Provider.displayName = "SomeContext.Provider";

Describe the solution you'd like

Describe alternatives you've considered
N/A

Additional context
N/A

Avoid Error Updates on Unmounted Component

Describe the bug
The Get component has an abort handler that will cancel any state updates after the component umounts. However, since the fetch call is wrapped in a try catch block, it will bypass the abort check and update state resulting in the classic Warning: Can't perform a React state update on an unmounted component

This is because the abort check is after the catch block intercepts the error thrown by fetch

restful-react/src/Get.tsx

Lines 256 to 262 in 529bdb3

const response = await fetch(request, { signal: this.signal });
const { data, responseError } = await processResponse(response);
// avoid state updates when component has been unmounted
if (this.signal.aborted) {
return;
}

I was able to fix this issue by putting the abort check in the catch block as well

https://github.com/AJHenry/restful-react/blob/8627e00f58cfc8c69d050c0d9a811717aa6e0533/src/Get.tsx#L381-L385

To Reproduce
Steps to reproduce the behavior:

  1. Mount the Get component
  2. Call an API that will result in an error such as 401, 404, etc
  3. Unmount the component before the call finishes and the error is thrown

Expected behavior
The state should not be updated when the component is unmounted, regardless of whether there is an error or not

Additional context
I would open a PR for this issue myself but I am unable to reliably reproduce the issue in a unit test (as setState on unmounted components are fairly hard to test)

Example of specifying headers for requests

Is your feature request related to a problem? Please describe.
When performing GET requests for resources, in development I may need to specify the CORS header in requests (e.g. in my setup I am developing React on my laptop, and the web server is on a Raspberry PI on the network). I can't find any examples of being able to specify headers.
I appreciate this may be a contrived issue specific to my set up, but specifying headers on requests is a fairly normal part of web development.

Describe the solution you'd like
A new example on code sandbox with examples of custom headers in requests

Describe alternatives you've considered
N/a

Additional context
N/a

Thanks!

Implement validator generation

Is your feature request related to a problem? Please describe.
Implement validator generation, which can be used for IO validation and will be compatible with static types (TypeScript).

Describe the solution you'd like
Generate io-ts (or any other library) validators from OpenAPI specification. For example, transform.now.sh can generate io-ts definitions from JSON.

Describe alternatives you've considered
N/A

Additional context
N/A

Improve clarity of backend route composition

Is your feature request related to a problem? Please describe.
If I start out a page with the following markup:

<Get path="/people">
  {data => (
    <Get path="/products" />
  )}
</Get>

I find it unintuitive that the resulting backend path in the child Get component is ${base}/people/products, which, in an HTML analogy, would make more sense if I omitted the slash like in <Get path="products">. It feels like an overhead to have to do ../products in order to replicate the 'absolute path' behavior, or to pull in the backend URL again to set as base prop. I wonder if we can make this more predictable.

Describe the solution you'd like
The following code:

<Get path="/people">
  {data => (
    <Get path="/products" />
  )}
</Get>

should make requests to ${base}/people and ${base}/products, whereas

<Get path="/people">
  {data => (
    <Get path="products" />
  )}
</Get>

should make requests to ${base}/people and ${base}/people/products.

In this proposal, URL composition can be disabled by simply adding a slash. As a personal preference, I can even see myself forcing absolute paths at all times in order to remove coupling my components to the entire component hierarchy (and thereby making rearranging components risky). We could even enforce this as a kind of strict mode, or issue runtime warnings if the user opts into them.

Alternatives
I can foresee some confusion happening when relative and absolute positions are nested, but I'm sure we can figure out a nice set of rules on the implementation level. Maybe we can gather some data from projects using restful-react - how much nesting/composition is desired and how nesting complexity is managed - to see what API makes the most sense.

Implement multipart/form-data handling when generating the component from OpenAPI spec

Is your feature request related to a problem? Please describe.
For the post request, it would be amazing 😍 to have autogeneration to handle multipart/form-data section of the OpenAPI spec, to make it make it possible to generate React components also for uploading a form data.

Additional context
Example of the OpenaAPI spec:

  "/api/upload":
    post:
      tags:
        - table
        - file
      summary: Upload a file
      description: Upload a file 
      operationId: UploadFile
      parameters:
        - name: collectionId
          in: path
          description: UUID or name of the collection
          required: true
          schema:
            type: string
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
        description: File data
        required: true

Triple change of the state in the useGet

Describe the bug

There is a bug in restful-react, it sets state thrice after changing input values:

  • "not loading" with previous data
  • then "loading" state
  • then "not loading" with new data

To Reproduce

To be done

Expected behavior

I would expect change of the state twice:

  • "loading" state
  • then "not loading" with new data

Screenshots
N/A

Desktop (please complete the following information):

  • OS: MacOS
  • Browser chrome
  • Version Version 76.0.3809.100 (Official Build) (64-bit)

Smartphone (please complete the following information):
N/A

Additional context
N/A

Promise callback of Mutate function not being called

Describe the bug
When using a Mutate function with the PUT verb, the callback of the mutation promise is not called

To Reproduce
Minimal Code example to reproduce

<Mutate
    verb="PUT"
    base={`https://jsonplaceholder.typicode.com/put/posts/1`}
>
    {(update, { loading, error }) => {
    console.log(loading);
    console.log(error);
    return (
        <div
        style={{ width: "100px", height: "100px" }}
        onClick={() => {
            console.log("Clicked");
            update({
            id: 1,
            title: "foo",
            body: "bar",
            userId: 1
            }).then(() => console.log("Then called"));
        }}
        >
        Click
        </div>
    );
    }}
</Mutate>

There are no throw errors and loading turns from false -> true -> false indicating an operation took place.

Not sure if the operation is being aborted early for some reason, or is throwing an error that is not being caught by the Mutate function.

NOTE: You can use the .finally method on the promise but it is unclear if this is intended behavior in the docs

Expected behavior
According to the docs, "Each mutation returns a promise...", which to me indicates that I should be allowed to use the .then function on the returned promise.

Screenshots
N/A

Desktop (please complete the following information):

  • OS: Windows 10 1803
  • Browser: Firefox
  • Version: 66.0.5 (64-bit)

Additional Note
Even the docs are using the .then function on the mutate function so I believe this is definitely a bug
See here

onClick={() => delete(movie.id).then(() => dispatch('DELETED'))}

Also a side note, delete is a reserved word in JS and that line will result in a compile error

The component should refetch on each props change

Is your feature request related to a problem? Please describe.

restful-react/src/Get.tsx

Lines 158 to 166 in 9064270

public componentDidUpdate(prevProps: GetProps<TData, TError>) {
// If the path or base prop changes, refetch!
const { path, base } = this.props;
if (prevProps.path !== path || prevProps.base !== base) {
if (!this.props.lazy) {
this.fetch();
}
}
}

Actually we only refetch on path or base changes.

Describe the solution you'd like
I was expecting to have a new set of data on resolve props change. Ideally, we can only re-evaluate the resolve part without fetching the API, but the resolve is in the middle of the flow so it's maybe not necessary for now 😉 => We can just call this.fetch and optimize later if it's really needed

Additional context
The problem in the context:

export type ListUsersProps = Omit<GetProps<UserListResponse, {}>, "base" | "path"> & {
  tenantId: string;
  page: number;
  filter?: string;
};

export const ListUsers = ({ page, filter, ...props }: ListUsersProps) => (
  <Config>
    {({ getConfig }) => (
      <Get<UserListResponse>
        debounce
        base={getConfig("backend")}
        path={`${props.tenantId}/users?page=${page}`}
        resolve={(res: UserListResponse) => ({
          ...res,
          data: res.data.filter(user => (filter ? user.name.includes(filter) : true)),
        })}
        {...props}
      />
    )}
  </Config>
);

This component should return a new set of data on every filter change, but nothing happens

Incorrect exit code on errors

Describe the bug
I am trying to use restful-rect as part of a CI flow to ensure that a OpenAPI spec will correctly generate a Typescript client, but even when restful-react fails, Jenkins treats the CI stage as a success. In Jenkins it looks like this

image

To Reproduce
Steps to reproduce the behavior:
If you have docker, you can run

  1. docker run --rm -it node curl https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/examples/v3.0/petstore.yaml --output api.yaml; npx restful-react@next import --file api.yaml --output ignoreme.tsx; echo $?
  2. See that that last line it prints is 0

Expected behavior
I would expect the above command to print 1 or at least some other positive number 1--255, per https://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF

Screenshots
If applicable, add screenshots to help explain your problem.
image

Additional context
Add any other context about the problem here. I used the docker container to reproduce this here because my use case in CI is very similar/the same as the docker run command

Implement a backoff strategy on Poll

Is your feature request related to a problem? Please describe.
This is related to #82. That issue is a little more nuanced as some error codes are retry-able, and others arent: for example, we can safely stop on a 404 and a 401, but maybe not so much on a 503.

Currently, if we're polling an endpoint that is returning retry-able error codes, it would be nice to have some type of backoff strategy.

Describe the solution you'd like

  • Endpoint returns 503 (Temporarily Unavailalble)
    • We wait a little bit longer, try again
      • Still 503
        • We wait even longer, try again
          • ...
            • 304.
              • Awesome. Keep going.
                • 200 with x-polling-index header.
                  • Awesome. Keep going.
                    • 200 without x-polling-index header
                    • Awesome. Stop.

Describe alternatives you've considered

  1. Fail and stop polling until full reload: this doesn't quite make sense on things that are retry-able (a la network downtime) because if the network comes back, the user needs to be updated.

  2. Brute force poll: this will keep making requests against the server without letting it breathe and might be an exercise in futility since a server could be 503ing for hours.

Thoughts?

nullable: true don't produce the correct type

Describe the bug

On our openAPI generator, nullable: true should produce a | null in the resulted generated type.

Reproduction steps

Schema:

 components:
  schemas:
    PrimitiveStrictValue:
      oneOf:
        - type: string
        - type: boolean
        - type: number

    PrimitiveValue:
      $ref: '#/components/schemas/PrimitiveStrictValue'
      nullable: true

Result:

export type PrimitiveStrictValue = string | boolean | number;

export type PrimitiveValue = PrimitiveStrictValue;

Expected:

export type PrimitiveStrictValue = string | boolean | number;

export type PrimitiveValue = PrimitiveStrictValue | null;

References
https://swagger.io/specification/#schemaNullable

Better docs / examples

Hi,

As someone somewhat new to React, this seems a good way to do simple REST requests - but there are a lot of gotchas, and the example snippets aren't enough to understand why things don't work as expected.
Example:

import Mutate from "restful-react";

{(addLocation) => (
<Button outline color="gr" onClick={() => addLocation()}> Add)}

fails with "No function called addLocation()". You need to change the import to:
import { Mutate } from "restful-react";

This may be obvious to someone who knows React well, but to someone new it's really not.
Can the docs be updated, or (better) a working example complete mini-app be created?

Thanks

An in-range update of ts-jest is breaking the build 🚨

The devDependency ts-jest was updated from 23.10.0 to 23.10.1.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

ts-jest is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • Travis CI - Branch: The build failed.

Commits

The new version differs by 13 commits.

  • d9c5b45 Merge pull request #743 from huafu/release/23.10.1
  • e4a3a09 chore(release): 23.10.1
  • ab94359 Merge pull request #742 from huafu/fix-740-no-js-compilation-with-allow-js
  • a844fd4 Merge branch 'master' into fix-740-no-js-compilation-with-allow-js
  • 18dced1 Merge pull request #741 from huafu/e2e-weird-deep-paths
  • 9e7d6a0 test(config): adds test related to allowJs
  • 374dca1 fix(compile): js files were never transpiled thru TS
  • 70fd9af ci(cache): removes some paths from the caching
  • c12dfff fix(windows): normalize paths
  • 0141098 test(e2e): deep paths and coverage
  • 6ccbff3 Merge pull request #736 from huafu/detect-import-and-throw-if-none
  • a2a4be2 fix(config): warn instead of forcing ESM interoperability
  • 21644da Merge pull request #735 from huafu/master

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

useMutate can crash if the server is sending a bad JSON

Describe the bug
ngnix is a bad boy, you ask for a json, send you an html -> boom!

We already handle this case on classic component, apparently I forgot to port this edge case to the hook one 😕

Late note

It was covered, a unit test was added to demontrate this use case 😉

Respect full URLs even if using a provider

If I do use a <RestfulProvider base="https://example.com/api"> but then make a request using full URL (https://example.com/api/xxx etc), it should use the provided URL not concatenate them together.

Right now this

<RestfulProvider base="https://example.com/api">
  <Get path="https://reqres.in/api/users">
    ...
  </Get>
</RestfulProvider>

Results in attempting to fetch URL https://example.com/apihttps://reqres.in/api/users but it should just use the provided URL https://reqres.in/api/users.

Makes sense that most of my requests go to my own API for which I have added the RestfulProvider but perhaps I want to make some requests to 3rd party API so for those requests, I would provide the full URL and expect the provider base to be ignored.

Tested on React native but it's likely the same on the web.

Export simple type annotations for function-as-a-child arguments

Is your feature request related to a problem? Please describe.
Sometimes it is necessary to pass down methods such as refetch and mutate that come from the function-as-a-child arguments of the Get and Mutate components, e.g. child component or a component method at which point they need a type annotation. Currently, this is quite hard to reproduce with the mutate API looking something like this: https://github.com/contiamo/restful-react/blob/master/src/Mutate.tsx#L119.

Describe the solution you'd like
The library should export interfaces for these methods taking the number and configuration of generics as the Get or Mutate components themselves. This would make type annotations straightforward and easy to follow.

Allow for validation failures in the `resolve` prop

Is your feature request related to a problem? Please describe.
Currently, the resolve prop of the Get component is a simple function that does some transformation on the data coming from the backend. The developer using restful-react is responsible that it is correct (no runtime errors), and add additional logic on their own to handle unexpected responses from the server. In order to offer some legwork to help folks make absolutely sure that the data passing this function either fails gracefully or passes through with sound data, I'd like to suggest two features:

  • catch runtime errors in the fetch method (adding a .catch in https://github.com/contiamo/restful-react/blob/master/src/Get.tsx#L152), sending runtime errors to either the global error management of the library, or the local one if localErrorsOnly is enabled. This would help with third-party parsing/validating solutions like https://github.com/jquense/yup#mixedcastvalue-any-any, which are super powerful, but always throw runtime errors when they fail.
  • allow for the resolve prop to accept a promise which either resolves with the correctly shaped data or rejects with an error. Again, this might help with third-party validation solutions which offer some validation features only asynchronously (recursive data structures and the like).

Describe alternatives you've considered
I cannot think of an alternative that doesn't bypass resolve entirely, leaving validation to be performed inside the render method and killing performance.

Additional context
At a high-level, the purpose of these suggestions is to allow a style of programming that does super-strict validation before data reaches the internals of a react component tree. This provides both safety and debugging convenience: there is always a failure before data even reaches presentational views, narrowing down issues to the validator logic, which can be unit-tested with an ever-broadening list of fixtures from the backend. No more looking around React code to fix backend-frontend miscommunication.

Consider renaming `host` to `base`

As this property might also contain a base path I would like to propose renaming it:

<RestfulProvider base="https://dog.ceo/api"> ...

Install from npm the built file is not compiled to es5.

Describe the bug
Just install the npm pkg, the files inside lib folder is not compatible into es5... I can see import etc syntax in lib folder.

node_modules/restful-react/lib/index.js:1
  (function (exports, require, module, __filename, __dirname) { import Get from "./Get";
                                                                       ^^^

  SyntaxError: Unexpected identifier
      at new Script (vm.js:74:7)
      at createScript (vm.js:246:10)
      at Object.runInThisContext (vm.js:298:10)
      at Module._compile (internal/modules/cjs/loader.js:657:28)
      at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
      at Module.load (internal/modules/cjs/loader.js:599:32)
      at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
      at Function.Module._load (internal/modules/cjs/loader.js:530:3)
      at Module.require (internal/modules/cjs/loader.js:637:17)

To Reproduce
just install from npm.

Expected behavior
Should work in browsers

Provider error handling

Goal

Having a simple and global pattern to catch application errors.

The idea is the following:

<RestfulProvider base="https://my-awesome-api.fake" onError={onError}>
  <Get path="">
    {children}
  </Get>
</RestfulProvider>

On every error (in Get, Mutation, Poll) the RestfulProvider.onError is called with the classic {data: response, message: ""}

This will permit to have a centralize way to handle every errors, and because it's an event, we can also simply add a state to stack / dismiss errors like this:

class App extends React.Component {
   state = {
     errors: []
  }
  onError(err) {
     this.setState(prev => ({errors: [err, ...prev.errors]}))
  }
  onDismiss() {
     this.setState(prev => ({ errors: prev.errors.slice(1) }))
  }
  render() {
  <RestfulProvider base="https://my-awesome-api.fake" onError={this.onError}>
   {this.state.errors.length && <Progress error={this.state.errors[0].message} onClose={this.onDismiss} />
    <Get path="">
      {/* children */}
    </Get>
  </RestfulProvider>
  }

Questions

  1. Sometime we need to handle error locally with the classic pattern (for a better UX basically)

For this I propose a to add a localErrorOnly props to Get, Mutate, Poll to avoid the bubbling to the provider.

// this request will trigger the provider `onError` on error
<Get path="">{/* children */}</Get>

// this request will NOT trigger the provider `onError` on error
<Get path="" localErrorOnly>{/* children */}</Get>
  1. Do we add the same logic for loading?
onLoading: (nbOfActiveLoading: number) => void

and a bunch of localErrorOnly, localLoadingOnly, localOnly for our components

Provide a possibility to pause/disable calls on a hook-based fetch component

Request: As a library user, I would like to be able to prevent a hook-based fetch component from dispatching a call, based on some conditional logic in my app. Therefore I would like to be able to pass a boolean value as a request parameter.

Use case: Fetching an entity by name, that user enters in the UI. I would like to prevent dispatching a fetch call before user types a name of the minimal required length.

Axios interceptors alternative

Hi, this is such a cool lib, really props you to guys.

I have an API that needs to update its token after each successful apicall (great I know). I use axios interceptors for this today, I failed to find something similar from your documentation. Is there anyway to run some code before and after each request ?

Also, you guys use AbortController to cancel requests, do you need a polyfill for that to work in IE and similar shitty browsers ?

Again this is so cool.

Query params support

Problem

Actually we need to parse manually query params inside the path. This can be better!

<Mutate verb="DELETE" path="plop/?myparam=hello">
   {(del) => <button onClick={() => del()}>}
</Mutate>

Proposal

We can add an expanded way to call mutations, with {id: string, queryParams: {[key: string]: any }}

The parsing can be deal with https://www.npmjs.com/package/qs to avoid any useless extra works 😉

<Mutate verb="DELETE" path="">
   {(del) => <button onClick={() => del({id: "plop", queryParams: {myparam: "hello"}})}>}
</Mutate>

Note

This is just an API addition, this change must not break the actual API

Unit test for this feature

// Mutation.test.tsx
it("should add a query params", async () => {
      nock("https://my-awesome-api.fake")
        .delete("/plop?tejas=awesome")
        .reply(200, { id: 1 });

      const children = jest.fn();
      children.mockReturnValue(<div />);

      // setup - first render
      render(
        <RestfulProvider base="https://my-awesome-api.fake">
          <Mutate verb="DELETE" path="">
            {children}
          </Mutate>
        </RestfulProvider>,
      );

      await wait(() => expect(children.mock.calls.length).toBe(1));
      expect(children.mock.calls[0][1].loading).toEqual(false);
      expect(children.mock.calls[0][0]).toBeDefined();

      // delete action
      children.mock.calls[0][0]({ id: "plop", queryParams: { tejas: "awesome" } });
      await wait(() => expect(children.mock.calls.length).toBe(3));

      // transition state
      expect(children.mock.calls[1][1].loading).toEqual(true);

      // after delete state
      expect(children.mock.calls[2][1].loading).toEqual(false);
    });

Do not stop polling after a failed poll request

Describe the bug
I left my browser open for a while and lost my internet connection for a few mins. Poll got a failed request and never made another poll request.

To Reproduce

  1. Poll
  2. Disable network, wait until request fails
  3. Enable network again
  4. No more polling requests

Expected behavior
We should think about a retry / backoff strategy for Polling

Independent requests

Is your feature request related to a problem? Please describe.
Sometimes there is a need to fetch data and handle the response completely independently so that the state of Get and Mutate components is not affected at all, for instance in a Redux side effect manager. Request options, common headers, resolvers etc. should be the same for predictable usage.

Describe the solution you'd like
Expose an independent fetch method like so:

<Get path="/dogs" lazy>
  {(_unusedDogs, _unusedStatus, { fetchIndependent }) => (
    <button onClick={() => fetchIndependent().then(() => {
      this.setState(/** */)
    })} />
  )}
</Get>

We can think of fetchIndependent as a version of e.g. refetch that doesn't change the state, so _unusedDogs and _unusedStatus will never change throughout the lifecycle of the component. I am using lazy in the example to indicate that not even an initial fetch needs to happen in this case, but the two different ways of using restful-react can be used in combination so that the app both relies on fetching an initial value, but then also passes the independent fetcher down someplace else (e.g. an epic middleware) without having to worry about unintended refetches happening somewhere up the component tree.

Inconsistent render behavior when fetch rejects with net::ERR_SSL_PROTOCOL_ERROR

Describe the bug

The fetch api can reject its returned promise if the server it is fetching data from returns an invalid certificate. While this behavior should result in the <Get> rendering it's error section, I instead see the progress indicator. While not 100% sure, I believe this is caused by the following line: https://github.com/contiamo/restful-react/blob/v5.2.1/src/Get.tsx#L202 as I figured there was some await call without a catch.

To Reproduce

Option 1:

If this ends up sharing correctly, this should be located here: https://codesandbox.io/s/9p3wmx7po

Option 2:

If using react-scripts, you should be able to switch some api call from http://... to https://....

<RestfulProvider base="https://localhost:8080">
<Get path="/some/api/pets">
{(pets, { loading, error }) =>
    loading ? (
       Loading...
    ) : (
        <div>
        You should only see this after things are loaded.
        {error ? (
            <div>"OH NO!"</div>
        ) : (
            <div>success!</div>
        )}
        </div>
)}
</Get>
</RestfulProvider>

Option 3:

  1. Create a server that returns a different certificate than the domain used to query it (Subject Alternative Name or Common Name is different)
  2. Send a GET request to this server
  3. observe UI behavior

Expected behavior

If the GET request fails because in invalid certificates, the error section should render.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: OSX Darwin Kernel Version 15.6.0
  • Browser Chrome
  • Version v5.2.1

Additional context

There might be other edge cases (maybe CORS or timeouts?) that cause this same behavior though I don't have any evidence of that.

Support `discriminator` in openapi generator

Is your feature request related to a problem? Please describe.

We actually don't deal with the discrimator pattern, the actual workaround is to use oneOf combine with enum like this:

Error:
  oneOf:
  - "$ref": "#/components/schemas/GeneralError"
  - "$ref": "#/components/schemas/FieldError"
GeneralError:
  type: object
  required:
  - type
  - message
  properties:
    type:
      type: string
      enum:
      - GeneralError
    message:
      type: string
FieldError:
  type: object
  required:
  - type
  - message
  - key
  properties:
    type:
      type: string
      enum:
      - FieldError
    message:
      type: string
    key:
      type: string

Describe the solution you'd like
The goal is to support the discrimator pattern as describe here -> https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/

The previous example will become:

Error:
  oneOf:
  - "$ref": "#/components/schemas/GeneralError"
  - "$ref": "#/components/schemas/FieldError"
  discriminator:
    propertyName: type
    mapping:
      GeneralError: "#/components/schemas/GeneralError"
      FieldError: "#/components/schemas/FieldError"
GeneralError:
  type: object
  required:
  - type
  - message
  properties:
    type:
      type: string
    message:
      type: string
FieldError:
  type: object
  required:
  - type
  - message
  - key
  properties:
    type:
      type: string
    message:
      type: string
    key:
      type: string

The generated types for the two previous examples should be the same:

export interface GeneralError {
  type: "GeneralError";
  message: string;
}

export interface FieldError {
  type: "FieldError";
  message: string;
  key: string;
}

export type Error = GeneralError | FieldError;

Get Component fails to resolve path without RestfulProvider's base prop

Describe the bug
If Get Component is used without RestfulProvider top-level configuration, the path resolution adds a preceding slash to endpoints. This does not happen with the useGet method. It works as expected.
For example, the below code will result in incorrect get request of
/https://dog.ceo/api/breeds/image/random

const App = () => {
  <Get path="https://dog.ceo/api/breeds/image/random">
    {randomDogImage => (
      <img
        alt="Here's a good boye!"
        src={randomDogImage && randomDogImage.message}
      />
    )}
  </Get>;
};

To Reproduce
Steps to reproduce the behavior:

  1. Create a new React/React-Native Project
  2. yarn add restful-react
  3. Add the Get Component in your App.tsx with a url to fetch data.
  4. Check the actual fetched URL in Network tab

Reproducible Demo

Expected behavior
Even without the global configuration of RestfulProvider, Get Component should parse the right URL and fetch the data.

Screenshots
N/A

Desktop (please complete the following information):

  • OS: macOS 15.1
  • Browser : Chrome/Safari/Firefox
  • Version : 9.2.0

Smartphone (please complete the following information):
N/A

Additional context
N/A

Does this work with react native

I am building an app in react native and I require to poll an API. Will this library be useful for me in react native for polling?

Add CodeSandbox examples and link in README.md

Why

Proper, real-world examples (maybe using the Dogs API) would help people understand the power of this tool and how it works fully: including examples for state handling, polling, and other advanced features of this library.

How

Head on over to CodeSandbox, create examples, and submit a PR with links at relevant places in the README.

Move openapi validator to speccy

Is your feature request related to a problem? Please describe.

The ibm validator is nice, but way to strict… no un-usuable in practice (for example type: uuid is an error), let's move to speccy from our old friends -> wework!

https://github.com/wework/speccy

Describe the solution you'd like
We need to way to enable/disable the validation and specify some custom rules from restful-react generate options.

Describe alternatives you've considered
https://github.com/IBM/openapi-validator already in place, but sadly always disabled because it's too strict…

Additional context
The main idea to have a validator/linter part of our tool is to provide a better message when specs are not valid. Restful-react assume that the spec is perfectly valid for now, so it can break with a non helping message… (at least it can be better)

Also, our backend team already use speccy as validator in CI 😉

Declarative API for useGet

Declarative API

Current API has non-declarative parts, for example, cancel, refetch, lazy.

Purely declarative API would not have this, instead it would do this

  • to cancel unmount component with hook
  • to refetch bump key of component with hook
  • lazy not needed, but we can add skip, to prevent immediate fetching (setting skip to true will cancel current request)

Refetch vs cache

How refetch will behave in presence of cache?

Without refetch we can send request of each cache miss e.g. if component remounts and there is no fresh data in the cache it will submit request again. Not typical API though 🤔

Not sure where I'm going with this. WDYT?

Handle offline error in onError of the RestfulProvider

As a library user, I would like to be notified about the network error using the onError callback of the <RestfulProvider/> component.

Test case: switch on offline tick in the Crome Dev Tools before triggering the request

Current: request fails but no error callback is triggered

Expected: the callback should be called and return a network error

An in-range update of ts-jest is breaking the build 🚨

The devDependency ts-jest was updated from 23.1.4 to 23.10.0.

🚨 View failing branch.

This version is covered by your current version range and after updating it in your project the build failed.

ts-jest is a devDependency of this project. It might not break your production code or affect downstream projects, but probably breaks your build or test tools, which may prevent deploying or publishing.

Status Details
  • continuous-integration/travis-ci/push: The Travis CI build failed (Details).
  • Travis CI - Branch: The build failed.

Release Notes for 23.10.0

ts-jest, reloaded!

  • lots of new features including full type-checking and internal cache (see changelog)
  • improved performances
  • Babel not required anymore
  • improved (and growing) documentation
  • a ts-jest Slack community where you can find some instant help
  • end-to-end isolated testing over multiple jest, typescript and babel versions
Commits

The new version differs by 293 commits.

  • 0e5ffed chore(release): 23.10.0
  • 3665609 Merge pull request #734 from huafu/appveyor-optimizations
  • 45d44d1 Merge branch 'master' into appveyor-optimizations
  • 76e2fe5 ci(appveyor): cache npm versions as well
  • 191c464 ci(appveyor): try to improve appveyor's config
  • 0f31b42 Merge pull request #733 from huafu/fix-test-snap
  • 661853a Merge branch 'master' into fix-test-snap
  • aa7458a Merge pull request #731 from kulshekhar/dependabot/npm_and_yarn/tslint-plugin-prettier-2.0.0
  • 70775f1 ci(lint): run lint scripts in series instead of parallel
  • a18e919 style(fix): exclude package.json from tslint rules
  • 011b580 test(config): stop using snapshots for pkg versions
  • 7e5a3a1 build(deps-dev): bump tslint-plugin-prettier from 1.3.0 to 2.0.0
  • fbe90a9 Merge pull request #730 from kulshekhar/dependabot/npm_and_yarn/@types/node-10.10.1
  • a88456e build(deps-dev): bump @types/node from 10.9.4 to 10.10.1
  • 54fd239 Merge pull request #729 from kulshekhar/dependabot/npm_and_yarn/prettier-1.14.3

There are 250 commits in total.

See the full diff

FAQ and help

There is a collection of frequently asked questions. If those don’t help, you can always ask the humans behind Greenkeeper.


Your Greenkeeper Bot 🌴

Mutation error management

The actual problem

image

The actual implementation is throw response on error. So for the following implementation:

const BundleImport = () => 
<Mutation verb="POST" path="/">
{(post, {rootError}) => (
  <form onSubmit={() => post({/* my data */}).then(/* my reponse*/).catch(/* error */)}>
   { /* my inputs */}
  </form>
)}
</Mutation>
  • I can't retrieve the error response in the /* error */ block, because the body is already consume (response.json() can be call only one time)
  • I have the error body in rootError

The problem is that with this configuration it's quite hard to clean the error state on user input (the rootError is managed by Mutation internally) and it's a bit tricky how to handle errors (rootError vs .catch())

Proposal

I think that the simple way to manage these errors, is send all errors on catch block and don't polluate the rootError.

So to manage errors, we can simply do this:

<Mutation verb="POST" path="/">
{(post, {rootError}) => (
  <form onSubmit={() => 
    post({/* my data */})
       .then(/* my reponse*/)
       .catch(error => console.log(error)) // { data: responseData, message: `Failed to fetch: ${response.status} ${response.statusText}` }
    }
   >
   { /* my inputs */}
  </form>
)}
</Mutation>

So we can easily this.setState to store every errors, and deal with custom ux cases as remove the error message when the user begin to type.

Cancel requests

Again cool lib.

The logic for cancelling requests on unmount doesn't work, getting:

image

In useGet:

image

Cover components with unit tests

Why

Unit tests are good and allow us to have predictable software.

What

Currently, components like Get and Poll work reliably and beautifully, but are not covered with unit tests. It would be nice if we had them covered with test in order to prevent side effects and surprises when consuming these components.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.