<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged solidjs</title>
<link>https://coderlegion.com/tag/solidjs</link>
<description>Powered by Question2Answer</description>
<item>
<title>SolidJS 2.0 for React Developers: A First Look</title>
<link>https://coderlegion.com/22950/solidjs-2-0-for-react-developers-a-first-look</link>
<description>&lt;p&gt;I've shipped React for the better part of a decade. Hooks, Suspense, Server Components, the whole tour. So when the &lt;strong&gt;SolidJS 2.0&lt;/strong&gt; beta showed up in my feed with the tagline &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0&quot;&gt;&quot;The &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; is Over,&quot;&lt;/a&gt; I rolled my eyes a little and then clicked anyway.&lt;/p&gt;
&lt;p&gt;I'm glad I did. Solid has been the framework React people admire from a distance for years, and 2.0 is the release that finally made me install it and poke at the reactivity model instead of just reading about it. This post is my honest first look: what's new, what a React developer will recognize, and where Solid quietly does something React can't.&lt;/p&gt;
&lt;h3 id=&quot;what-is-solidjs-2-0-&quot;&gt;What is SolidJS 2.0?&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.solidjs.com&quot;&gt;SolidJS&lt;/a&gt; is a UI library with React-like JSX and a completely different engine underneath. Instead of re-rendering components and diffing a virtual DOM, it uses &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.solidjs.com/concepts/intro-to-reactivity&quot;&gt;&lt;strong&gt;fine-grained reactivity&lt;/strong&gt;&lt;/a&gt;: your component runs once, and updates flow directly to the exact DOM nodes that depend on a piece of state.&lt;/p&gt;
&lt;p&gt;The 2.0 line is currently in beta. The first public build, &lt;code&gt;v2.0.0-beta.0&lt;/code&gt;, landed on March 3, 2026, and the team skipped the alpha phase entirely because the milestones planned for it stopped feeling worth their own release. You can try it today:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pnpm add solid-js@next
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The headline of the whole release is async. Solid's reactive graph now understands promises natively, and a lot of the API changes fall out of that one decision.&lt;/p&gt;
&lt;h3 id=&quot;fine-grained-reactivity-from-a-react-brain&quot;&gt;Fine-grained reactivity, from a React brain&lt;/h3&gt;
&lt;p&gt;Here's the mental model shift, and it's the thing to internalize before anything else makes sense.&lt;/p&gt;
&lt;p&gt;In React, state changes re-run your component function. React then builds a new virtual DOM tree and diffs it against the old one to figure out what actually changed. &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and dependency arrays exist to keep that re-render machine from doing too much work. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/learn/react-compiler&quot;&gt;React Compiler&lt;/a&gt; automates a lot of that memoization now, but the underlying model is the same: the component re-runs and React diffs the result.&lt;/p&gt;
&lt;p&gt;In Solid, the component body is setup code. It runs a single time. When you read a signal in your JSX, Solid records that specific dependency and wires it straight to the DOM. Change the signal later and only that text node or attribute updates. No component re-render, and no dependency array to keep honest.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Counter() {
  const [count, setCount] = createSignal(0);

  // This whole function runs ONCE. Only the text node updates on click.
  return &amp;lt;button onClick={() =&amp;gt; setCount(count() + 1)}&amp;gt;{count()}&amp;lt;/button&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tell for a React dev: &lt;code&gt;count&lt;/code&gt; is a function, not a value. You call &lt;code&gt;count()&lt;/code&gt; to read it. That call is what subscribes the surrounding computation to changes. Once it clicks, the absence of a dependency array stops feeling like something is missing and starts feeling like a bug class that no longer exists.&lt;/p&gt;
&lt;h3 id=&quot;why-async-is-the-real-story-of-solidjs-2-0&quot;&gt;Why async is the real story of SolidJS 2.0&lt;/h3&gt;
&lt;p&gt;This is where 2.0 earns its version bump. In Solid 1.x, async data meant &lt;code&gt;createResource&lt;/code&gt; and wrapping things in &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;. In 2.0, a derived computation can just return a promise, and the graph handles suspending and resuming for you.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// Solid 1.x
const [data] = createResource(userId, fetchUser);

// Solid 2.0: the promise flows through the reactive graph
const data = createMemo(() =&amp;gt; fetchUser(userId()));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suspense gets replaced by &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, and the semantics are the part I actually care about. &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; is scoped to &lt;em&gt;initial&lt;/em&gt; readiness. It shows a fallback while the subtree can't render anything yet, and then it stays out of your way. When &lt;code&gt;userId()&lt;/code&gt; changes and the data refetches, the UI doesn't tear itself down and flash a spinner. It holds the old content until the new content is ready.&lt;/p&gt;
&lt;p&gt;To show that a refresh is in flight, you reach for &lt;code&gt;isPending&lt;/code&gt;, which reports on pending reactive work without unmounting anything:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const users = createMemo(() =&amp;gt; api.listUsers());
const refreshing = () =&amp;gt; isPending(() =&amp;gt; users());

&amp;lt;&amp;gt;
  &amp;lt;Show when={refreshing()}&amp;gt;
    &amp;lt;RefreshIndicator /&amp;gt;
  &amp;lt;/Show&amp;gt;
  &amp;lt;Loading fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
    &amp;lt;UserList users={users()} /&amp;gt;
  &amp;lt;/Loading&amp;gt;
&amp;lt;/&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React 19 gets you most of the way here: put the update in a transition with &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useTransition&quot;&gt;&lt;code&gt;useTransition&lt;/code&gt;&lt;/a&gt;, and Suspense holds the old content on screen instead of flashing its fallback, with &lt;code&gt;isPending&lt;/code&gt; reporting the refresh. The difference is that React makes you opt into that at each call site, while Solid bakes the distinction between &quot;we have nothing to show yet&quot; and &quot;we're refreshing what's already on screen&quot; into the primitives themselves. I take this apart properly in a &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/solidjs-2-async-data&quot;&gt;follow-up deep dive on Solid 2.0's async data&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;how-do-mutations-work-in-solidjs-2-0-&quot;&gt;How do mutations work in SolidJS 2.0?&lt;/h3&gt;
&lt;p&gt;For a long time React had no blessed way to do writes, so you rolled your own or reached for a data library. React 19 changed that with &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/blog/2024/12/05/react-19&quot;&gt;Actions&lt;/a&gt; and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useOptimistic&quot;&gt;&lt;code&gt;useOptimistic&lt;/code&gt;&lt;/a&gt; hook, so this is a spot where the two frameworks have converged. Solid 2.0's version is &lt;code&gt;action&lt;/code&gt; for mutations, plus &lt;code&gt;createOptimistic&lt;/code&gt; and &lt;code&gt;createOptimisticStore&lt;/code&gt; for optimistic updates, with &lt;code&gt;refresh&lt;/code&gt; to revalidate afterward.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [messages, setMessages] = createOptimisticStore(() =&amp;gt; chatServer.loadMessages(), []);

const sendMessage = action(function* (next) {
  setMessages((m) =&amp;gt; {
    m.push(next); // optimistic: show it instantly
  });
  yield chatServer.sendMessage(next); // await the server
  refresh(messages); // reconcile with the source of truth
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generator function is doing real work here. Each &lt;code&gt;yield&lt;/code&gt; is a point where the action awaits, and the reactive system tracks the optimistic state until the real value lands and reconciles. Solid has been circling this for a while: &lt;code&gt;@solidjs/router&lt;/code&gt; already shipped &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.solidjs.com/solid-router/reference/data-apis/create-async&quot;&gt;&lt;code&gt;createAsync&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;action&lt;/code&gt; for data loading and mutations. 2.0 takes that thinking and builds async and actions into the core reactive graph, so the patterns aren't router-specific anymore.&lt;/p&gt;
&lt;h3 id=&quot;deterministic-batching-and-a-gotcha&quot;&gt;Deterministic batching, and a gotcha&lt;/h3&gt;
&lt;p&gt;One behavioral change will trip you up if you're not ready for it. Writes are batched on a microtask, and reads don't reflect a write until the batch flushes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [count, setCount] = createSignal(0);

setCount(1);
count(); // still 0: the update is queued on the microtask
flush(); // apply queued updates synchronously
count(); // now 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;flush()&lt;/code&gt; forces the queue to apply right away when you need to read the result immediately, like focusing an input after a state change. Coming from React's synchronous batching it takes a moment to adjust, but the model is predictable, and that matters once async is woven through everything.&lt;/p&gt;
&lt;h3 id=&quot;the-renames-a-react-developer-should-know&quot;&gt;The renames a React developer should know&lt;/h3&gt;
&lt;p&gt;Solid 2.0 is a major version, so it cleans house. Since this is beta, some of these identifiers could still shift before stable, but the direction is set, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/MIGRATION.md&quot;&gt;migration guide&lt;/a&gt; tracks where things stand today. The changes that matter most coming from React:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; → &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;&lt;/strong&gt;, with the initial-readiness semantics above. &lt;code&gt;&amp;lt;SuspenseList&amp;gt;&lt;/code&gt; becomes &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;createEffect&lt;/code&gt; is split&lt;/strong&gt; into a compute phase (what to track) and an apply phase (what to run with the result). Instead of reading signals inside one callback, you pass the two separately:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Solid 1.x
createEffect(() =&amp;gt; apiCall(signalB()));

// Solid 2.0
createEffect(signalB, apiCall);
createEffect(
  () =&amp;gt; [signalA(), signalB()],
  ([a, b]) =&amp;gt; a &amp;amp;&amp;amp; apiCall(b),
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you've ever shipped a bug because a value was in a &lt;code&gt;useEffect&lt;/code&gt; body but missing from the dependency array, separating the two is a direct answer to that. And in practice you reach for &lt;code&gt;createEffect&lt;/code&gt; far less than you did in 1.x, since async and derived state now cover a lot of what used to send you to an effect.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;onMount&lt;/code&gt; → &lt;code&gt;onSettled&lt;/code&gt;&lt;/strong&gt; (the closest replacement rather than a straight rename), reflecting the async-aware lifecycle.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;Index&amp;gt;&lt;/code&gt; → &lt;code&gt;&amp;lt;For keyed={false}&amp;gt;&lt;/code&gt;&lt;/strong&gt;; in that non-keyed mode the row is passed as an accessor, matching the old &lt;code&gt;&amp;lt;Index&amp;gt;&lt;/code&gt; stability model.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Store setters are draft-first by default.&lt;/strong&gt; You mutate a draft instead of threading a path. This is 1.x's &lt;code&gt;produce&lt;/code&gt; behavior promoted to the default, so you no longer wrap the callback, with a &lt;code&gt;storePath&lt;/code&gt; helper as the opt-in escape hatch for the old path style:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 2.0 default: mutate the draft
setStore((s) =&amp;gt; {
  s.todos[id].done = true;
});
// Legacy path style, opt-in
setStore(storePath(&quot;todos&quot;, id, &quot;done&quot;, true));
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;classList&lt;/code&gt; is folded into &lt;code&gt;class&lt;/code&gt;&lt;/strong&gt;, which now takes strings, arrays, and objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;use:&lt;/code&gt; directives are removed&lt;/strong&gt; in favor of &lt;code&gt;ref&lt;/code&gt; directive factories.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context is simpler.&lt;/strong&gt; The context is directly usable as the provider, so &lt;code&gt;Context.Provider&lt;/code&gt; is gone. A context created without a default is typed as its value instead of &lt;code&gt;T | undefined&lt;/code&gt;, and &lt;code&gt;useContext&lt;/code&gt; throws &lt;code&gt;ContextNotFoundError&lt;/code&gt; when there's no provider above it; a context created with a default still returns that default:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const Theme = createContext(); // no default
// 2.0: the context is its own provider
&amp;lt;Theme value=&quot;dark&quot;&amp;gt;{/* ... */}&amp;lt;/Theme&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;signals-are-heading-toward-the-standard&quot;&gt;Signals are heading toward the standard&lt;/h3&gt;
&lt;p&gt;The word &quot;signals&quot; is everywhere now, and it's worth knowing why. There's a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tc39/proposal-signals&quot;&gt;TC39 Signals proposal&lt;/a&gt; at Stage 1, with design input from the people behind Angular, Vue, Svelte, Preact, Qwik, MobX, and Solid, among others. Solid's creator Ryan Carniato has written about fine-grained reactivity for years (his &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob&quot;&gt;evolution of signals in JavaScript&lt;/a&gt; piece is a good entry point), and Solid is one of the frameworks feeding into the proposal.&lt;/p&gt;
&lt;p&gt;Worth being precise here, because it's easy to overstate: Solid 2.0 does not ship the TC39 API as its core. The proposal is synchronous, and Solid 2.0's whole thing is &lt;em&gt;async&lt;/em&gt; reactivity, which goes further than what's currently on the table. Solid influences and tracks the standard rather than implementing it. But the fact that the primitive Solid has bet on for years is now a serious effort to fold it into &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/five-things-you-might-not-know-about-javascript&quot;&gt;the JavaScript language itself&lt;/a&gt; tells you the industry is drifting toward the model React chose not to adopt. It's the same trajectory &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/typescript-7-is-here&quot;&gt;TypeScript's Go rewrite&lt;/a&gt; just traced: a bet on a different model that looked niche until it wasn't. Svelte 5 already rebuilt its reactivity around signals with its &lt;a rel=&quot;nofollow&quot; href=&quot;https://svelte.dev/blog/runes&quot;&gt;runes&lt;/a&gt; API, and it's unlikely to be the last framework to make the jump.&lt;/p&gt;
&lt;h3 id=&quot;should-you-drop-react-no-but-watch-this-closely&quot;&gt;Should you drop React? No. But watch this closely&lt;/h3&gt;
&lt;p&gt;I'm not migrating my day job to Solid this quarter, and I don't think you should either. It's a beta, the ecosystem is smaller than React's, and API names are still moving. That's the honest read.&lt;/p&gt;
&lt;p&gt;But 2.0 is the most interesting frontend release I've looked at in a while, precisely because it isn't chasing React. It takes fine-grained reactivity seriously and follows the idea all the way through to async, and the result is a set of primitives that make the hard parts of React (stale closures, dependency arrays, Suspense re-triggering, figuring out where mutations should live) feel like problems you can just stop having.&lt;/p&gt;
&lt;p&gt;If you write React and you've never actually run Solid, this is the release to spend a weekend on. Spin up a project with &lt;code&gt;solid-js@next&lt;/code&gt;, build something small with &lt;code&gt;createSignal&lt;/code&gt; and &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, and pay attention to how much of your usual React vigilance you can put down.&lt;/p&gt;
&lt;p&gt;The beta is live at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0&quot;&gt;github.com/solidjs/solid&lt;/a&gt;, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/discussions/2596&quot;&gt;announcement discussion&lt;/a&gt; is worth reading in full if any of this piqued your interest.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22950/solidjs-2-0-for-react-developers-a-first-look</guid>
<pubDate>Sat, 18 Jul 2026 22:31:47 +0000</pubDate>
</item>
<item>
<title>SolidJS 2.0 Async Data: A Deep Dive for React Devs</title>
<link>https://coderlegion.com/22951/solidjs-2-0-async-data-a-deep-dive-for-react-devs</link>
<description>&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/solidjs-2-react-developers-first-look&quot;&gt;my first look at SolidJS 2.0&lt;/a&gt; I said first-class async was the real story, then barely opened the box. This post opens it.&lt;/p&gt;
&lt;p&gt;Here's the short version. In Solid 2.0 a promise is just another value the reactive graph knows how to wait for. A computation can return one, anything that reads it suspends until it resolves, and a &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; boundary shows a fallback only until the first real value lands. There's no &lt;code&gt;createResource&lt;/code&gt;, no manual &lt;code&gt;await&lt;/code&gt; in your components, and no re-render. If you write React, this is the part that behaves least like what you're used to, so it's worth slowing down on.&lt;/p&gt;
&lt;p&gt;Everything below is from the &lt;code&gt;2.0.0-beta.18&lt;/code&gt; line (&lt;code&gt;npm install solid-js@next&lt;/code&gt;). It's a beta, and some of these names will still move before stable, so I've flagged the parts that are rougher.&lt;/p&gt;
&lt;h3 id=&quot;how-does-async-data-fetching-work-in-solid-2-0-&quot;&gt;How does async data fetching work in Solid 2.0?&lt;/h3&gt;
&lt;p&gt;A derived computation can return a promise, and the reactive graph treats the pending state as &quot;not ready&quot; until it settles. &lt;code&gt;createResource&lt;/code&gt; is gone; a plain &lt;code&gt;createMemo&lt;/code&gt; that returns a promise does the job, as the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/05-async-data.md&quot;&gt;async RFC&lt;/a&gt; puts it: async is &quot;a first-class capability of computations.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// Solid 1.x
const [user] = createResource(userId, fetchUser);

// Solid 2.0: the memo returns a promise, the graph waits on it
const user = createMemo(() =&amp;gt; fetchUser(userId()));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;user()&lt;/code&gt; anywhere and you get the resolved value. While the promise is in flight the read throws a &lt;code&gt;NotReadyError&lt;/code&gt; internally, which propagates up the graph to the nearest boundary rather than crashing. You never catch it yourself; it's the mechanism that lets &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and error boundaries work without a dedicated suspense primitive.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;userId()&lt;/code&gt; changes, the memo re-runs, returns a new promise, and the graph waits again. Same accessor, no dependency array, no effect wired up to refetch.&lt;/p&gt;
&lt;p&gt;If you need the value imperatively outside a reactive scope (a route loader, an event handler), &lt;code&gt;resolve&lt;/code&gt; hands you a real promise that settles once the expression is ready:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const currentUser = await resolve(() =&amp;gt; user());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One correction to a thing you'll see repeated: &lt;strong&gt;there is no &lt;code&gt;createAsync&lt;/code&gt; in core 2.0.&lt;/strong&gt; &lt;code&gt;createAsync&lt;/code&gt; is a &lt;code&gt;@solidjs/router&lt;/code&gt; API, and its 2.0 shape isn't settled in the sources I could find, so don't reach for it as the core data primitive. The core primitive is the async &lt;code&gt;createMemo&lt;/code&gt; above.&lt;/p&gt;
&lt;h3 id=&quot;what-replaced-lt-suspense-gt-the-lt-loading-gt-boundary&quot;&gt;What replaced &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;? The &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; boundary&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; is the direct replacement for 1.x's &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;, and the semantics are the reason the release was titled &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0&quot;&gt;&quot;The &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; is Over.&quot;&lt;/a&gt; &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; covers &lt;strong&gt;initial readiness only&lt;/strong&gt;. It shows its fallback while the subtree has nothing to render yet, and once real content is on screen it gets out of the way.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Loading fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
  &amp;lt;Profile user={user()} /&amp;gt;
&amp;lt;/Loading&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the behavior React's Suspense makes you fight for. In React, when a value a Suspense boundary depends on changes, the boundary can rip the rendered content down and flash the fallback again unless you wrap the update in &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useTransition&quot;&gt;&lt;code&gt;useTransition&lt;/code&gt;&lt;/a&gt;. That's also why router-driven navigations, which wrap their updates in transitions by default, keep the stale content visible instead and need a &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/Suspense#resetting-suspense-boundaries-on-navigation&quot;&gt;&lt;code&gt;key&lt;/code&gt;&lt;/a&gt; to reset the boundary. Solid inverts the default: after the first paint, a refetch holds the old content on screen. It won't kick you back to the spinner.&lt;/p&gt;
&lt;p&gt;When you do want the fallback to reappear on a specific change (say the whole record is being swapped, not refreshed), the new &lt;code&gt;on&lt;/code&gt; prop opts into that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Loading on={userId()} fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
  &amp;lt;Profile user={user()} /&amp;gt;
&amp;lt;/Loading&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now a change to &lt;code&gt;userId()&lt;/code&gt; while data is pending re-shows the fallback; other pending work doesn't.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-coordinate-several-boundaries-lt-reveal-gt-&quot;&gt;How do you coordinate several boundaries? &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt; replaces 1.x's &lt;code&gt;&amp;lt;SuspenseList&amp;gt;&lt;/code&gt; and controls the order in which sibling &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; boundaries reveal their content. It takes an &lt;code&gt;order&lt;/code&gt; prop of &lt;code&gt;&quot;sequential&quot;&lt;/code&gt; (the default), &lt;code&gt;&quot;together&quot;&lt;/code&gt;, or &lt;code&gt;&quot;natural&quot;&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sequential&lt;/code&gt; reveals boundaries in DOM order; later ones stay on their fallbacks until every earlier one has resolved.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;together&lt;/code&gt; holds every fallback until the whole group is ready, then reveals at once.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;natural&lt;/code&gt; lets each boundary reveal as soon as its own data lands.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's also a &lt;code&gt;collapsed&lt;/code&gt; boolean, consulted only under &lt;code&gt;order=&quot;sequential&quot;&lt;/code&gt;, that renders a single frontier fallback instead of one per boundary. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/03-control-flow.md&quot;&gt;control-flow RFC&lt;/a&gt; is the only source documenting the fine-grained nesting rules here, so treat the exact &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt; semantics as provisional until stable.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-show-a-refresh-without-a-spinner-ispending&quot;&gt;How do you show a refresh without a spinner? &lt;code&gt;isPending&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;isPending&lt;/code&gt; reports that a change to a specific read is in flight, so you can show a subtle indicator while the stale content stays put. It takes a thunk, and it actually performs the read, so where you place it matters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const refreshing = () =&amp;gt; isPending(() =&amp;gt; user());

&amp;lt;Loading fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
  &amp;lt;Show when={refreshing()}&amp;gt;
    &amp;lt;RefreshBar /&amp;gt;
  &amp;lt;/Show&amp;gt;
  &amp;lt;Profile user={user()} /&amp;gt;
&amp;lt;/Loading&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First load hits &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and shows the spinner. A later refetch keeps &lt;code&gt;&amp;lt;Profile&amp;gt;&lt;/code&gt; visible and flips &lt;code&gt;refreshing()&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, so you render a thin bar instead of tearing the page down. Stale-while-revalidate comes built into the primitive, so you don't assemble it yourself.&lt;/p&gt;
&lt;p&gt;The gotcha that will catch you: &lt;strong&gt;a bare &lt;code&gt;refresh()&lt;/code&gt; reads as not pending.&lt;/strong&gt; Re-asking the same question (a poll, a manual refresh, a confirming refetch after a mutation) is treated as silent by design. If you want that reload to register as pending, you declare it with &lt;code&gt;affects&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Silent: isPending stays false, no refresh UI
refresh(user);

// Declared: now the refresh reads as pending
affects(user);
refresh(user);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;affects&lt;/code&gt; is one of the newer, rougher corners of the API (it lives in a single RFC family and is likely to move), but the underlying rule is worth knowing now: pending is about a &lt;em&gt;changed input&lt;/em&gt;, not about work happening. There's also a &lt;code&gt;latest(fn)&lt;/code&gt; helper that peeks at the in-flight value during a transition and falls back to stale if the next value isn't ready, for when you want to show the incoming id before its data lands.&lt;/p&gt;
&lt;h3 id=&quot;where-do-mutations-live-now-action-and-optimistic-updates&quot;&gt;Where do mutations live now? &lt;code&gt;action&lt;/code&gt; and optimistic updates&lt;/h3&gt;
&lt;p&gt;Solid 2.0 gives writes a home in core with &lt;code&gt;action&lt;/code&gt;, and the shape is unusual: &lt;code&gt;action&lt;/code&gt; wraps a &lt;strong&gt;generator&lt;/strong&gt;. Each &lt;code&gt;yield&lt;/code&gt; is a point where the action awaits, which lets the reactive system track an optimistic value until the real one lands.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [todos, setTodos] = createOptimisticStore(() =&amp;gt; api.getTodos(), []);

const addTodo = action(function* (todo) {
  setTodos((t) =&amp;gt; {
    t.push(todo); // optimistic: show it immediately
  });
  yield api.addTodo(todo); // await the server
  refresh(todos); // reconcile with the source of truth
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;createOptimistic&lt;/code&gt; has the same surface as &lt;code&gt;createSignal&lt;/code&gt;, but its writes are optimistic: they can be overridden during a transition and revert when the transition completes. &lt;code&gt;createOptimisticStore&lt;/code&gt; is the store version. The RFCs document it as &lt;code&gt;createOptimisticStore(fnOrValue, seed, options?)&lt;/code&gt; while the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/MIGRATION.md&quot;&gt;migration guide&lt;/a&gt; shows a single-object form, so the exact arity is still settling; the two-argument derived form above is the one that's fully specified.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;refresh(x)&lt;/code&gt; asks a derived signal or store to recompute. It's imperative revalidation, not a piece of UI state, which is why the &lt;code&gt;affects&lt;/code&gt; dance above exists for the cases where you want a refresh to be visible. An async-generator form of &lt;code&gt;action&lt;/code&gt; also works, where a bare &lt;code&gt;yield;&lt;/code&gt; resumes the action in the same transition context after an &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you've used React 19's &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useOptimistic&quot;&gt;Actions and &lt;code&gt;useOptimistic&lt;/code&gt;&lt;/a&gt;, the intent is familiar. The difference is that Solid folds the optimistic write, the server call, and the revalidation into one generator instead of splitting them across a hook and an action.&lt;/p&gt;
&lt;h3 id=&quot;why-is-batching-deterministic-now-&quot;&gt;Why is batching deterministic now?&lt;/h3&gt;
&lt;p&gt;Solid 2.0 batches updates on a microtask by default, so a setter queues the write and reads don't reflect it until the batch flushes. &lt;code&gt;batch()&lt;/code&gt; is removed; &lt;code&gt;flush()&lt;/code&gt; is how you apply pending updates synchronously when you need the result right away.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [count, setCount] = createSignal(0);

setCount(1);
count(); // still 0: queued on the microtask
flush();
count(); // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I mentioned this in the first post as a gotcha; the reason it matters here is that it's what makes async reliable. Running the tracking (compute) half of every effect before any side-effecting half gives the graph a complete dependency picture before anything runs, which is exactly what &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and error boundaries need to decide what's ready. &lt;code&gt;flush(fn)&lt;/code&gt; also takes a callback and drains the writes inside it before returning, preserving the return value. Coming from React's synchronous batching it's an adjustment, but the model is predictable once async is threaded through everything.&lt;/p&gt;
&lt;h3 id=&quot;what-changed-for-transitions-they-re-built-in&quot;&gt;What changed for transitions? They're built in&lt;/h3&gt;
&lt;p&gt;This one's aimed squarely at React developers: &lt;strong&gt;&lt;code&gt;startTransition&lt;/code&gt; and &lt;code&gt;useTransition&lt;/code&gt; are gone.&lt;/strong&gt; Solid 2.0 treats transitions as a core scheduling concept, and multiple can be in flight at once. Pending UI is expressed through &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and &lt;code&gt;isPending&lt;/code&gt; rather than by wrapping updates. You don't opt in per call site; the framework does the bookkeeping.&lt;/p&gt;
&lt;p&gt;Here's the async surface mapped against React 19:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Task &lt;/th&gt;&lt;th&gt;React 19 &lt;/th&gt;&lt;th&gt;Solid 2.0&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Fetch data &lt;/td&gt;&lt;td&gt;&lt;code&gt;use()&lt;/code&gt; + Suspense, or a data library &lt;/td&gt;&lt;td&gt;&lt;code&gt;createMemo&lt;/code&gt; returning a promise&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Loading UI &lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;Suspense fallback&amp;gt;&lt;/code&gt; (can re-trigger) &lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; (initial readiness only)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Refresh without a flash &lt;/td&gt;&lt;td&gt;&lt;code&gt;useTransition&lt;/code&gt; + &lt;code&gt;isPending&lt;/code&gt;, opt-in per call &lt;/td&gt;&lt;td&gt;&lt;code&gt;isPending(fn)&lt;/code&gt;, observe any read&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Optimistic update &lt;/td&gt;&lt;td&gt;&lt;code&gt;useOptimistic&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;createOptimistic&lt;/code&gt; / &lt;code&gt;createOptimisticStore&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mutations &lt;/td&gt;&lt;td&gt;Actions / form actions &lt;/td&gt;&lt;td&gt;&lt;code&gt;action(function*)&lt;/code&gt; + &lt;code&gt;refresh&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Batching &lt;/td&gt;&lt;td&gt;automatic sync batching &lt;/td&gt;&lt;td&gt;microtask batching + &lt;code&gt;flush()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The pattern across that table: React exposes each capability as a hook you opt into, while Solid pushes the distinction between &quot;nothing to show&quot; and &quot;refreshing what's shown&quot; down into the primitives, so you get the good behavior by default. That reactivity model is the same shift toward &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/five-things-you-might-not-know-about-javascript&quot;&gt;signals happening across the whole ecosystem&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;should-you-build-on-this-yet-&quot;&gt;Should you build on this yet?&lt;/h3&gt;
&lt;p&gt;Not in production. It's &lt;code&gt;beta.18&lt;/code&gt;, the same early-but-real stage &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/pnpm-v12-rust-rewrite&quot;&gt;pnpm v12's Rust rewrite&lt;/a&gt; is in. The async core (async &lt;code&gt;createMemo&lt;/code&gt;, &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, microtask batching with &lt;code&gt;flush&lt;/code&gt;, generator &lt;code&gt;action&lt;/code&gt;s) is stable enough to learn on, but the edges (&lt;code&gt;affects&lt;/code&gt;, &lt;code&gt;createOptimisticStore&lt;/code&gt;'s arity, &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt;'s nesting rules) are still moving. The announcement is worth reading in full in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/discussions/2596&quot;&gt;beta.0 discussion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What I'd actually do, and what I'm doing: build a small data-heavy screen with &lt;code&gt;solid-js@next&lt;/code&gt;, wire up a &lt;code&gt;createMemo&lt;/code&gt; that fetches, drop a &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; around it, and trigger a refetch. Watch the old content stay on screen while the new content loads. That single behavior is the clearest argument for why fine-grained reactivity was worth following all the way to async.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22951/solidjs-2-0-async-data-a-deep-dive-for-react-devs</guid>
<pubDate>Sat, 18 Jul 2026 22:31:40 +0000</pubDate>
</item>
<item>
<title>SolidJS 2.0 Async Data: A Deep Dive for React Devs</title>
<link>https://coderlegion.com/22779/solidjs-2-0-async-data-a-deep-dive-for-react-devs</link>
<description>&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/solidjs-2-react-developers-first-look&quot;&gt;my first look at SolidJS 2.0&lt;/a&gt; I said first-class async was the real story, then barely opened the box. This post opens it.&lt;/p&gt;
&lt;p&gt;Here's the short version. In Solid 2.0 a promise is just another value the reactive graph knows how to wait for. A computation can return one, anything that reads it suspends until it resolves, and a &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; boundary shows a fallback only until the first real value lands. There's no &lt;code&gt;createResource&lt;/code&gt;, no manual &lt;code&gt;await&lt;/code&gt; in your components, and no re-render. If you write React, this is the part that behaves least like what you're used to, so it's worth slowing down on.&lt;/p&gt;
&lt;p&gt;Everything below is from the &lt;code&gt;2.0.0-beta.18&lt;/code&gt; line (&lt;code&gt;npm install solid-js@next&lt;/code&gt;). It's a beta, and some of these names will still move before stable, so I've flagged the parts that are rougher.&lt;/p&gt;
&lt;h3 id=&quot;how-does-async-data-fetching-work-in-solid-2-0--1&quot;&gt;How does async data fetching work in Solid 2.0?&lt;/h3&gt;
&lt;p&gt;A derived computation can return a promise, and the reactive graph treats the pending state as &quot;not ready&quot; until it settles. &lt;code&gt;createResource&lt;/code&gt; is gone; a plain &lt;code&gt;createMemo&lt;/code&gt; that returns a promise does the job, as the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/05-async-data.md&quot;&gt;async RFC&lt;/a&gt; puts it: async is &quot;a first-class capability of computations.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// Solid 1.x
const [user] = createResource(userId, fetchUser);

// Solid 2.0: the memo returns a promise, the graph waits on it
const user = createMemo(() =&amp;gt; fetchUser(userId()));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;user()&lt;/code&gt; anywhere and you get the resolved value. While the promise is in flight the read throws a &lt;code&gt;NotReadyError&lt;/code&gt; internally, which propagates up the graph to the nearest boundary rather than crashing. You never catch it yourself; it's the mechanism that lets &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and error boundaries work without a dedicated suspense primitive.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;userId()&lt;/code&gt; changes, the memo re-runs, returns a new promise, and the graph waits again. Same accessor, no dependency array, no effect wired up to refetch.&lt;/p&gt;
&lt;p&gt;If you need the value imperatively outside a reactive scope (a route loader, an event handler), &lt;code&gt;resolve&lt;/code&gt; hands you a real promise that settles once the expression is ready:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const currentUser = await resolve(() =&amp;gt; user());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One correction to a thing you'll see repeated: &lt;strong&gt;there is no &lt;code&gt;createAsync&lt;/code&gt; in core 2.0.&lt;/strong&gt; &lt;code&gt;createAsync&lt;/code&gt; is a &lt;code&gt;@solidjs/router&lt;/code&gt; API, and its 2.0 shape isn't settled in the sources I could find, so don't reach for it as the core data primitive. The core primitive is the async &lt;code&gt;createMemo&lt;/code&gt; above.&lt;/p&gt;
&lt;h3 id=&quot;what-replaced-lt-suspense-gt-the-lt-loading-gt-boundary-1&quot;&gt;What replaced &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;? The &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; boundary&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; is the direct replacement for 1.x's &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;, and the semantics are the reason the release was titled &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0&quot;&gt;&quot;The &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; is Over.&quot;&lt;/a&gt; &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; covers &lt;strong&gt;initial readiness only&lt;/strong&gt;. It shows its fallback while the subtree has nothing to render yet, and once real content is on screen it gets out of the way.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Loading fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
  &amp;lt;Profile user={user()} /&amp;gt;
&amp;lt;/Loading&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the behavior React's Suspense makes you fight for. In React, when a value a Suspense boundary depends on changes, the boundary can rip the rendered content down and flash the fallback again unless you wrap the update in &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useTransition&quot;&gt;&lt;code&gt;useTransition&lt;/code&gt;&lt;/a&gt;. Solid inverts the default: after the first paint, a refetch holds the old content on screen. It won't kick you back to the spinner.&lt;/p&gt;
&lt;p&gt;When you do want the fallback to reappear on a specific change (say the whole record is being swapped, not refreshed), the new &lt;code&gt;on&lt;/code&gt; prop opts into that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Loading on={userId()} fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
  &amp;lt;Profile user={user()} /&amp;gt;
&amp;lt;/Loading&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now a change to &lt;code&gt;userId()&lt;/code&gt; while data is pending re-shows the fallback; other pending work doesn't.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-coordinate-several-boundaries-lt-reveal-gt--1&quot;&gt;How do you coordinate several boundaries? &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt; replaces 1.x's &lt;code&gt;&amp;lt;SuspenseList&amp;gt;&lt;/code&gt; and controls the order in which sibling &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; boundaries reveal their content. It takes an &lt;code&gt;order&lt;/code&gt; prop of &lt;code&gt;&quot;sequential&quot;&lt;/code&gt; (the default), &lt;code&gt;&quot;together&quot;&lt;/code&gt;, or &lt;code&gt;&quot;natural&quot;&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sequential&lt;/code&gt; reveals boundaries in DOM order; later ones stay on their fallbacks until every earlier one has resolved.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;together&lt;/code&gt; holds every fallback until the whole group is ready, then reveals at once.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;natural&lt;/code&gt; lets each boundary reveal as soon as its own data lands.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There's also a &lt;code&gt;collapsed&lt;/code&gt; boolean, consulted only under &lt;code&gt;order=&quot;sequential&quot;&lt;/code&gt;, that renders a single frontier fallback instead of one per boundary. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/03-control-flow.md&quot;&gt;control-flow RFC&lt;/a&gt; is the only source documenting the fine-grained nesting rules here, so treat the exact &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt; semantics as provisional until stable.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-show-a-refresh-without-a-spinner-ispending-1&quot;&gt;How do you show a refresh without a spinner? &lt;code&gt;isPending&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;isPending&lt;/code&gt; reports that a change to a specific read is in flight, so you can show a subtle indicator while the stale content stays put. It takes a thunk, and it actually performs the read, so where you place it matters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const refreshing = () =&amp;gt; isPending(() =&amp;gt; user());

&amp;lt;Loading fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
  &amp;lt;Show when={refreshing()}&amp;gt;
    &amp;lt;RefreshBar /&amp;gt;
  &amp;lt;/Show&amp;gt;
  &amp;lt;Profile user={user()} /&amp;gt;
&amp;lt;/Loading&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First load hits &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and shows the spinner. A later refetch keeps &lt;code&gt;&amp;lt;Profile&amp;gt;&lt;/code&gt; visible and flips &lt;code&gt;refreshing()&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, so you render a thin bar instead of tearing the page down. Stale-while-revalidate comes built into the primitive, so you don't assemble it yourself.&lt;/p&gt;
&lt;p&gt;The gotcha that will catch you: &lt;strong&gt;a bare &lt;code&gt;refresh()&lt;/code&gt; reads as not pending.&lt;/strong&gt; Re-asking the same question (a poll, a manual refresh, a confirming refetch after a mutation) is treated as silent by design. If you want that reload to register as pending, you declare it with &lt;code&gt;affects&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Silent: isPending stays false, no refresh UI
refresh(user);

// Declared: now the refresh reads as pending
affects(user);
refresh(user);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;affects&lt;/code&gt; is one of the newer, rougher corners of the API (it lives in a single RFC family and is likely to move), but the underlying rule is worth knowing now: pending is about a &lt;em&gt;changed input&lt;/em&gt;, not about work happening. There's also a &lt;code&gt;latest(fn)&lt;/code&gt; helper that peeks at the in-flight value during a transition and falls back to stale if the next value isn't ready, for when you want to show the incoming id before its data lands.&lt;/p&gt;
&lt;h3 id=&quot;where-do-mutations-live-now-action-and-optimistic-updates-1&quot;&gt;Where do mutations live now? &lt;code&gt;action&lt;/code&gt; and optimistic updates&lt;/h3&gt;
&lt;p&gt;Solid 2.0 gives writes a home in core with &lt;code&gt;action&lt;/code&gt;, and the shape is unusual: &lt;code&gt;action&lt;/code&gt; wraps a &lt;strong&gt;generator&lt;/strong&gt;. Each &lt;code&gt;yield&lt;/code&gt; is a point where the action awaits, which lets the reactive system track an optimistic value until the real one lands.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [todos, setTodos] = createOptimisticStore(() =&amp;gt; api.getTodos(), []);

const addTodo = action(function* (todo) {
  setTodos((t) =&amp;gt; {
    t.push(todo); // optimistic: show it immediately
  });
  yield api.addTodo(todo); // await the server
  refresh(todos); // reconcile with the source of truth
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;createOptimistic&lt;/code&gt; has the same surface as &lt;code&gt;createSignal&lt;/code&gt;, but its writes are optimistic: they can be overridden during a transition and revert when the transition completes. &lt;code&gt;createOptimisticStore&lt;/code&gt; is the store version. The RFCs document it as &lt;code&gt;createOptimisticStore(fnOrValue, seed, options?)&lt;/code&gt; while the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/MIGRATION.md&quot;&gt;migration guide&lt;/a&gt; shows a single-object form, so the exact arity is still settling; the two-argument derived form above is the one that's fully specified.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;refresh(x)&lt;/code&gt; asks a derived signal or store to recompute. It's imperative revalidation, not a piece of UI state, which is why the &lt;code&gt;affects&lt;/code&gt; dance above exists for the cases where you want a refresh to be visible. An async-generator form of &lt;code&gt;action&lt;/code&gt; also works, where a bare &lt;code&gt;yield;&lt;/code&gt; resumes the action in the same transition context after an &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you've used React 19's &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useOptimistic&quot;&gt;Actions and &lt;code&gt;useOptimistic&lt;/code&gt;&lt;/a&gt;, the intent is familiar. The difference is that Solid folds the optimistic write, the server call, and the revalidation into one generator instead of splitting them across a hook and an action.&lt;/p&gt;
&lt;h3 id=&quot;why-is-batching-deterministic-now--1&quot;&gt;Why is batching deterministic now?&lt;/h3&gt;
&lt;p&gt;Solid 2.0 batches updates on a microtask by default, so a setter queues the write and reads don't reflect it until the batch flushes. &lt;code&gt;batch()&lt;/code&gt; is removed; &lt;code&gt;flush()&lt;/code&gt; is how you apply pending updates synchronously when you need the result right away.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [count, setCount] = createSignal(0);

setCount(1);
count(); // still 0: queued on the microtask
flush();
count(); // 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I mentioned this in the first post as a gotcha; the reason it matters here is that it's what makes async reliable. Running the tracking (compute) half of every effect before any side-effecting half gives the graph a complete dependency picture before anything runs, which is exactly what &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and error boundaries need to decide what's ready. &lt;code&gt;flush(fn)&lt;/code&gt; also takes a callback and drains the writes inside it before returning, preserving the return value. Coming from React's synchronous batching it's an adjustment, but the model is predictable once async is threaded through everything.&lt;/p&gt;
&lt;h3 id=&quot;what-changed-for-transitions-they-re-built-in-1&quot;&gt;What changed for transitions? They're built in&lt;/h3&gt;
&lt;p&gt;This one's aimed squarely at React developers: &lt;strong&gt;&lt;code&gt;startTransition&lt;/code&gt; and &lt;code&gt;useTransition&lt;/code&gt; are gone.&lt;/strong&gt; Solid 2.0 treats transitions as a core scheduling concept, and multiple can be in flight at once. Pending UI is expressed through &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; and &lt;code&gt;isPending&lt;/code&gt; rather than by wrapping updates. You don't opt in per call site; the framework does the bookkeeping.&lt;/p&gt;
&lt;p&gt;Here's the async surface mapped against React 19:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Task &lt;/th&gt;&lt;th&gt;React 19 &lt;/th&gt;&lt;th&gt;Solid 2.0&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Fetch data &lt;/td&gt;&lt;td&gt;&lt;code&gt;use()&lt;/code&gt; + Suspense, or a data library &lt;/td&gt;&lt;td&gt;&lt;code&gt;createMemo&lt;/code&gt; returning a promise&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Loading UI &lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;Suspense fallback&amp;gt;&lt;/code&gt; (can re-trigger) &lt;/td&gt;&lt;td&gt;&lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; (initial readiness only)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Refresh without a flash &lt;/td&gt;&lt;td&gt;&lt;code&gt;useTransition&lt;/code&gt; + &lt;code&gt;isPending&lt;/code&gt;, opt-in per call &lt;/td&gt;&lt;td&gt;&lt;code&gt;isPending(fn)&lt;/code&gt;, observe any read&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Optimistic update &lt;/td&gt;&lt;td&gt;&lt;code&gt;useOptimistic&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;createOptimistic&lt;/code&gt; / &lt;code&gt;createOptimisticStore&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mutations &lt;/td&gt;&lt;td&gt;Actions / form actions &lt;/td&gt;&lt;td&gt;&lt;code&gt;action(function*)&lt;/code&gt; + &lt;code&gt;refresh&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Batching &lt;/td&gt;&lt;td&gt;automatic sync batching &lt;/td&gt;&lt;td&gt;microtask batching + &lt;code&gt;flush()&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The pattern across that table: React exposes each capability as a hook you opt into, while Solid pushes the distinction between &quot;nothing to show&quot; and &quot;refreshing what's shown&quot; down into the primitives, so you get the good behavior by default. That reactivity model is the same shift toward &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/five-things-you-might-not-know-about-javascript&quot;&gt;signals happening across the whole ecosystem&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;should-you-build-on-this-yet--1&quot;&gt;Should you build on this yet?&lt;/h3&gt;
&lt;p&gt;Not in production. It's &lt;code&gt;beta.18&lt;/code&gt;, the async core (async &lt;code&gt;createMemo&lt;/code&gt;, &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, microtask batching with &lt;code&gt;flush&lt;/code&gt;, generator &lt;code&gt;action&lt;/code&gt;s) is stable enough to learn on, but the edges (&lt;code&gt;affects&lt;/code&gt;, &lt;code&gt;createOptimisticStore&lt;/code&gt;'s arity, &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt;'s nesting rules) are still moving. The announcement is worth reading in full in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/discussions/2596&quot;&gt;beta.0 discussion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What I'd actually do, and what I'm doing: build a small data-heavy screen with &lt;code&gt;solid-js@next&lt;/code&gt;, wire up a &lt;code&gt;createMemo&lt;/code&gt; that fetches, drop a &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; around it, and trigger a refetch. Watch the old content stay on screen while the new content loads. That single behavior is the clearest argument for why fine-grained reactivity was worth following all the way to async.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22779/solidjs-2-0-async-data-a-deep-dive-for-react-devs</guid>
<pubDate>Thu, 16 Jul 2026 06:40:00 +0000</pubDate>
</item>
<item>
<title>SolidJS 2.0: A React Developer's First Look at Signals and Async</title>
<link>https://coderlegion.com/21782/solidjs-2-0-a-react-developers-first-look-at-signals-and-async</link>
<description>&lt;p&gt;I've shipped React for the better part of a decade. Hooks, Suspense, Server Components, the whole tour. So when the &lt;strong&gt;SolidJS 2.0&lt;/strong&gt; beta showed up in my feed with the tagline &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0&quot;&gt;&quot;The &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; is Over,&quot;&lt;/a&gt; I rolled my eyes a little and then clicked anyway.&lt;/p&gt;
&lt;p&gt;I'm glad I did. Solid has been the framework React people admire from a distance for years, and 2.0 is the release that finally made me install it and poke at the reactivity model instead of just reading about it. This post is my honest first look: what's new, what a React developer will recognize, and where Solid quietly does something React can't.&lt;/p&gt;
&lt;h3 id=&quot;what-is-solidjs-2-0--1&quot;&gt;What is SolidJS 2.0?&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.solidjs.com&quot;&gt;SolidJS&lt;/a&gt; is a UI library with React-like JSX and a completely different engine underneath. Instead of re-rendering components and diffing a virtual DOM, it uses &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.solidjs.com/concepts/intro-to-reactivity&quot;&gt;&lt;strong&gt;fine-grained reactivity&lt;/strong&gt;&lt;/a&gt;: your component runs once, and updates flow directly to the exact DOM nodes that depend on a piece of state.&lt;/p&gt;
&lt;p&gt;The 2.0 line is currently in beta. The first public build, &lt;code&gt;v2.0.0-beta.0&lt;/code&gt;, landed on March 3, 2026, and the team skipped the alpha phase entirely because the milestones planned for it stopped feeling worth their own release. You can try it today:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pnpm add solid-js@next
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The headline of the whole release is async. Solid's reactive graph now understands promises natively, and a lot of the API changes fall out of that one decision.&lt;/p&gt;
&lt;h3 id=&quot;fine-grained-reactivity-from-a-react-brain-1&quot;&gt;Fine-grained reactivity, from a React brain&lt;/h3&gt;
&lt;p&gt;Here's the mental model shift, and it's the thing to internalize before anything else makes sense.&lt;/p&gt;
&lt;p&gt;In React, state changes re-run your component function. React then builds a new virtual DOM tree and diffs it against the old one to figure out what actually changed. &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt;, and dependency arrays exist to keep that re-render machine from doing too much work. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/learn/react-compiler&quot;&gt;React Compiler&lt;/a&gt; automates a lot of that memoization now, but the underlying model is the same: the component re-runs and React diffs the result.&lt;/p&gt;
&lt;p&gt;In Solid, the component body is setup code. It runs a single time. When you read a signal in your JSX, Solid records that specific dependency and wires it straight to the DOM. Change the signal later and only that text node or attribute updates. No component re-render, and no dependency array to keep honest.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Counter() {
  const [count, setCount] = createSignal(0);

  // This whole function runs ONCE. Only the text node updates on click.
  return &amp;lt;button onClick={() =&amp;gt; setCount(count() + 1)}&amp;gt;{count()}&amp;lt;/button&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tell for a React dev: &lt;code&gt;count&lt;/code&gt; is a function, not a value. You call &lt;code&gt;count()&lt;/code&gt; to read it. That call is what subscribes the surrounding computation to changes. Once it clicks, the absence of a dependency array stops feeling like something is missing and starts feeling like a bug class that no longer exists.&lt;/p&gt;
&lt;h3 id=&quot;first-class-async-is-the-real-story&quot;&gt;First-class async is the real story&lt;/h3&gt;
&lt;p&gt;This is where 2.0 earns its version bump. In Solid 1.x, async data meant &lt;code&gt;createResource&lt;/code&gt; and wrapping things in &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;. In 2.0, a derived computation can just return a promise, and the graph handles suspending and resuming for you.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// Solid 1.x
const [data] = createResource(userId, fetchUser);

// Solid 2.0: the promise flows through the reactive graph
const data = createMemo(() =&amp;gt; fetchUser(userId()));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suspense gets replaced by &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, and the semantics are the part I actually care about. &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt; is scoped to &lt;em&gt;initial&lt;/em&gt; readiness. It shows a fallback while the subtree can't render anything yet, and then it stays out of your way. When &lt;code&gt;userId()&lt;/code&gt; changes and the data refetches, the UI doesn't tear itself down and flash a spinner. It holds the old content until the new content is ready.&lt;/p&gt;
&lt;p&gt;To show that a refresh is in flight, you reach for &lt;code&gt;isPending&lt;/code&gt;, which reports on pending reactive work without unmounting anything:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const users = createMemo(() =&amp;gt; api.listUsers());
const refreshing = () =&amp;gt; isPending(() =&amp;gt; users());

&amp;lt;&amp;gt;
  &amp;lt;Show when={refreshing()}&amp;gt;
    &amp;lt;RefreshIndicator /&amp;gt;
  &amp;lt;/Show&amp;gt;
  &amp;lt;Loading fallback={&amp;lt;Spinner /&amp;gt;}&amp;gt;
    &amp;lt;UserList users={users()} /&amp;gt;
  &amp;lt;/Loading&amp;gt;
&amp;lt;/&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React 19 gets you most of the way here: put the update in a transition with &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useTransition&quot;&gt;&lt;code&gt;useTransition&lt;/code&gt;&lt;/a&gt;, and Suspense holds the old content on screen instead of flashing its fallback, with &lt;code&gt;isPending&lt;/code&gt; reporting the refresh. The difference is that React makes you opt into that at each call site, while Solid bakes the distinction between &quot;we have nothing to show yet&quot; and &quot;we're refreshing what's already on screen&quot; into the primitives themselves.&lt;/p&gt;
&lt;h3 id=&quot;mutations-have-a-home-now&quot;&gt;Mutations have a home now&lt;/h3&gt;
&lt;p&gt;For a long time React had no blessed way to do writes, so you rolled your own or reached for a data library. React 19 changed that with &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/blog/2024/12/05/react-19&quot;&gt;Actions&lt;/a&gt; and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://react.dev/reference/react/useOptimistic&quot;&gt;&lt;code&gt;useOptimistic&lt;/code&gt;&lt;/a&gt; hook, so this is a spot where the two frameworks have converged. Solid 2.0's version is &lt;code&gt;action&lt;/code&gt; for mutations, plus &lt;code&gt;createOptimistic&lt;/code&gt; and &lt;code&gt;createOptimisticStore&lt;/code&gt; for optimistic updates, with &lt;code&gt;refresh&lt;/code&gt; to revalidate afterward.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [messages, setMessages] = createOptimisticStore(() =&amp;gt; chatServer.loadMessages(), []);

const sendMessage = action(function* (next) {
  setMessages((m) =&amp;gt; {
    m.push(next); // optimistic: show it instantly
  });
  yield chatServer.sendMessage(next); // await the server
  refresh(messages); // reconcile with the source of truth
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The generator function is doing real work here. Each &lt;code&gt;yield&lt;/code&gt; is a point where the action awaits, and the reactive system tracks the optimistic state until the real value lands and reconciles. Solid has been circling this for a while: &lt;code&gt;@solidjs/router&lt;/code&gt; already shipped &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.solidjs.com/solid-router/reference/data-apis/create-async&quot;&gt;&lt;code&gt;createAsync&lt;/code&gt;&lt;/a&gt; and &lt;code&gt;action&lt;/code&gt; for data loading and mutations. 2.0 takes that thinking and builds async and actions into the core reactive graph, so the patterns aren't router-specific anymore.&lt;/p&gt;
&lt;h3 id=&quot;deterministic-batching-and-a-gotcha-1&quot;&gt;Deterministic batching, and a gotcha&lt;/h3&gt;
&lt;p&gt;One behavioral change will trip you up if you're not ready for it. Writes are batched on a microtask, and reads don't reflect a write until the batch flushes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [count, setCount] = createSignal(0);

setCount(1);
count(); // still 0: the update is queued on the microtask
flush(); // apply queued updates synchronously
count(); // now 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;flush()&lt;/code&gt; forces the queue to apply right away when you need to read the result immediately, like focusing an input after a state change. Coming from React's synchronous batching it takes a moment to adjust, but the model is predictable, and that matters once async is woven through everything.&lt;/p&gt;
&lt;h3 id=&quot;the-renames-a-react-developer-should-know-1&quot;&gt;The renames a React developer should know&lt;/h3&gt;
&lt;p&gt;Solid 2.0 is a major version, so it cleans house. Since this is beta, some of these identifiers could still shift before stable, but the direction is set, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/blob/next/documentation/solid-2.0/MIGRATION.md&quot;&gt;migration guide&lt;/a&gt; tracks where things stand today. The changes that matter most coming from React:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt; → &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;&lt;/strong&gt;, with the initial-readiness semantics above. &lt;code&gt;&amp;lt;SuspenseList&amp;gt;&lt;/code&gt; becomes &lt;code&gt;&amp;lt;Reveal&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;createEffect&lt;/code&gt; is split&lt;/strong&gt; into a compute phase (what to track) and an apply phase (what to run with the result). Instead of reading signals inside one callback, you pass the two separately:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Solid 1.x
createEffect(() =&amp;gt; apiCall(signalB()));

// Solid 2.0
createEffect(signalB, apiCall);
createEffect(
  () =&amp;gt; [signalA(), signalB()],
  ([a, b]) =&amp;gt; a &amp;amp;&amp;amp; apiCall(b),
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you've ever shipped a bug because a value was in a &lt;code&gt;useEffect&lt;/code&gt; body but missing from the dependency array, separating the two is a direct answer to that. &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/RyanCarniato&quot;&gt;Ryan Carniato&lt;/a&gt; has also said the idiomatic 2.0 code reaches for &lt;code&gt;createEffect&lt;/code&gt; far less than 1.x did, since async and derived state now cover cases you used to handle with an effect.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;onMount&lt;/code&gt; → &lt;code&gt;onSettled&lt;/code&gt;&lt;/strong&gt;, reflecting the async-aware lifecycle.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;Index&amp;gt;&lt;/code&gt; → &lt;code&gt;&amp;lt;For keyed={false}&amp;gt;&lt;/code&gt;&lt;/strong&gt;; in that non-keyed mode the row is passed as an accessor, matching the old &lt;code&gt;&amp;lt;Index&amp;gt;&lt;/code&gt; stability model.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Store setters are draft-first by default.&lt;/strong&gt; You mutate a draft instead of threading a path. This is 1.x's &lt;code&gt;produce&lt;/code&gt; behavior promoted to the default, so you no longer wrap the callback, with a &lt;code&gt;storePath&lt;/code&gt; helper as the opt-in escape hatch for the old path style:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 2.0 default: mutate the draft
setStore((s) =&amp;gt; {
  s.todos[id].done = true;
});
// Legacy path style, opt-in
setStore(storePath(&quot;todos&quot;, id, &quot;done&quot;, true));
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;classList&lt;/code&gt; is folded into &lt;code&gt;class&lt;/code&gt;&lt;/strong&gt;, which now takes strings, arrays, and objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;use:&lt;/code&gt; directives are removed&lt;/strong&gt; in favor of &lt;code&gt;ref&lt;/code&gt; directive factories.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context is simpler.&lt;/strong&gt; The context is directly usable as the provider, so &lt;code&gt;Context.Provider&lt;/code&gt; is gone. A context created without a default is typed as its value instead of &lt;code&gt;T | undefined&lt;/code&gt;, and &lt;code&gt;useContext&lt;/code&gt; throws &lt;code&gt;ContextNotFoundError&lt;/code&gt; when there's no provider above it; a context created with a default still returns that default:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const Theme = createContext(); // no default
// 2.0: the context is its own provider
&amp;lt;Theme value=&quot;dark&quot;&amp;gt;{/* ... */}&amp;lt;/Theme&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;signals-are-heading-toward-the-standard-1&quot;&gt;Signals are heading toward the standard&lt;/h3&gt;
&lt;p&gt;The word &quot;signals&quot; is everywhere now, and it's worth knowing why. There's a &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/tc39/proposal-signals&quot;&gt;TC39 Signals proposal&lt;/a&gt; at Stage 1, with design input from the people behind Angular, Vue, Svelte, Preact, Qwik, MobX, and Solid. Ryan Carniato, Solid's creator, has been part of that conversation for years, and his &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/this-is-learning/the-evolution-of-signals-in-javascript-8ob&quot;&gt;writing on fine-grained reactivity&lt;/a&gt; helped push it onto the committee's radar.&lt;/p&gt;
&lt;p&gt;Worth being precise here, because it's easy to overstate: Solid 2.0 does not ship the TC39 API as its core. The proposal is synchronous, and Solid 2.0's whole thing is &lt;em&gt;async&lt;/em&gt; reactivity, which goes further than what's currently on the table. Solid influences and tracks the standard rather than implementing it. But the fact that the primitive Solid has bet on for years is now a serious standardization effort tells you the industry is drifting toward the model React chose not to adopt. Svelte 5 already rebuilt its reactivity around signals with its &lt;a rel=&quot;nofollow&quot; href=&quot;https://svelte.dev/docs/svelte/what-are-runes&quot;&gt;runes&lt;/a&gt; API, and it's unlikely to be the last framework to make the jump.&lt;/p&gt;
&lt;h3 id=&quot;should-you-drop-react-no-but-watch-this-closely-&quot;&gt;Should you drop React? No. But watch this closely.&lt;/h3&gt;
&lt;p&gt;I'm not migrating my day job to Solid this quarter, and I don't think you should either. It's a beta, the ecosystem is smaller than React's, and API names are still moving. That's the honest read.&lt;/p&gt;
&lt;p&gt;But 2.0 is the most interesting frontend release I've looked at in a while, precisely because it isn't chasing React. It takes fine-grained reactivity seriously and follows the idea all the way through to async, and the result is a set of primitives that make the hard parts of React (stale closures, dependency arrays, Suspense re-triggering, figuring out where mutations should live) feel like problems you can just stop having.&lt;/p&gt;
&lt;p&gt;If you write React and you've never actually run Solid, this is the release to spend a weekend on. Spin up a project with &lt;code&gt;solid-js@next&lt;/code&gt;, build something small with &lt;code&gt;createSignal&lt;/code&gt; and &lt;code&gt;&amp;lt;Loading&amp;gt;&lt;/code&gt;, and pay attention to how much of your usual React vigilance you can put down.&lt;/p&gt;
&lt;p&gt;The beta is live at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/releases/tag/v2.0.0-beta.0&quot;&gt;github.com/solidjs/solid&lt;/a&gt;, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/solidjs/solid/discussions/2596&quot;&gt;announcement discussion&lt;/a&gt; is worth reading in full if any of this piqued your interest.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21782/solidjs-2-0-a-react-developers-first-look-at-signals-and-async</guid>
<pubDate>Thu, 02 Jul 2026 20:12:29 +0000</pubDate>
</item>
</channel>
</rss>