BarefootJS uses Solid's names and shapes for reactivity, React's JSX for control flow, and the compiler for what both do at runtime. Components run once — there is no re-render:
"use client"
import { createSignal, createEffect } from '@barefootjs/client'
export function Counter() {
const [count, setCount] = createSignal(0) // useState / createSignal
createEffect(() => console.log(count())) // useEffect, no deps array
return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
}
#Reactive primitives
| Solid |
React |
BarefootJS |
createSignal |
useState |
createSignal — [getter, setter], read as count() |
createMemo |
useMemo |
createMemo — no deps array, auto-tracked |
createEffect |
useEffect |
createEffect — no deps array; exclude reads with untrack |
batch |
automatic batching |
batch |
untrack |
deps array |
untrack — the only way to narrow dependencies |
on(deps, fn) |
deps array |
— write it with untrack |
createStore / produce |
useReducer / external store |
— put the object in a signal; forms live in @barefootjs/form |
createRoot |
— |
createRoot — test and CSR harness use |
createRenderEffect / createComputed |
useLayoutEffect |
— one effect timing only |
#Lifecycle
| Solid |
React |
BarefootJS |
onMount |
useEffect(fn, []) |
onMount — once, after hydration |
onCleanup |
effect return value |
onCleanup |
ref={el => …} |
useRef / ref callback |
ref={handleMount} — callback ref; the entry point for portals and focus |
#Control flow
Solid ships components; BarefootJS lowers React's JSX shapes in the compiler, with Solid's granularity, on every adapter.
| Solid |
React |
BarefootJS |
<Show when fallback> |
cond && … / ternary |
cond() && … / ternary |
<For each> |
items.map() |
items().map() — keyed; .filter() / .sort() / .flatMap() chains allowed |
<Switch> / <Match> |
ternary chain |
ternary chain |
<Index each> |
— |
— |
<Dynamic component> |
variable as tag |
— asChild / Slot cover most uses |
<ErrorBoundary> |
Error Boundary |
— SSR errors are the backend's; only client effects and handlers can throw |
<Portal> |
createPortal |
createPortal — imperative: move a ref element to body; SSR leaves an inert placeholder |
<Suspense fallback> |
<Suspense fallback> |
<Async fallback> — an SSR streaming boundary, compiled to the adapter's primitive |
lazy() |
React.lazy |
— Vite dynamic import; islands split per entry anyway |
#Components and props
| Solid |
React |
BarefootJS |
splitProps |
destructure + rest |
splitProps |
mergeProps |
destructure defaults |
literal destructure defaults, resolved at compile time ({ size = 'md' }) |
children() |
props.children |
props.children + Slot / asChild |
createContext |
createContext |
createContext |
<Ctx.Provider value> |
same |
same |
useContext |
useContext |
useContext |
createUniqueId |
useId |
— pass props.id in from outside |
#Async
| Solid |
React |
BarefootJS |
createResource |
use(promise) + Suspense |
createQuery — [value, action]; the server seeds the value from initial, and the component folds "no value yet" as an ordinary conditional instead of suspending |
action() / useSubmission |
useActionState / useFormStatus |
createMutation — sent when action() is called; action.isPending() / action.error(), invalidates on success. Forms: createForm().isSubmitting() |
renderToStream |
renderToPipeableStream |
<Async> + setupStreaming() |
createQuery and createMutation take a request function that returns an http descriptor, and are Alpha. spec/async.md has the full model; its layers 1 and 2 (a client-side <Async> fold, transitions) are still design.
#Mount and SSR
| Solid |
React |
BarefootJS |
render(() => <App/>, el) |
createRoot(el).render() |
render(el, 'Name', props) from @barefootjs/client/runtime |
hydrate() |
hydrateRoot() |
automatic, per "use client" file |
renderToString |
renderToString |
— the compiler emits templates; SSR is the backend's job |
<NoHydration> |
Server Components |
the default: no "use client" |
isServer |
typeof window |
"use client" / /* @client */ — static, per file or per expression |
| Solid Devtools |
React DevTools |
bf debug graph / bf debug profile |
#Router and URL
| Solid Router |
React Router |
BarefootJS |
useSearchParams |
useSearchParams |
createSearchParams — signal-shaped; query-only navigation updates it without a page swap |
<A href> / useNavigate |
<Link> / useNavigate |
plain <a> + queryHref() + router navigate() |
| — |
Next layout.tsx (close) |
<Region> — page lifecycle boundary |
- Route loaders / render-suspend — the backend fetches what the page needs and passes it as props; a client-side read is a
createQuery whose initial is that prop, and nothing suspends.
on() / deps arrays — untrack is the one way to narrow what an effect tracks.
- Stores — a signal holding an object, or
@barefootjs/form, covers it.
<ErrorBoundary> — SSR errors belong to the backend; only client effects and handlers can throw.
renderToString / hydrate() — templates render on the server; hydration is automatic per "use client" file.
createUniqueId — ids come in as props.
useTransition / <ViewTransition> — the router only sets data-bf-navigating.