Last active
March 20, 2018 23:46
-
-
Save danthedaniel/e18004bfa4e964d3416e1f12b13d8b7d to your computer and use it in GitHub Desktop.
Preact components with persistent state.
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
/** | |
* Simple example usage of a stored component. | |
*/ | |
import { h } from 'preact'; | |
import api from '../api'; | |
import StoredComponent from '../stored_component'; | |
type StateType = typeof defaultState; | |
const defaultState = {leaderboard: []}; | |
export default class LeaderboardView extends StoredComponent<{}, StateType> { | |
constructor(props) { | |
super(props, defaultState); | |
} | |
componentDidMount() { | |
api.leaderboard().then(leaderboard => this.setState({leaderboard})); | |
} | |
render(props: {}, state: StateType) { | |
return ( | |
<div class="row"> | |
<div class="col"> | |
<ul class="list-group"> | |
{ | |
state.leaderboard.length === 0 && | |
<div class="alert alert-info" role="alert"> | |
No one has won yet! | |
</div> | |
} | |
{ | |
state.leaderboard.map(entry => { | |
return ( | |
<li class="list-group-item bg-dark"> | |
<div class="row"> | |
<div class="col"> | |
<a | |
class="font-light" | |
href={`https://twitch.tv/${entry.name}`} | |
target="_blank"> | |
{ entry.name } | |
</a> | |
</div> | |
<div class="col"></div> | |
<div class="col">Wins: { entry.count }</div> | |
</div> | |
</li> | |
); | |
}) | |
} | |
</ul> | |
</div> | |
</div> | |
); | |
} | |
} |
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
namespace storage { | |
let defaults: {[name: string]: any} = {}; | |
// Could use localStorage here, but then you'd need to manage schema versions. | |
const data_store = window.sessionStorage; | |
const className = (val): string => Object.getPrototypeOf(val).constructor.name; | |
/** | |
* Remove any types that aren't JSON compatible. | |
*/ | |
const serializer = (key: string, val: any) => { | |
if (typeof val === 'undefined') | |
return; | |
if (val === null) | |
return val; | |
const shouldSerialize = val => { | |
const whitelist = ["Array", "Number", "Boolean", "Object", "Array", "String"]; | |
return whitelist.indexOf(className(val)) !== -1; | |
}; | |
if (className(val) === "Array") { | |
return (<any[]> val).filter(shouldSerialize); | |
} else if (shouldSerialize(val)) { | |
return val; | |
} | |
}; | |
/** | |
* Get the state object for a view. | |
* | |
* @param view_class The name of the class for the view (e.g.: GuessView). | |
*/ | |
export const getState = <T>(view_class: string): T => { | |
if (data_store.getItem(view_class)) { | |
const from_store = JSON.parse(data_store.getItem(view_class)); | |
return Object.assign(defaults[view_class], from_store); | |
} else { | |
return resetState(view_class); | |
} | |
}; | |
/** | |
* Reset the state object for a view to the default. | |
* | |
* @param view_class The name of the class for the view (e.g.: GuessView). | |
*/ | |
export const resetState = <T>(view_class: string) => { | |
data_store.setItem(view_class, JSON.stringify(defaults[view_class], serializer)); | |
return defaults[view_class] as T; | |
}; | |
/** | |
* Set the state object for a view. | |
* | |
* @param view_class The name of the class for the view (e.g.: GuessView). | |
* @param state The value of the state. | |
*/ | |
export const setState = (view_class: string, state) => { | |
data_store.setItem(view_class, JSON.stringify(state, serializer)); | |
} | |
export const registerDefault = (view_class: string, def: any) => { | |
defaults[view_class] = def; | |
}; | |
} | |
export default storage; |
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 { h, Component } from 'preact'; | |
import storage from './storage'; | |
export default abstract class StoredComponent<P, S> extends Component<P, S> { | |
constructor(props, default_state: S) { | |
super(props); | |
storage.registerDefault(this.constructor.name, default_state); | |
this.state = storage.getState(this.constructor.name); | |
} | |
componentDidUpdate() { | |
storage.setState(this.constructor.name, this.state); | |
} | |
/** | |
* Reset the component's state to its default value. | |
*/ | |
defaultState() { | |
const newState: S = storage.resetState(this.constructor.name); | |
this.setState(newState); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment