You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
import{h,text,app}from"hyperapp"app({view: ()=>h('main',{},text('text')),// cast to Node only if you're sure it existsnode: document.getElementById('app')asNode,})
import{h,app,Action}from"hyperapp"typeState={name: string}constHandleInput : Action<State,Event>=(state,event)=>({
...state,// since we are sure in this case, the event.target is// an input element, it is ok to cast. Otherwise we// probably should make sure.name: (event.targetasHTMLInputElement).value})app({init: {name: ''},view: state=>h('main',{},[h('input',{value: state.name,oninput: HandleInput})]),node: document.getElementById('app')asNode,})
Action decorators, and actions with non-event payload types
import{h,app,Action}from"hyperapp"// basic decorator typetypeDecorator<From,To>=<S>(action: Action<S,To>)=>Action<S,From>// decorator for actions that take strings as payloads, and meant to be// used on events with input-elements as targets (but I'm not sure how// to enforce that part with types)constwithTargetValue:Decorator<Event,string>=action=>(_,event)=>[action,(event.targetasHTMLInputElement).value]// It's always a good idea to specify the state of your// app as a typetypeState={name: string}// Action takes string payloadconstHandleInput : Action<State,string>=(state,name)=>({...state, name})app({init: {name: ''},view: state=>h('main',{},[h('input',{value: state.name,// We can't use the HandleInput action directly// as it takes a string payload, but by using // the withTargetValue decorator, the types// match up:oninput: withTargetValue(HandleInput)}),]),node: document.getElementById('app')asNode,})
import{h,app,text,Action}from"hyperapp"typeChoice='A'|'B'typeState={choice?: Choice}constChoose : Action<State,Choice>=(state,choice)=>({...state, choice})// typescript can't infer the State type from // the default initial state {}, so we tell it:app<State>({view: state=>h('main',{},[h('button',{onclick: [Choose,'A']},text('A')),// An invalid choice as payload makes typescript// complain (change to 'B' or 'A' to fix it):h('button',{onclick: [Choose,'C']},text('B')),h('p',{},text('Current choice: '+state.choice))]),node: document.getElementById('app')asNode,})
import{h,text}from"hyperapp"typeMyComponentProps={foo: stringbar: number}constmyComponent=(props: MyComponentProps)=>h('div',{},[props.bar>20
? h('h1',{},text(props.foo))
: h('h3',{},text(props.foo))])// no errors because we are passing correct typesmyComponent({foo: 'Yes',bar: 5})// has ts-errors because we're passing wrong typesmyComponent({bar: '5'})
import{h,text,Action}from"hyperapp"// By using a generic parameter (here: S), for the// state-parameter to the actions, we ensure that// the actions provided will operate on the same// state-type, even though we do not know what that// state type might be.typeMyComponentProps<S>={foo: Action<S,Event>bar: Action<S,Event>}// Since we don't know the app's state in a reusable// view component, like this the state must be inferred// using a generic function type parameter. constmyComponent=<S>(props: MyComponentProps<S>)=>h('div',{},[h('button',{onclick: props.foo},text('Do Foo')),h('button',{onclick: props.bar},text('Do Bar'))])// no errors because we are passing correct typesmyComponent({foo: (x:number)=>x+1,bar: (x:number)=>x*2})// has ts-errors because we're using mismatched action typesmyComponent({foo: (x:number)=>x+1,bar: (x:string)=>x+'!'})
Interactive View Components with payload validation
import{h,text,Action,ValidateCustomPayloads}from"hyperapp"typeMyComponentProps<S>={foo: Action<S,Event>|[Action<S,any>,any]bar: Action<S,Event>|[Action<S,any>,any]}// Since the view component props allow actions with custom payloads// We should use the ValidateCustomPayloads utility type to ensure// that the provided payloads match the given action-types.constmyComponent=<S,X>(props: ValidateCustomPayloads<S,X>&MyComponentProps<S>)=>h('div',{},[h('button',{onclick: props.foo},text('Do Foo')),h('button',{onclick: props.bar},text('Do Bar'))])typeState={score: number,name: string,}constSetName : Action<State,string>=(state,name)=>({...state, name})constAddScore : Action<State,number>=(state,amount)=>({...state,score: state.score+amount})// no errors, since payloads match their actionsmyComponent({foo: [SetName,'unknown'],bar: [AddScore,20]})// error, since one payload donesn't match the actionsmyComponent({foo: [SetName,20],bar: [AddScore,20]})
import{h,text,Action,VDOM}from"hyperapp"typeMyComponentProps<S>={foo: Action<S,any>,bar: string}// In this version, typescript tries to infer the// return type of the view component, but cannot,// because only the first child has any actions // from which the state type can be inferred// in the second child, the state type is inferred// to be unknown.constmyComponent=<S>(props: MyComponentProps<S>)=>h('div',{},[h('button',{onclick: props.foo},text('click')),h('p',{},text(props.bar)),])// The easiest way to solve this is to explicitly// declare the return type:constmyComponent2=<S>(props: MyComponentProps<S>):VDOM<S>=>h('div',{},[h('button',{onclick: props.foo},text('click')),h('p',{},text(props.bar)),])
mport{Action,Dispatch,Effect}from"hyperapp"// definition of getJSON effect:typeGetJSONOptions<S>={url: string,action: Action<S,any>error?: Action<S,Error>}// here we define an effect runner. It needs the Dispatch type for the// first argument to qualify as an effect runer.construnGetJSON=<S>(dispatch:Dispatch<S>,options:GetJSONOptions<S>)=>{fetch(options.url).then(response=>{if(response.status!==200&&options.error)thrownewError('Status Error: '+response.status)returnresponse}).then(response=>response.json()).then(data=>dispatch(options.action,data)).catch(e=>{if(options.error)dispatch(options.error,e)})}// this is the effect creator. It returns the Effect type which// is a tuple of [runner, options] where the runner should accept// options of the specified type.constgetJSON=<S>(url:string,action:Action<S,any>,error?: Action<S,Error>):Effect<S,GetJSONOptions<S>>=>[runGetJSON,{url, action, error}]// -----------------// usage in actions:typeState={fetching: boolean,data: any}// this action fetches some dataconstFetchData :Action<State>=(state: State)=>[{...state,fetching: true,data: null},getJSON('http://example.com/data',GotData)]// this action saves response data on the stateconstGotData : Action<State,any>=(state,data)=>({
...state,fetching: false,
data
})
Type-checking the Payload response in fetch effect
import{Action,Dispatch,Effect}from"hyperapp"// definition of getJSON effect:// Through the type parameter D, we specify the type of the// Payload we expect to get backtypeGetJSONOptions<S,D>={url: string,action: Action<S,D>error?: Action<S,Error>}// The type parameter D needs to be an type parameter to this function as well, construnGetJSON=<S,D>(dispatch:Dispatch<S>,options:GetJSONOptions<S,D>)=>{fetch(options.url).then(response=>{if(response.status!==200&&options.error)thrownewError('Status Error: '+response.status)returnresponse}).then(response=>response.json())// we can not have type safetyp about what an API will provide// we have to take it on faith that the type of response will// be the one we asked for..then(data=>dispatch(options.action,dataasD)).catch(e=>{if(options.error)dispatch(options.error,e)})}// By passing D along from this main interface to the// effect, we allow users to state what type of payload// they expect backconstgetJSON=<S,D>(url:string,action:Action<S,D>,error?: Action<S,Error>):Effect<S,GetJSONOptions<S,D>>=>[runGetJSON,{url, action, error}]// -----------------// usage in actions:typeDataA={foo: number}typeDataB={bar: string}typeState={fetching: boolean,data: null|DataA|DataB}constFetchDataA :Action<State>=(state: State)=>[{...state,fetching: true,data: null},getJSON<State,DataA>('http://example.com/data-a',GotDataA)]constFetchDataB :Action<State>=(state: State)=>[{...state,fetching: true,data: null},// Notice the error here: We said we expect data of type DataB// but the callback action we provided expects data of type// DataA. Fix it by changing the action to GotDataB,// or the expected type to DataA.getJSON<State,DataB>('http://example.com/data-b',GotDataA)]constGotDataA : Action<State,DataA>=(state,data)=>({ ...state,fetching: false, data })constGotDataB : Action<State,DataB>=(state,data)=>({ ...state,fetching: false, data })