Coder Social home page Coder Social logo

react-fetch-component's Introduction

react-fetch-component CircleCI

React component to declaratively fetch data

Install

yarn add react-fetch-component

or

npm install --save react-fetch-component

Usage

You supply a single function as a child of <Fetch /> which receives a single argument as an object. The function will be called anytime the state of the fetch request changes (for example, before a request has been made, while the request is in flight, and after the request returned a response).

While you can pass a single property to the function (for example, (fetchProps) => ...), it is common to instead use object destructuring to peel off the properties on the object you plan to use.

An example of destructing and using the most common properties loading, error, and data.

<Fetch url="someUrl">
  {({ loading, error, data }) => (
    <div>
      {loading &&
        {
          /* handle loading here */
        }}
      {error &&
        {
          /* handle error here */
        }}
      {data &&
        {
          /* handle data here */
        }}
    </div>
  )}
</Fetch>

You can also use React's context via <Fetch.Consumer> for accessing the state in a deep tree (or to create components based on state)

const Loading = () => <Fetch.Consumer>{({ loading }) => loading ? <MySpinner /> : null}</Fetch.Consumer>
const Error = () => <Fetch.Consumer>{({ error }) => error ? <MyError error={error} /> : null}</Fetch.Consumer>

<Fetch url="someUrl">
  <div>
    <div>
      <div>
        <Loading />
        <Error />
        <Fetch.Consumer>
          {({ data }) => <div>{ data ? /* handle data here */ : null}</div>}
        </Fetch.Consumer>
      </div>
    </div>
  </div>
</Fetch>

Props

  • url (string) - address of the request. Initial fetch will only be created when it's a non-empty string. You can initially set this to undefined, false, or an empty string to delay the fetch to a later render.
  • options (object|function) - request options such as method, headers, credentials, etc. If passed as a function, it will not be evaluated until the request is sent, which is useful when calling expensive methods like JSON.stringify for options.body for example.
  • as - declare how to handle the response body
    • default: auto (will attempt to parse body based on Content-Type header)
    • can either be a string for any of the body methods including:
      • arrayBuffer
      • blob
      • formData
      • json
      • text
    • or a function that takes in the response and returns a Promise. For example <Fetch as={res => res.text()} />
    • or an object that maps the Content-Type of the response to a function that takes in the response and returns a Promise. For example <Fetch as={{ 'application/json': res => JSON.parse(res.text(), customReviver)}} />. html, json, xml, and other are also available for simplification.
  • cache (boolean|object)
    • If set, will cache responses by url and return values from cache for matches urls without issues another request. Useful for typeahead features, etc.
    • If true, will use an instance of SimpleCache per component instance
    • Can supply an instance with get(url) and set(url, promise) methods. Passing an instance of SimpleCache allows for multiple instances to share the same (simple) cache
    • Other implementations of a cache can be supplied for more control (LRU, persist to local/sessionStorage, etc)
    • default: false
  • manual (boolean) - If true, requires calling fetch explicitly to initiate requests. Useful for better control of POST/PUT/PATCH requests.
    • default: false
  • onDataChange (function) - Function called only when data is changed. It is called before onChange, and if a result is returned (i.e. not undefined), this value will be used as data passed to onChange and the child function instead of the original data. onDataChange also receives the current data as the second parameter, which allows for concatenating data (ex. infinity scroll).
  • onChange (function) - Function called with same props as child function. Useful to call setState (or dispatch a redux action) since this is not allowed within render. onChange will always be called even if <Fetch /> component has been unmounted.
    • default: undefined
  • fetchFunction (function) - Specify own fetch function. Useful to debounce fetch requests (although probably best to debounce outside of <Fetch /> so not call unneccessary renders)
    • default: window.fetch

Object properties passed to child function

  • loading

    • Set to true while request is in flight
    • Set to false once response has returned
    • Set to null when manual={true} before fetch() is called
  • error

    • Is undefined while request is pending
    • Will be set to the parsed response body (json by default) if !response.ok (i.e. status < 200 || status >= 300)
    • Will be set to an Error instance if thrown during the request (ex. CORS issue) or if thrown while attemping to parse the response body, such as returning text/html when json was expected (which is the default parser)
    • Will remain undefined if neither of the previous occur
  • data

    • Is undefined while request is pending
    • Set to parsed response body (json by default) unless one of the error conditions occur
  • request

    • Set to an object containing the props passed to the component (url, options, etc) when request is sent.
    • Added for convenience when <Fetch /> is wrapped by your own data component (ex. <UserData />)
  • response

    • Set to the response of the fetch call
    • Useful to check the status code/text, headers, etc
  • fetch

    • Function that can be called to create a new fetch request (useful when last request had an error or you want to manually refresh the data (see manual prop))
    • The first 2 parameters match window.fetch (url, options). A third parameter (updateOptions) is available to pass options to the update phase (where onChange, onDataChange, and the child render function is called). Currently only 1 option is available (ignorePreviousData) which passes undefined as the current data (second parameter) to onDataChange, which is useful when using onDataChange to concatenate data across requests (ie. infinite loading) and the query changes
  • clearData

    • Function to clear data state.

Examples

Include credentials

<Fetch url="someUrl" options={{ credentials: 'include' }}>
  {/* ... */}
</Fetch>

More interactive examples on CodeSandbox

See also

react-fetch-component's People

Contributors

aaronjensen avatar davin-english-tfs avatar flamenaak avatar lediur avatar rodolfosilva avatar techniq avatar tiodan81 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

react-fetch-component's Issues

Uncaught (in promise) TypeError: failed to fetch

Fatal fetch errors are not caught and passed as props to the render callback.

To reproduce, make a fetch to any URL that does not support CORS (i.e. https://example.org). This will immediately fail to fetch the URL because the server does not respond with a Access-Control-Allow-Origin header.

Because the error occurs before the request is made, a response is never created. This causes the existing error handler to never run and thus allows errors to go uncaught.

This can be easily remedied by adding a catch clause to the Promise to catch any error and handle it appropriately through your error handler:

.catch((error) => {
  const newState = {
    data: undefined,
    error,
    loading: false
  }
  this.setStateIfMounted(newState)
  return newState
});

Allow for fetching cached data

Hi! Thanks so much for your work on this package; overall I really like the API!

I was wondering if there's any way to allow for fetching data on mount/update even when that data has been cached? I know that seems to go against the purpose of a cache, but I'd like to optimistically show data that's already been loaded and doesn't change often (and possibly been cached for a longer period of time, like in localStorage/sessionStorage) but still fetch in case any changes have happened. This is for an enterprise application which receives fairly low traffic, so I don't need caching for load reasons.

From my reading of the fetch method, it looks like this isn't something I could do with a custom Cache =/

error remains an empty object

I purposefully sent incorrect cors-mode to a url.

Fetch API cannot load https://swapi.co/api/films/. Request mode is "same-origin" but the URL's origin is not same as the request origin http://localhost:7331.

prints to my console but error as a renderProp remains {}

Supply caching strategy

Currently we have a single cache prop as a bool that uses an object to store the {url: Promise} for each request if enabled and checks if an entry exists (see here)

If would be nice if you could pass in your own cache instance to support:

I think it should be as simple as supporting passing an object to cache that has a get and set function that returns Promises (need to support multiple in-flight requests waiting on the same request).

class SimpleCache {
  get(url) {
    // load from internal object, read from local storage, etc
  }

  set(url, result) {
    // set to internal object, set to local storage, etc
  }
}

const myCache = new SimpleCache()

<Fetch cache={myCache} />

If you wanted to clear the cache, you would do that on your own cache instance (if provided), such as:
myCache.clear()

Support refetching if `options.body` changes, even if url doesn't

In the version that I'm using,

if (url && url !== prevProps.url && !manual) {
specifies that we shouldn't refetch if the url hasn't changed.

In my code I'm using this component with a POST call with a body, where the user can modify the page state to change the body, which I'd like to trigger a refetch, but since the URL is the same, it doesn't. I worked around this by adding a fragment to the end of the URL that takes a hash of the body, but it'd be nice if this just worked by default. At minimum, it would be good if this behavior was documented, since I had to spend a decent amount of time poking around before I figured it out.

Is this project abandoned?

Was curious if api could be extended, with things like skip, fetchMore, refetch which are implemented in react-apollo/Query

Support async onDataChange

It would be very useful if you could return a Promise from onDataChange to allow using asynchronous APIs (callbacks/promises) within it. For example you could process some blob data using FileReader (untested example, but general idea);

<Fetch
  url="/someFile"
  as="blob"
  manual
  onDataChange={data => {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = e => resolve(e.target.result);
      reader.readAsDataURL(data);
    });
  }}
>
  {({ data: dataUrl, fetch }) => (
    data ? (
      <iframe src={dataUrl} />
    ) : (
      <a onClick={() => fetch()}>Download</a>
    )
  )
</Fetch>

or if onDataChange could be async

async function getDataUrl(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = e => resolve(e.target.result);
    reader.readAsDataURL(data);
  });
}

<Fetch
  url="/someFile"
  as="blob"
  manual
  onDataChange={async data => {
    const dataUrl = await getDataUrl(data);
    return dataUrl;
  }}
>
  {({ data: dataUrl, fetch }) => (
    data ? (
      <iframe src={dataUrl} />
    ) : (
      <a onClick={() => fetch()}>Download</a>
    )
  )
</Fetch>

Add explicit "onDataChange" property

Currently we overload onChange and if it does not return undefined, we replace the data with the result of onChange. This is error prone (dispatching redux functions in onChange, not handling reloading states properly, etc).

Instead it would be better if we have an explicit onDataChange handler that is only called when data is returned (before here and only when data is changed)

Questions to think about

  • Should we call it onDataChange or processData
  • If called onDataChange, is it obvious it is called before onChange and will modify data handed to it
  • onDataChange could simplify uses of onChange. Example: onChange={({data}) => data && dispatch(someAction(data)) onDataChange(data => dispatch(someAction(data))

Only retain successful responses in cache

Hi @techniq,

I saw you had a TODO on SimpleCache: // TODO: only retain successful responses?

Not sure how high this is on your priority list, but it would be a great help for my use-case (and I'm sure others). If there's a brief network error when making a fetch call, the URL gets set in the cache even though it errored. As such, subsequent calls are never made.

I'm currently working around this by removing the URL from the cache in the error callback, but it would be nice if the lib handled it for me (even if it's a prop on the Fetch component itself so users aren't forced into not caching unsuccessful calls). Having the lib handle it would remove a fair amount of boilerplate code where I'm doing the same workaround.

How to refetch data if error occured

Hey!
I have a case where when the error while fetching occurs, I would give the user ability to try again using a button.
Would be nice to have the instance of current component, because I use the same functions for multiple components.

Regards,
TheAifam5.

Support Suspense

Will need to wait for react-cache to be officially released (which is currently slated for mid 2019).

An implementation is available that rolled it's own caching (https://github.com/CharlesStover/fetch-suspense), although this is likely not production ready (cache invalidation, etc)

Mostly creating this as a feature tracking issue for now.

Smart body parsing based on Content-Type

Instead of defaulting to json for body parsing, it would be better if we read the response's Content-Type header and parsed accordingly. This would also better handle when the endpoint changes types (ex. some frameworks will return a HTML debug page when a 500 is thrown, which breaks when we expect an endpoint to return JSON).

After a quick look around, it appears fetch-parse has a good start on the mapping between Content-Type and parsers.

Probably look at using one of the following to parse the header / mime type, as it can be fairly involved to cover all cases defined by the spec.

Lastly, it would be nice to support passing a function to as to allow the user to perform the body parsing mapping themselves (might be needed to determine whether they need blob or arrayBuffer.

Add support for Hooks

Support useFetch hooks API

function Example(props) {
  const { loading, data, error } = useFetch({ url: props.url });
  // ...
}

along with the existing Fetch render props component

function Example(props) {
  <Fetch url={props.url}>
    {({ loading, data, error }) => (
      // ...
    )}
  </Fetch>
}

To support consistency with the Fetch component, the useFetch hook will take in a props object for url, options, etc.

I might consider change the name of the package to better represent it's not just a component (but also a hook). I'm considering using a scoped @techniq/react-fetch instead of trying to come up with a unique name.

TODO

  • Update README
  • Switch to Typescript
  • Wait for official React release with hooks

Support aborting

Now that fetch supports aborting using AbortController via a signal, it would be nice if we did as well. Currently we track whether the component is mounted and not call setState, but properly cancelling the fetch request would be better, especially since it's considered an anti-pattern

Some details:

It's only supported in recent green browsers (Chrome, Firefox, Edge, Safari (desktop) and not IE, iOS Safari (currently) so we will need to support opting in / polyfiling.

A quick search returned this polyfill, and Netflix's yetch (which you should be able to pass to the fetchFunction prop

Maybe this is an opt-in via cancellable prop, and we continue to track mounted otherwise.

`loading` initially starts as `null`?

Maybe I'm doing something wrong, but this seems weird to me. Here's my code:

<Fetch url="/in-progress">
    {({loading, error, data}) => {
        console.log(loading,error,data);
        if(!data) {
            return <p>Loading...</p>
        }
    }}
</Fetch>

And the output:

image

Loading is null on the first invocation. Why is that? I would think it'd start as true. this prevents me from doing if(loading) for my condition!

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.