Last active
May 21, 2020 07:32
-
-
Save calderaro/5383166bfb7c7b0c96f97014055e0731 to your computer and use it in GitHub Desktop.
Async Handlers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
export interface FormHandlerChildrenProps<T> { | |
state: FormState<T>; | |
reload?: () => Promise<void>; | |
create?: () => Promise<void>; | |
update?: () => Promise<void>; | |
remove?: () => void; | |
onChange: (change: { id: string; value: any }) => void; | |
setModal: (name: string) => void; | |
setSection: (name: string) => void; | |
} | |
export interface FormHandlerProps<T> { | |
id?: string; | |
onCreateSuccess?: (data: T) => void; | |
onUpdateSuccess?: (data: T) => void; | |
onRemoveSuccess?: (id: string) => void; | |
getDefaultState: () => T; | |
children(res: FormHandlerChildrenProps<T>): React.ReactNode; | |
load?: (id: string) => Promise<T>; | |
create?: (data: T) => Promise<T>; | |
update?: (data: T) => Promise<T>; | |
remove?: (id: string) => void; | |
} | |
export interface FormState<T> { | |
status: string; | |
message: string; | |
modal: string; | |
section: string; | |
data: T; | |
} | |
class FormHandler<T> extends React.Component<FormHandlerProps<T>, FormState<T>> { | |
constructor(props: FormHandlerProps<T>) { | |
super(props); | |
this.state = { | |
status: "", | |
message: "", | |
modal: "", | |
section: "", | |
data: this.props.getDefaultState() | |
}; | |
} | |
componentDidMount() { | |
this.load(); | |
} | |
setModal = (name: string) => { | |
this.setState({ modal: name }); | |
}; | |
setSection = (name: string) => { | |
this.setState({ section: name }); | |
}; | |
load = async () => { | |
if (this.props.load) { | |
if (!this.props.id) { | |
return this.setState({ status: "loaded", data: this.props.getDefaultState(), message: "" }); | |
} | |
try { | |
this.setState({ status: "loading", message: "" }); | |
const data = await this.props.load(this.props.id); | |
this.setState({ status: "loaded", data, message: "" }); | |
} catch (error) { | |
if (error.code === "permission-denied") { | |
return this.setState({ status: "failure", message: "Permisos insuficientes!" }); | |
} | |
this.setState({ status: "failure", message: "Un error inesperado ha ocurrido!" }); | |
} | |
} | |
}; | |
create = async () => { | |
if (this.props.create) { | |
try { | |
this.setState({ status: "creating", message: "" }); | |
const data = await this.props.create(this.state.data); | |
this.props.onCreateSuccess?.(data); | |
this.setState({ status: "created", data, message: "Guardado exitoso!" }); | |
} catch (error) { | |
if (error.code === "permission-denied") { | |
return this.setState({ status: "failure", message: "Permisos insuficientes!" }); | |
} | |
this.setState({ status: "failure", message: "Un error inesperado ha ocurrido!" }); | |
} | |
} | |
}; | |
update = async () => { | |
if (this.props.update && this.props.id) { | |
try { | |
this.setState({ status: "updating", message: "" }); | |
const data = await this.props.update(this.state.data); | |
this.props.onUpdateSuccess?.(data); | |
this.setState({ status: "updated", data, message: "Guardado exitoso!" }); | |
} catch (error) { | |
if (error.code === "permission-denied") { | |
return this.setState({ status: "failure", message: "Permisos insuficientes!" }); | |
} | |
this.setState({ status: "failure", message: "Un error inesperado ha ocurrido!" }); | |
} | |
} | |
}; | |
remove = async () => { | |
if (this.props.remove && this.props.id) { | |
try { | |
this.setState({ status: "deleting" }); | |
await this.props.remove(this.props.id); | |
this.setState({ status: "removed" }); | |
this.props.onRemoveSuccess?.(this.props.id); | |
} catch (error) { | |
if (error.code === "permission-denied") { | |
return this.setState({ status: "failure", message: "Permisos insuficientes!" }); | |
} | |
this.setState({ status: "failure", message: "Un error inesperado ha ocurrido!" }); | |
} | |
} | |
}; | |
onChange = ({ id, value }: any) => { | |
this.setState((state) => ({ | |
data: { | |
...state.data, | |
[id]: value | |
} | |
})); | |
}; | |
render() { | |
return this.props.children?.({ | |
state: this.state, | |
onChange: this.onChange, | |
setModal: this.setModal, | |
setSection: this.setSection, | |
reload: this.props.load ? this.load : undefined, | |
remove: this.props.remove ? this.remove : undefined, | |
create: this.props.create ? this.create : undefined, | |
update: this.props.update ? this.update : undefined | |
}); | |
} | |
} | |
export default FormHandler; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import debounce from "lodash/debounce"; | |
export interface SearchResult<T> { | |
hits: T[]; | |
page: number; | |
pages: number; | |
} | |
type OnChangeParams<Y> = <K extends keyof Y>(s: K, a: Y[K]) => void; | |
export interface ListHandlerChildrenProps<T, Y> { | |
state: ListState<T, Y>; | |
reload: () => Promise<void>; | |
onChangeSearchText: (searchText: string) => void; | |
onChangeParams: OnChangeParams<Y>; | |
onNextPage: () => void; | |
onPrevPage: () => void; | |
} | |
export type ListHandlerListFunction<T> = (searchText: string, params?: { page: number }) => Promise<SearchResult<T>>; | |
export interface ListHandlerProps<T, Y> { | |
list: ListHandlerListFunction<T>; | |
getParamsDefaultState?: () => Y; | |
children(res: ListHandlerChildrenProps<T, Y>): React.ReactNode; | |
} | |
export interface ListState<T, Y> { | |
status: "loading" | "loaded" | "searching" | "searched" | "failure"; | |
searchText: string; | |
page: number; | |
params: Y | {}; | |
data: SearchResult<T>; | |
} | |
class ListHandler<T, Y> extends React.Component<ListHandlerProps<T, Y>, ListState<T, Y>> { | |
dsearch: () => Promise<void>; | |
constructor(props: ListHandlerProps<T, Y>) { | |
super(props); | |
this.state = { | |
status: "loading", | |
searchText: "", | |
page: 1, | |
params: this.props.getParamsDefaultState?.() || {}, | |
data: { | |
hits: [], | |
page: 0, | |
pages: 1 | |
} | |
}; | |
this.dsearch = debounce(this.search, 220); | |
} | |
componentDidMount() { | |
this.list(); | |
} | |
componentDidUpdate(prevProps: ListHandlerProps<T, Y>, prevState: ListState<T, Y>) { | |
if (this.state.searchText !== prevState.searchText) { | |
this.dsearch(); | |
} | |
if (this.state.page !== prevState.page) { | |
this.dsearch(); | |
} | |
} | |
list = async () => { | |
this.setState({ status: "loading" }); | |
const data = await this.props.list(this.state.searchText, { page: this.state.page }); | |
this.setState({ status: "loaded", data }); | |
}; | |
search = async () => { | |
this.setState({ status: "searching" }); | |
const data = await this.props.list(this.state.searchText, { page: this.state.page }); | |
this.setState({ status: "searched", data }); | |
}; | |
onChangeSearchText = (searchText: string) => { | |
this.setState({ searchText }); | |
}; | |
onChangeParams: OnChangeParams<Y> = (field, value) => { | |
this.setState({ params: { ...this.state.params, [field]: value } }); | |
}; | |
onNextPage = () => { | |
if (this.state.page + 1 > this.state.data.pages - 1) { | |
return this.setState({ page: this.state.data.pages }); | |
} | |
this.setState({ page: this.state.data.page + 1 }); | |
}; | |
onPrevPage = () => { | |
if (this.state.page - 1 < 1) { | |
return this.setState({ page: 1 }); | |
} | |
this.setState({ page: this.state.page - 1 }); | |
}; | |
render() { | |
return this.props.children?.({ | |
state: this.state, | |
reload: this.list, | |
onChangeSearchText: this.onChangeSearchText, | |
onChangeParams: this.onChangeParams, | |
onNextPage: this.onNextPage, | |
onPrevPage: this.onPrevPage | |
}); | |
} | |
} | |
export default ListHandler; | |
/* | |
Page and SearchText should be part of params? | |
*/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Route, Switch } from "react-router-dom"; | |
import get from "lodash/get"; | |
import SectionLayout from "../SectionLayout"; | |
import ListHandler, { ListHandlerChildrenProps, ListHandlerListFunction } from "../ListHandler"; | |
import FormHandler, { FormHandlerChildrenProps } from "../FormHandler"; | |
import DeleteModal from "../DeleteModal"; | |
interface Props<T> { | |
label: string; | |
path: string; | |
push: (path: string) => void; | |
list: ListHandlerListFunction<T>; | |
load?: (id: string) => Promise<T>; | |
create?: (data: T) => Promise<T>; | |
update?: (data: T) => Promise<T>; | |
remove?: (id: string) => Promise<void>; | |
getDefaultState: () => T; | |
renderFormComponent(res: FormHandlerChildrenProps<T>): React.ReactNode; | |
renderListComponent(res: ListHandlerChildrenProps<T>): React.ReactNode; | |
} | |
class SectionHandler<T> extends React.Component<Props<T>> { | |
render() { | |
const { | |
label, | |
push, | |
path, | |
list, | |
renderListComponent, | |
getDefaultState, | |
load, | |
create, | |
update, | |
remove, | |
renderFormComponent | |
} = this.props; | |
return ( | |
<ListHandler list={list}> | |
{(listProps) => ( | |
<SectionLayout label={label}> | |
<Switch> | |
<Route path={path} exact> | |
{renderListComponent(listProps)} | |
</Route> | |
<Route path={[`${path}/new`, `${path}/:id`]}> | |
{({ match }) => ( | |
<FormHandler | |
id={match?.params.id} | |
getDefaultState={getDefaultState} | |
load={load} | |
create={create} | |
update={update} | |
remove={remove} | |
onCreateSuccess={(data) => [push(`${path}/${get(data, "id", "")}`), listProps.reload()]} | |
onUpdateSuccess={() => listProps.reload()} | |
onRemoveSuccess={() => [push(path), listProps.reload()]} | |
> | |
{(formProps) => ( | |
<> | |
<DeleteModal | |
open={formProps.state.modal === "delete"} | |
onClose={() => formProps.setModal("")} | |
onRemove={formProps.remove} | |
/> | |
{renderFormComponent({ ...formProps, remove: () => formProps.setModal("delete") })} | |
</> | |
)} | |
</FormHandler> | |
)} | |
</Route> | |
</Switch> | |
</SectionLayout> | |
)} | |
</ListHandler> | |
); | |
} | |
} | |
export default SectionHandler; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment