Skip to content

Instantly share code, notes, and snippets.

@nwellis
Last active December 8, 2020 20:54
Show Gist options
  • Save nwellis/916e6896014e1268c9eba8950f1581e6 to your computer and use it in GitHub Desktop.
Save nwellis/916e6896014e1268c9eba8950f1581e6 to your computer and use it in GitHub Desktop.
Resource wrappers in Kotlin and Typescript
sealed class Resource<T> {
object Loading: Resource<Nothing>()
data class Success<T>(val data: T): Resource<T>()
data class Error(val error: AppError): Resource<Nothing>()
}
export type Resource<T> = ResourceLoading | ResourceSuccess<T> | ResourceError
export class ResourceLoading { }
export class ResourceSuccess<T> {
constructor(readonly data: T) { }
}
export class ResourceError {
constructor(readonly error: AppError) { }
}
/**
* When using the state patter `type State = Readonly<typeof initialState>` with React, a resource should be
* of type `Resource`, not the initial state given. Use this to instantiate the `initialState`.
*
* @param initial Optional state to begin with, if none is provided this defaults to loading
*/
export function initialResource<T>(initial?: Resource<T>): Resource<T> {
return initial || new ResourceLoading()
}
type TParams = {
gistId: string
}
type Props = RouteComponentProps<TParams>
const initialState = {
gist: initialResource<Gist>()
}
type State = Readonly<typeof initialState>
class GistDetail extends React.Component<Props, State> {
readonly state: State = initialState
componentDidUpdate(prevProps: Props, prevState: State) {
const currentId = prevProps.match.params.gistId
const nextId = this.props.match.params.gistId
if (nextId !== currentId) {
this.setState({
gist: new ResourceLoading()
})
this.fetchGist(nextId)
}
}
render() {
const { gist } = this.state
if (gist instanceof ResourceLoading) {
return (
<Segment>
<Loader active inline='centered' />
</Segment>
)
} else if (gist instanceof ResourceSuccess) {
return (
<Container>
<code>
{gist.data.url}
</code>
</Container>
)
} else {
return (
<Container>
<h1>{gist.error.message}</h1>
</Container>
)
}
}
private fetchGist(gistId: string) {
console.log(`fetching ${gistId}`)
fetch(`https://api.github.com/gists/${gistId}`)
.then(res => res.json())
.then(gist => { this.setState({ gist: new ResourceSuccess(gist) }) })
}
}
export default GistDetail;
@Beng89
Copy link

Beng89 commented Jun 2, 2019

I would probably make the following changes:

export type Resource<T> = ResourceLoading | ResourceSuccess<T> | ResourceError

export class ResourceLoading { }

export class ResourceSuccess<T> {
    constructor(readonly data: T) { }
}

export class ResourceError {
    constructor(readonly error: AppError) { }
}

/**
 * When using the state patter `type State = Readonly<typeof initialState>` with React, a resource should be
 * of type `Resource`, not the initial state given. Use this to instantiate the `initialState`.
 * 
 * @param initial Optional state to begin with, if none is provided this defaults to loading
 */
export function initialResource<T>(initial?: Resource<T>): Resource<T> {
    return initial || new ResourceLoading()
}

@nwellis
Copy link
Author

nwellis commented Jun 2, 2019

That's much cleaner 👍 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment