# Coming from React Zoijs shares React's component model and immutable-update style, but drops JSX, hooks, the build step, and the Virtual DOM. ## Concept map | React | Zoijs | |---|---| | `function App() { return }` | `function App() { return html\`...\` }` | | `useState(0)` → `[count, setCount]` | `createState(0)` → `count.get()` / `count.set()` | | `useMemo(() => ..., deps)` | `computed(() => ...)` (no deps array) | | `useEffect` | bindings react automatically; for true side-effects, see [Cleanup](../concepts/cleanup.md) | | `{items.map(i =>
  • )}` | `each(() => items.get(), i => i.id, i => html\`
  • \`)` | | re-renders on state change | **no re-render** — setup runs once, nodes update in place | | `onClick={fn}` | `onclick=${fn}` | ## The biggest mental shift: no re-rendering In React, your component function re-runs on every state change. In Zoijs it runs **once**. There are no stale closures, no dependency arrays, no `useCallback`/`useMemo` to stabilize references. ```jsx // React function Counter() { const [count, setCount] = useState(0); return ; } ``` ```js // Zoijs function Counter() { const count = createState(0); return html``; } ``` Note the `${() => count.get()}` — because there's no re-render, the arrow function is how Zoijs knows that binding is live. ## Gotchas for React developers - **Don't expect re-execution.** Code in the component body runs once. Put per-change logic in bindings or computeds, not in the function body. - **Wrap reactive reads in `() =>`** inside templates. `${count.get()}` would be a one-time snapshot (like reading a ref once). - **Keys work the same** — stable ids, not indexes. - **No Context/Redux needed** for shared state — `createState` in a module, imported where needed. ## What you'll miss (for now) Router, SSR, and a large ecosystem. Zoijs is intentionally small. See the [FAQ](../faq.md).