A component to asynchronously load any object into the react context, for example when using Code Splitting.
Just like the new context API, this function returns:
Provider
, which injects the loaded object into the context as soon as the promise is resolvedConsumer
components, which gracefully handle loading and error states.This is particularly usefull when creating libraries which interface react with third party heavy libraries, such as Cesium, vis.js, openlayers and others. The main "wrapper" component for the library uses the Provider
. The various "elements" of the library use the Consumer
.
Losely inspired by react-loadable and the new context API
In this example the loader resolves a promise immediately.
const { Provider, Consumer } = LoadableContext({
loader: () => "Loaded simply."
});
<Provider>
<Consumer>{data => data}</Consumer>
</Provider>
In this example the loader resolves a promise after a delay.
const { Provider, Consumer } = LoadableContext({
loader: async () => {
await delay(3000);
return "Loaded after a delay.";
}
});
<Provider>
<Consumer>{data => data}</Consumer>
</Provider>
In this example the loader loads an external chunk using code splitting, with the asynchronous import statement.
const { Provider, Consumer } = LoadableContext({
loader: async () => await import("./thingToLoad")
});
<Provider>
<Consumer>{thingToLoad => thingToLoad.theMessage}</Consumer>
</Provider>
// thingToLoad.js
export const theMessage =
"Loaded asynchronously using code splitting from another file.";
In this example we use a loading placeholder component to keep the user informed about what is going on.
import Loading from "./loading";
const { Provider, Consumer:Consumer } = LoadableContext({
loader: async () => {
await delay(3000);
return "Loaded after a delay and detailed info.";
},
loading: Loading,
delay: 1000,
timeOut: 2000
});
<Provider>
<ConsumerLoading>{thingToLoad => thingToLoad.theMessage}</ConsumerLoading>
</Provider>
// loading.js
import * as React from "react";
type Props = {
retry?: mixed => mixed,
loading?: boolean,
error?: mixed,
timedOut?: boolean,
pastDelay?: boolean
};
export default function Loading(props: Props) {
if (props.loading) {
if (props.timedOut) {
return (
<div>
Loader timed out!
<button onClick={props.retry}>Retry loading</button>
</div>
);
} else if (props.pastDelay) {
return (
<div>
Loading is taking some time (display some activity indicator here)
</div>
);
} else {
return (
<div>
Before delay (display some placeholder or skeleton component here)
</div>
);
}
} else if (props.error !== null && props.error !== undefined) {
return (
<div>
{props.error.toString()}
<button onClick={props.retry}>Retry loading</button>
</div>
);
} else {
throw new Error(
"This should not happen, but react loadable context sent wrong props to loading component"
);
}
}
In this example, the promise throws an error.
import Loading from "./loading";
const { Provider, Consumer:Consumer } = LoadableContext({
loader: async () => {
await delay(3000);
throw new Error("Not okay !");
},
loading: Loading,
delay: 1000,
timeOut: 2000
});
<Provider>
<ConsumerLoading>{thingToLoad => thingToLoad.theMessage}</ConsumerLoading>
</Provider>
In this example we compose several loaders. See also react-composer, which helps dealing with that.
const { Provider: Provider1, Consumer: Consumer1 } = LoadableContext({
loader: async () => "Loaded context number 1."
});
const { Provider: Provider2, Consumer: Consumer2 } = LoadableContext({
loader: async () => "Loaded context number 2."
});
const { Provider: Provider3, Consumer: Consumer3 } = LoadableContext({
loader: async () => "Loaded context number 3."
});
<Provider1>
<Provider2>
<Provider3>
<Consumer1>{data => data}</Consumer1>
<Consumer2>{data => data}</Consumer2>
<Consumer3>{data => data}</Consumer3>
</Provider3>
</Provider2>
</Provider1>
In this example the consumer is the raw react context consumer, it receives more info than just the loader result.
const { Provider, ConsumerRaw } = LoadableContext({
loader: async () => {
await delay(3000);
return "Loaded!";
},
delay: 1000,
timeOut: 2000
});
<Provider>
<ConsumerRaw>
{({ data, error, loading, timedOut, pastDelay }) =>
JSON.stringify(
{ data, error, loading, timedOut, pastDelay },
null,
2
)
}
</ConsumerRaw>
</Provider>
In this example the consumer uses the setData
and getData
to directly modify the context's data.
const { Provider, Consumer, ConsumerRaw } = LoadableContext({
loader: async () => {
await delay(3000);
return "Initially loaded data !";
}
});
<Provider>
<Consumer>{data => data}</Consumer>
<ConsumerRaw>
{({ setData }) => (
<button
onClick={() =>
setData(
"New data set manually at " + new Date().toTimeString()
)
}
>
Set data
</button>
)}
</ConsumerRaw>
</Provider>
In this example the loader uses props sent to the provider.
const { Provider, Consumer, ConsumerRaw } = LoadableContext({
loader: async props => {
await delay(props.delay);
return props.resultToShow;
}
});
<Provider delay={5000} resultToShow="Hey">
<Consumer>{data => data}</Consumer>
</Provider>