An Engine for DOM Recombobulation.
import { createRender, useState } from 'endr';
const Root = () => {
const [count, setCount] = useState(0);
return (
<div>
<button onclick={() => setCount(count + 1)}>This button</button>{' '}
has been clicked {count} {count === 1 ? 'time' : 'times'}
</div>
);
};
createRender(document.body)(<Root />);Endr takes the best parts of React and ditches the rest to result in a lean and fast virtual DOM rendering library. React is a great tool but has acquired some baggage over its long career that it must maintain for backwards compatibility. Endr loses the baggage and keeps the modern API.
| Package | Size |
|---|---|
| Endr | |
| Preact Preact Hooks |
|
| React React DOM |
Use jsx: automatic and jsxImportSource: 'endr' in your tsconfig.json and
JSX transpiler (babel, esbuild, etc) to get correct autocomplete and
rendered output. If you forget to do this you'll see errors about React not
being found.
-
There are no class components.
-
There are no synthetic events.
-
There is no property redirection.
- All element properties should be passed as if you were setting them directly
on the element. For example
onclickinstead ofonClickandondblclickinstead ofonDoubleClick.
- All element properties should be passed as if you were setting them directly
on the element. For example
-
refis not a special property on function components (it is passed through without something likeForwardRef). -
useRefaccepts an initializer function. -
createContextreturns the equivalent of a ReactContext.Providercomponent.- There is no
Context.Consumercomponent. Access context values throughuseContext(Context).
- There is no
-
useCallbackdoes not take a second argument and will return a constant function that will call the last seen function passed touseCallback. The less common case of memoizing a callback that creates a new function when dependencies change can be achieved withconst sumAB = useMemo(() => () => a + b, [a, b]). -
useMemocan be called without a second argument to default to an empty dependency array. -
setStatereturns the most recently set value. -
There is no
useLayoutEffect. -
useEffectis called immediately after the DOM is reconciled. -
First class portals are not included. This is an intentional decision to remove ambiguous behavior and really focus on the essentials for the core API.
A
Portalcomponent like/** @param {{ children: Children; to: ParentNode }} props */ export const Portal = ({ children, to }) => { const render = useMemo(() => createRender(to), [to]); useEffect(() => () => render(), [render]); render(children); };
being used like
<Portal to={parentElement}><div /></Portal>
is roughly equivalent to React's
createPortal(<div />, parentElement)
-
Exceptions thrown during render can be caught by the nearest Try component.
const MyComponent () => { const [error, setError] = useState(undefined); if (error) return `An error occurred! ${error}`; return ( <Try catch={setError}> <AllMyChildren /> </Try> ); };
can be used, for example, to show an error message whenever rendering
<AllMyChildren />or any descendents throws an exception. -
React's
Suspensecan be recreated withTryby awaiting all thrown promises, if desired. -
createRenderreplacescreateRoot.createRenderreturns a render function that can be called as often as desired to re-render at the root. To unmount all rendered components, simply call render with no children.import { createRender } from 'endr'; const render = createRender(document.body); render(<MyDisappearingApp />); setTimeout(() => render(), 5000);