Skip to content

Instantly share code, notes, and snippets.

@danthedaniel
Last active March 20, 2018 23:46
Show Gist options
  • Save danthedaniel/e18004bfa4e964d3416e1f12b13d8b7d to your computer and use it in GitHub Desktop.
Save danthedaniel/e18004bfa4e964d3416e1f12b13d8b7d to your computer and use it in GitHub Desktop.
Preact components with persistent state.
/**
* 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>
);
}
}
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;
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