npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-loading-switch

v1.2.0

Published

React component API for easily composing the render logic surrounding react-apollo data fetching, loading, and error handling

Downloads

3,538

Readme

react-loading-switch 🐶

React component API for easily composing the render logic surrounding react-apollo data fetching, loading, and error handling.

Compatible with React, React Native, React Web, React anything!

Getting Started

npm i --save react-loading-switch

Why?

Data-related conditional rendering code mucking up our render functions

In our experience, re-writing identical or similar logic in every component can lead to problems ❌

  • Multiple programming styles result in different-looking code.
  • Difficult to digest at a glance.
  • Easy to make a mistake if hard-coding everywhere.
  • These problems grow as the codebase grows.
  • Wasted brain cycles thinking about it, writing it, reviewing it.

Say goodbye to if (loading) and if (error) 👋

With react-loading-switch, we won't need this:

const Puppy = ({ loading, error, puppy }) => {
  if (error) {
    return <RenderError error={error} />
  }

  if (!puppy) {
    if (loading) {
      return <RenderLoading />
    }

    return <RenderError error={new Error('Missing puppy data!')} />
  }

  return (
    <View>{ `Finally the puppy is here! ${puppy.id}` }</View>
  )
}

We won't need this:

const Puppy = ({ loading, error, puppy }) => {
  if (loading) return <RenderLoading />
  if (error) return <RenderError error={error} />

  return <View>{ `Finally the puppy is here! ${puppy.id}` }</View>
}

Instead, compose this logic with react-loading-switch

  • Consistent JSX component API.
  • Easy to digest at a glance.
  • Extensible & Functional
  • Optionally centralize a shared configuration across many components.
    • It's just a react component. Wrap it with some default props and export.

Hello <LoadingSwitch /> 🍻

This example uses all available props, but in practice it gets cleaner:

import LoadingSwitch from 'react-loading-switch'

const Puppy = ({ loading, error, puppy }) => (
  <LoadingSwitch
    error={error}
    errorWhenMissing={() => new Error('Missing puppy data!')}
    loading={loading}
    renderError={(error) => <DataError error={error} />}
    renderLoading={() => <Loading />}
    require={puppy}
  >
    { () => (
      <View>{ `The puppy data is here! ${puppy.id}` }</View>
    ) }
  </LoadingSwitch>
)

DRY it up by wrapping with some default props 🤔

Share identical behavior across similar components 👩‍👦‍👦

import LoadingSwitch from 'react-loading-switch'

export const PuppyLoadingSwitch = (props) => (
  <LoadingSwitch
    errorWhenMissing={() => new Error('Could not find puppy!')}
    renderLoading={() => <p>Loading puppies...</p>}
    renderError={(error) => <p>Error: {error.message}</p>}
    {...props}
  />
)

Use <PuppyLoadingSwitch /> in every component that shares this logic

Now we're talkin' 🎉

import PuppyLoadingSwitch from '../PuppyLoadingSwitch'

const Puppy = ({ loading, error, puppy }) => (
  <PuppyLoadingSwitch
    error={error}
    loading={loading}
    require={puppy}
  >
    { () => (
      <View>{ `The puppy data is here! ${puppy.id}` }</View>
    ) }
  </PuppyLoadingSwitch>
)

You can use one LoadingSwitch component for your entire application, or you can use different LoadingSwitches in different areas. It's up to you!

The function-child prop / child-render prop receives the value of require

This optional feature allows us to avoid long property lookup chains in JSX. Compare the below to the above. Notice the lack of data.puppy.whatever

const PuppyBirthday = ({ loading, error, data}) => (
  <PuppyLoadingSwitch
    /* ... */
    require={data && data.puppy}
  >
    { ({ name, birthday }) => (
      <View>{ `${name}'s birthday is ${birthday}!` }</View>
    ) }
  </PuppyLoadingSwitch>
)

With React-Apollo <Query /> components

import PuppyLoadingSwitch from '../PuppyLoadingSwitch'
import { Query } from 'react-apollo'

const GET_PUPPY = gql`
  query puppy($puppyId: ID!) {
    puppy(id: $puppyId) {
      id
      name
      birthday
    }
  }
`;

const PuppyBirthday = ({ puppyId }) => (
  <Query query={GET_PUPPY} variables={{ puppyId }}>
    {({ loading, error, data}) => (
      <PuppyLoadingSwitch
        error={error}
        loading={loading}
        require={data && data.puppy}
      >
        { ({ name, birthday }) => (
          <View>{ `${name}'s birthday is ${birthday}!` }</View>
        ) }
      </PuppyLoadingSwitch>
    )}
  </Query>
)

Versitile require prop uses JavaScript truthy/falsey checking.

Falsey in JavaScript: false || null || undefined || 0 || '' || NaN

const Puppy = ({ loading, error, someData, moreData }) => (
  <PuppyLoadingSwitch
    /* ... */
    require={someData && moreData && moreData.foo}
  >
    { () => (
      <View>{ moreData.foo.name }</View>
    ) }
  </PuppyLoadingSwitch>
)

API

See the test/ directory in this repo for detailed snapshot tests that cover the whole API.

React-Apollo fetch policies

Most of the React-Apollo example apps use this pattern, where loading takes precedence:

export const Character = withCharacter(({ loading, hero, error }) => {
  if (loading) return <div>Loading</div>;
  // ...

fetchPolicy: 'cache-and-network' conflicts with the above

Excerpt from the apollo-client README

However, just because data.loading is true it does not mean that you won’t have data. For instance, if you already have data.todos, but you want to get the latest todos from your API data.loading might be true, but you will still have the todos from your previous request.

tl;dr we might still want to render the data we have, even if loading === true.

ReactLoadingSwitch considers data before loading

As long as there is no error, and require is truthy, it renders its children; even if loading === true. Now we can safely use the cache-and-network fetch-policy with no chance of seeing a loading state when we have data we could be rendering.

From src/LoadingSwitch.js

if (error) {
  return renderError(error)
}

if (!require) {
  if (loading) {
    return renderLoading()
  }

  if (errorWhenMissing) {
    return renderError(typeof errorWhenMissing === 'function' ? errorWhenMissing() : errorWhenMissing)
  }
}

return children(require)

However, it's easy to revert to the classic example behavior

In this example, renderLoading will be rendered if loading is truthy, even if we have some other data:

require={!loading && puppy}

Now when loading is truthy require evaluates falsey.

import PuppyLoadingSwitch from '../PuppyLoadingSwitch'

const Puppy = ({ loading, error, puppy }) => (
  <PuppyLoadingSwitch
    error={error}
    loading={loading}
    require={!loading && puppy}
  >
    { () => (
      <View>
        { `We are not loading and the puppy data is here! ${puppy.id}` }
      </View>
    ) }
  </PuppyLoadingSwitch>
)

Or, if we only care about loading and error, we don't need to check for data presence:

import PuppyLoadingSwitch from '../PuppyLoadingSwitch'

const Puppy = ({ loading, error, puppy }) => (
  <PuppyLoadingSwitch
    error={error}
    loading={loading}
    require={!loading}
  >
    { () => (
      <View>
        { `We are not loading! ${puppy.id}` }
      </View>
    ) }
  </PuppyLoadingSwitch>
)