Skip to content

Instantly share code, notes, and snippets.

@Oskang09
Last active November 9, 2021 14:57
Show Gist options
  • Save Oskang09/e7fb2950ef6acf1b0a8842336cd03e19 to your computer and use it in GitHub Desktop.
Save Oskang09/e7fb2950ef6acf1b0a8842336cd03e19 to your computer and use it in GitHub Desktop.
Async Provider Components

Types:

type PromiseAction : {
    // For reloading specified promise to re-run by name
    reload: Function<String | Array<String>>,
    // For direct updating value to a promise instead of re-run ( reload will still work after update )
    update: Function<Map<String, Any>>,
}

type PromiseResponse : {
  [key]: Any
}

Provider Component:

Props: {
    promise: Map<String, Promise>
}

<AsyncProvider promise={}}>
    
</AsyncProvider>

Consumer Component:

Props : {
    promise: Array<String> | String,
    loading: React.Node | Function<React.Node>,
    error: React.Node | Function<React.Node>,
    children: Function<PromiseResponse, PromiseAction> | Function<PromiseAction>,
}

// Render only when promise resolved, return `PromiseResponse` & `PromiseAction`
<Async promise={[ "value" ]} loading={() => ()} error={() => ()}>
  {
    ({ value }, { reload, update }) => ()
  }
</Async>

// Render directly, return `PromiseAction`.
<Async>
  {
    ({ reload, update }) => ()
  }
</Async>

Example Usage:

import React from 'react';
import Async, { Provider as AsyncProvider } from 'AsyncProvider';

export default class App extends React.Component {
    render() {
       <AsyncProvider
            promise={{
                value: () => Promise.resolve(1),
                timeout: () => new Promise(
                    (resolve) => setTimeout(
                        () => resolve(1),
                        3000
                    )
                )
            }}
       >
          <Async>
            {
                ({ reload, update }) => (
                    <View>
                        <Button onPress={() => reload('timeout')}>RELOAD</Button>
                        <Button onPress={() => update({ timeout: 10 })}>UPDATE</Button>
                    </View>
                )
            }
          </Async>
          <Text>|| Just a seperator ||</Text>
          <Async
            promise={[ "value" ]}
            loading={<Text>LOading</Text>}
            error={<Text>Errors</Text>}
          >
            {
                ({ value }) => <Text>Value : {value}</Text>
            }
          </Async>
          <Text>|| Just a seperator ||</Text>
          <Async
            promise={[ "value", "timeout" ]}
            loading={<Text>Loading</Text>}
            error={(error) => <Text>{error.message}</Text>}
          >
            {
                ({ value, timeout }, { reload, update }) => <Text>Value : {value}, Timeout: {timeout}</Text>
            }
          </Async>
       </AsyncProvider>
    }
};
import React from 'react';
import PropTypes from 'prop-types';
const AsyncContext = React.createContext();
AsyncContext.displayName = 'AsyncContext';
class AsyncProvider extends React.Component {
static propTypes = {
promise: PropTypes.object,
}
state = {
promiseState: {},
}
UNSAFE_componentWillMount() {
this._mounted = true;
this.reload();
}
componentWillUnmount() {
this._mounted = false;
}
update = (promiseMap) => {
const { promiseState } = this.state;
const newState = {};
for (const key of Object.keys(promiseMap)) {
newState[key] = {
loading: false,
error: null,
result: promiseMap[key]
};
}
return this.setState({
promiseState: Object.assign(
promiseState,
newState
)
});
}
reload = async (keys = Object.keys(this.props.promise)) => {
if (!this._mounted) {
return;
}
const { promise } = this.props;
const { promiseState } = this.state;
for (const key of keys) {
promiseState[key] = {
...promiseState[key],
loading: true,
result: null,
};
}
this.setState({ promiseState });
for (const key of keys) {
try {
promiseState[key] = {
...promiseState[key],
loading: false,
result: promise[key] ? await promise[key] : null
};
} catch (error) {
promiseState[key] = {
...promiseState[key],
loading: false,
error,
};
}
this.setState({ promiseState });
}
}
render() {
return (
<AsyncContext.Provider
children={this.props.children}
value={{
promiseState: this.state.promiseState,
update: this.update,
reload: this.reload,
}}
/>
);
}
};
class AsyncConsumer extends React.PureComponent {
static contextType = AsyncContext;
static propTypes = {
promise: PropTypes.array,
loading: PropTypes.node,
error: PropTypes.oneOf(PropTypes.node, PropTypes.func),
children: PropTypes.func,
}
render() {
const { promise, loading, error, children } = this.props;
const { promiseState, update, reload } = this.context;
if (!promise) {
return typeof children === 'function' ? children({ reload, update }) : children;
}
const keys = Array.isArray(promise) ? promise : [promise];
const state = {};
for (const key of keys) {
const currentState = promiseState[key];
if (currentState) {
if (currentState.loading) {
return typeof loading === 'function' ? loading() : loading;
}
if (currentState.error) {
return typeof error === 'function' ? error(currentState.error, { reload, update }) : error;
}
}
if (!currentState) {
state[key] = null;
} else {
state[key] = currentState.result;
}
}
return typeof children === 'function' ? children(state, { reload, update }) : children;
}
};
export const Provider = AsyncProvider;
export const Consumer = AsyncConsumer;
export default AsyncConsumer;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment