Skip to content

Instantly share code, notes, and snippets.

@lkostrowski
Created May 29, 2020 09:42
Show Gist options
  • Save lkostrowski/3e92be00e31f7331ab67691d93a090fd to your computer and use it in GitHub Desktop.
Save lkostrowski/3e92be00e31f7331ab67691d93a090fd to your computer and use it in GitHub Desktop.
Component resolver
import React, {ComponentType} from 'react';
// Lib
declare function withDataResolving<TAppState, TReturnState, R1 = any>(injectSelectors: [(appState: TAppState) => R1], strategy: (data: R1) => TReturnState): <TProps extends {}>(Component: ComponentType<TProps>) => (props: TProps & TReturnState) => ComponentType<Exclude<TProps, TReturnState>>;
declare function withDataResolving<TAppState, TReturnState, R1 = any, R2 = any>(injectSelectors: [(appState: TAppState) => R1, (appState: TAppState) => R2], strategy: (data: R1, data2: R2) => TReturnState): <TProps extends {}>(Component: ComponentType<TProps>) => (props: TProps & TReturnState) => ComponentType<Exclude<TProps, TReturnState>>;
declare function withDataResolving<TAppState, TReturnState, R1 = any, R2 = any, R3 = any>(injectSelectors: [(appState: TAppState) => R1, (appState: TAppState) => R2, (appState: TAppState) => R3], strategy: (data: R1, data2: R2, data3: R3) => TReturnState): <TProps extends {}>(Component: ComponentType<TProps>) => (props: TProps & TReturnState) => ComponentType<Exclude<TProps, TReturnState>>;
// Example type
declare interface User {
name: string
}
// Example state-props for componanet
namespace UsersPageState {
export enum StateTag {
Loading, NoUsers, WithUsers, ErrorFetching
}
export type Loading = {
tag: StateTag.Loading
};
export type WithNoUsers = {
tag: StateTag.NoUsers
}
export type WithUsers = {
tag: StateTag.WithUsers;
users: User[];
}
export type ErrorFetching = {
tag: StateTag.ErrorFetching;
reason: string;
}
export type AllStates = Loading | WithUsers | WithNoUsers | ErrorFetching;
}
// Example app state
type AppState = {
users: User[];
error?: Error;
fetchingState: 'pending' | 'fetched' | 'error';
}
// Example redux selectors
declare module UsersSelector {
export function fetchingState(state: AppState): 'pending' | 'fetched' | 'error';
export function getUsers(state: AppState): User[];
export function fetchingError(state: AppState): Error[] | null;
}
// Example usage
const provideData = withDataResolving<AppState, UsersPageState.AllStates>(
[UsersSelector.fetchingState, UsersSelector.getUsers, UsersSelector.fetchingError],
(fetchingState, users, error) => {
if (fetchingState === 'pending') {
return {
tag: UsersPageState.StateTag.Loading
}
}
if (fetchingState === 'fetched' && users.length === 0) {
return {
tag: UsersPageState.StateTag.NoUsers,
}
}
if (fetchingState === 'fetched' && users.length > 0) {
return {
tag: UsersPageState.StateTag.WithUsers,
users,
}
}
if (fetchingState === 'error') {
return {
tag: UsersPageState.StateTag.ErrorFetching,
reason: error.message || 'Error fetching users'
}
}
throw Error()
})
const UsersPage = (props: UsersPageState.AllStates) => {
const renderLoading = () => <div>loading</div>;
const renderEmpty = () => <div>no users</div>
const renderList = (users: User[]) => <div>{users.map(user => <div>{user.name}</div>)}</div>
switch (props.tag) {
case UsersPageState.StateTag.Loading:
return renderLoading();
case UsersPageState.StateTag.NoUsers:
return renderEmpty();
case UsersPageState.StateTag.WithUsers:
return renderList(props.users);
default:
throw Error();
}
}
export const UsersPageWithData = provideData(UsersPage);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment