Myra
Myra is a simple and small Typescript framework for building web interfaces.
Install / Use
/learn @jhdrn/MyraREADME
Myra
Myra is (another) JSX rendering library. It is small, simple and built with and for TypeScript.
Myra implements a React-like API (hooks, memo, fragments) on top of a custom virtual DOM diffing engine. It has no runtime dependencies.
Setup
Install with npm:
npm install --save myra
Add a tsconfig.json to your project:
{
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"jsx": "react",
"jsxFactory": "myra.h",
"jsxFragmentFactory": "myra.Fragment",
/* Optional, but recommended */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
Quick start
import * as myra from 'myra'
const Counter = myra.define(() => {
const [count, setCount] = myra.useState(0)
return (
<div>
<p>Count: {count}</p>
<button onclick={() => setCount(count + 1)}>Increment</button>
</div>
)
})
myra.mount(<Counter />, document.body)
Mounting a component
Use myra.mount to mount a component to the DOM:
myra.mount(<MyComponent />, document.body)
Defining components
Use myra.define to wrap a component function. This is purely a convenience for TypeScript type inference and has no runtime effect:
interface Props {
name: string
}
const MyComponent = myra.define<Props>(({ name }) => <p>Hello, {name}!</p>)
Hooks
useState
Manages local component state. Supports lazy initialization:
const [count, setCount] = myra.useState(0)
const [data, setData] = myra.useState(() => expensiveInitialValue())
// Functional update
setCount(prev => prev + 1)
useEffect / useLayoutEffect
useEffect runs asynchronously after render. useLayoutEffect runs synchronously after render (equivalent to React's useLayoutEffect). Both accept an optional deps array.
myra.useEffect(() => {
const sub = subscribe()
return () => sub.unsubscribe() // optional cleanup
}, [dep])
useRef
const inputRef = myra.useRef<HTMLInputElement>()
return <input ref={inputRef} />
// inputRef.current is the DOM element after render
useMemo
Memoizes a computed value. Re-computes when deps change:
const sorted = myra.useMemo(() => items.slice().sort(), [items])
useCallback
Memoizes a callback. Re-creates when deps change:
const handleClick = myra.useCallback(() => setCount(c => c + 1), [])
useReducer
An alternative to useState for more complex state logic. Works the same as React's useReducer:
type Action = { type: 'increment' } | { type: 'decrement' }
function reducer(state: number, action: Action): number {
switch (action.type) {
case 'increment': return state + 1
case 'decrement': return state - 1
}
}
const Counter = myra.define(() => {
const [count, dispatch] = myra.useReducer(reducer, 0)
return (
<div>
<p>{count}</p>
<button onclick={() => dispatch({ type: 'increment' })}>+</button>
<button onclick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
)
})
useContext
Subscribes to a context value provided by a Context.Provider ancestor. Re-renders the component whenever the context value changes. Falls back to the default value if no matching provider is found in the tree:
const ThemeContext = myra.createContext('light')
const ThemedButton = myra.define(() => {
const theme = myra.useContext(ThemeContext)
return <button class={theme}>Click me</button>
})
useErrorHandler
Catches errors thrown during render and shows a fallback view:
myra.useErrorHandler(error => <p>An error occurred: {error}</p>)
Context
Context lets you pass values down the component tree without threading props through every level.
Use myra.createContext to create a context object with a default value, then wrap the subtree with its Provider to supply a value:
const ThemeContext = myra.createContext('light')
const App = myra.define(() => (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
))
Any descendant can read the nearest provider's value with useContext (see above). When the provider's value prop changes, all subscribed consumers re-render automatically. If no provider is found, the default value passed to createContext is used.
Memoized components
Use myra.memo to skip re-renders when props have not changed. By default a shallow comparison is used. Pass a custom comparator as the second argument to override:
const MyMemoComponent = myra.memo<Props>(props => <p>{props.name}</p>)
// Custom comparator — return true to keep the existing render, false to re-render
const MyMemoComponent = myra.memo<Props>(
props => <p>{props.name}</p>,
(newProps, oldProps) => newProps.name === oldProps.name
)
Fragments
Use <></> (or <myra.Fragment>) to return multiple elements without a wrapper:
const MyComponent = myra.define(() => (
<>
<h1>Title</h1>
<p>Body</p>
</>
))
Special props
key— ensures stable identity for list items during reconciliation. Must be unique among siblings. Also prevents unnecessary re-renders of elements.class— maps to the DOMclassNameproperty.ref— populated with the DOM element after render (use withuseRef).nothing— a special JSX tag that always renders as an HTML comment node (<!-- Nothing -->), useful as a conditional placeholder.

