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!
npm i --save react-loading-switchIn 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.
With react-loading-switch, we won't need this:
constPuppy=({ loading, error, puppy })=>{if(error){return<RenderErrorerror={error}/>}if(!puppy){if(loading){return<RenderLoading/>}return<RenderErrorerror={newError('Missing puppy data!')}/>}return(<View>{`Finally the puppy is here! ${puppy.id}`}</View>)}We won't need this:
constPuppy=({ loading, error, puppy })=>{if(loading)return<RenderLoading/>if(error)return<RenderErrorerror={error}/>return<View>{`Finally the puppy is here! ${puppy.id}`}</View>}- 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.
This example uses all available props, but in practice it gets cleaner:
importLoadingSwitchfrom'react-loading-switch'constPuppy=({ loading, error, puppy })=>(<LoadingSwitcherror={error}errorWhenMissing={()=>newError('Missing puppy data!')}loading={loading}renderError={(error)=><DataErrorerror={error}/>}renderLoading={()=><Loading/>}require={puppy}>{()=>(<View>{`The puppy data is here! ${puppy.id}`}</View>)}</LoadingSwitch>)Share identical behavior across similar components 👩👦👦
importLoadingSwitchfrom'react-loading-switch'exportconstPuppyLoadingSwitch=(props)=>(<LoadingSwitcherrorWhenMissing={()=>newError('Could not find puppy!')}renderLoading={()=><p>Loading puppies...</p>}renderError={(error)=><p>Error: {error.message}</p>}{...props}/>)Now we're talkin' 🎉
importPuppyLoadingSwitchfrom'../PuppyLoadingSwitch'constPuppy=({ loading, error, puppy })=>(<PuppyLoadingSwitcherror={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!
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
constPuppyBirthday=({ loading, error, data})=>(<PuppyLoadingSwitch/* ... */require={data&&data.puppy}>{({ name, birthday })=>(<View>{`${name}'s birthday is ${birthday}!`}</View>)}</PuppyLoadingSwitch>)importPuppyLoadingSwitchfrom'../PuppyLoadingSwitch'import{Query}from'react-apollo'constGET_PUPPY=gql` query puppy($puppyId: ID!) { puppy(id: $puppyId) { id name birthday } }`;constPuppyBirthday=({ puppyId })=>(<Queryquery={GET_PUPPY}variables={{ puppyId }}>{({ loading, error, data})=>(<PuppyLoadingSwitcherror={error}loading={loading}require={data&&data.puppy}>{({ name, birthday })=>(<View>{`${name}'s birthday is ${birthday}!`}</View>)}</PuppyLoadingSwitch>)}</Query>)Falsey in JavaScript: false || null || undefined || 0 || '' || NaN
constPuppy=({ loading, error, someData, moreData })=>(<PuppyLoadingSwitch/* ... */require={someData&&moreData&&moreData.foo}>{()=>(<View>{moreData.foo.name}</View>)}</PuppyLoadingSwitch>)See the test/ directory in this repo for detailed snapshot tests that cover the whole API.
Most of the React-Apollo example apps use this pattern, where loading takes precedence:
exportconstCharacter=withCharacter(({ loading, hero, error })=>{if(loading)return<div>Loading</div>;// ...Excerpt from the apollo-client README
However, just because
data.loadingis true it does not mean that you won’t have data. For instance, if you already havedata.todos, but you want to get the latest todos from your APIdata.loadingmight 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.
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){returnrenderError(error)}if(!require){if(loading){returnrenderLoading()}if(errorWhenMissing){returnrenderError(typeoferrorWhenMissing==='function' ? errorWhenMissing() : errorWhenMissing)}}returnchildren(require)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.
importPuppyLoadingSwitchfrom'../PuppyLoadingSwitch'constPuppy=({ loading, error, puppy })=>(<PuppyLoadingSwitcherror={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:
importPuppyLoadingSwitchfrom'../PuppyLoadingSwitch'constPuppy=({ loading, error, puppy })=>(<PuppyLoadingSwitcherror={error}loading={loading}require={!loading}>{()=>(<View>{`We are not loading! ${puppy.id}`}</View>)}</PuppyLoadingSwitch>)