Navigation
Search
|
Hands-on with Solid: Reactive programming with signals
Wednesday August 27, 2025. 11:00 AM , from InfoWorld
![]() With a clean core design built for speed and a full set of enterprise features, Solid charted an impressive 90% developer satisfaction in the January 2025 State of JavaScript survey. Let’s get started with Solid.js. Scaffolding a new project with SolidStart Like other frameworks in its class, Solid offers a full-stack platform that supports capabilities like server-side rendering (SSR). It also features a command-line tool for starting and managing projects. SolidStart is the official way to scaffold a new project in Solid. I recently reviewed my experience using Roo Code and Gemini in VS Code, so I decided to use this AI-powered stack to generate the scaffolding for my Solid app. I also coded things manually to see how the two approaches compared. On my request, Roo Code generated the following scaffolding: npm create solid@latest -- --template typescript --name iw-solid-app Although that command is reasonable for starting a new project with a template and TypeScript, the AI wasn’t smart enough to engage with the interactive CLI prompt, so I had to drop into the terminal and answer those questions. The command then failed with an error, so I just ran what I would have run normally (npm create solid@latest) and responded to the questions as I saw fit. This mix of manual and AI-assisted coding seems to be the current state of the art for modern development. The need to actually understand how the tech works doesn’t look like it’s going extinct anytime soon; AI is just another way to use what we already know. To run the app in dev mode, enter $ npm run dev. Now if you go to you’ll see the following template: Matthew Tyson The dev server supports on-the-fly updates including hot module replacement (HMR). As you make changes to files, they’ll automatically update what is displayed in the browser. Using signals for reactive state management Like other frameworks, Solid lets you create components to encapsulate functionality in the UI. And like React, it also uses JSX as its template language. The counter.tsx component provided by SolidStart gives you an idea for how Solid uses signals to manage state: import { createSignal } from 'solid-js'; import './Counter.css'; export default function Counter() { const [count, setCount] = createSignal(0); return ( setCount(count() + 1)} type='button'> Clicks: {count()} ); } This almost maps directly to a React component, except where React would have useState, Solid uses createSignal. Signals work differently under the hood than state. They give Solid a more fine-grained access to the DOM, so that the engine will only update the specific node that requires it. Signals are also more general operators, and they are used across Solid, whereas useState only manages component state. Also notice that signals, like our count here, are accessed by calling the getter function, (count()}, rather than directly accessing the variable. Signals and effects Because Solid components are a function call, they only execute once upon creation. Therefore, if you had a signal that you needed to access outside of the template, you would have to wrap it in an effect. Inside the JSX, you can just call the signal getter, like we have just done with count(), and it will give you the reactive value as it changes. However, in the body of the function, you need to use an effect: console.log('Count:',count()); // ❌ not tracked - only runs once during initialization. createEffect(()=>{ console.log(count()); // ✅ will update whenever `count()` changes. }); // snippet from the docs So, useEffect is a kind of ad hoc observer for signals. Anytime you need to perform some out-of-template effect based on a signal, that is what you need to use. Between createSignal, createEffect, and the native reactivity of JSX, you have most of the basic elements of reactivity. Fetching a remote API with createResource Solid layers capabilities on top of the basic features of signals. One of those capabilities is createResource, which makes it easy to handle asynchronous requests in a reactive way. This is a simple layer on top of createSignal. Let’s use it create a Joke component that fetches 10 jokes from the Joke API and displays them. First, we include the component in index.tsx: import Joker from '~/components/Joker'; // … Then, in the components directory, we can create a very basic fetch operation: import { createResource } from 'solid-js'; const fetchProgrammingJokes = async () => { const response = await fetch(`https://official-joke-api.appspot.com/jokes/programming/ten`); return response.json(); }; export default function JokerSimple() { const [jokes] = createResource(fetchProgrammingJokes); return ( Raw Jokes JSON {JSON.stringify(jokes(), null, 2)} ); } This is a very simple version without any error handling or display logic. The point is to really highlight how the createResource function wraps createSignal and makes it very simple to handle asynchronous operations like fetch. The key is that fetchProgrammingJokes returns a promise, and the createResource call lets us produce a jokes() function that we can freely use in the template to capture the results. Solid’s idiomatic looping Now let’s make this display a bit nicer using the element. This is Solid’s idiomatic way of looping and it’s built for better performance than using the map functional operator: import { createResource, For } from 'solid-js'; //... {(joke) => ( {joke.setup} {joke.punchline} )} lets us consume an iterable like the array returned by the Jokes() promise when it resolves. We don’t have to worry about the mechanics of the asynchronous resolution. Inside the loop, we have access to the joke iterator object. Here we use it to create a simple list of items. (Note that I’m not responsible for the quality of the jokes.) Where did the API go to eat?To the RESTaurant. Suspense boundaries The component makes it easier to define boundaries where an asynchronous loading can display its different states. You can see at work in the following example, which builds on our last one: import { createResource, For, Suspense } from 'solid-js'; //... {(joke) => ( {joke.setup} {joke.punchline} )} Notice that we don’t have to tell what promise or resource it is waiting on. It automatically detects and waits for the resolution of the asynchronous operations inside of it, displaying the placeholder content in the meantime. This makes it very easy to handle the fetch states. Error boundaries Another common need is to define error-handling boundaries for your UI. Solid also makes that operation painless: import { createResource, For, Suspense, ErrorBoundary } from 'solid-js'; ( Failed to load jokes! Error details: {err.message} )} > {(joke) => ( {joke.setup} {joke.punchline} )} This is just like our previous version except it is now wrapped by the ErrorBoundary component. This component lets you simply define a chunk of UI to display if something goes awry with the asynchronous call. (Put a typo in the Jokes API to test this out.) Event handling in Solid We can test the event handling by removing the punchline from the inline display and capturing a mouse click on the setup, then displaying the punchline in an alert: handleSetupClick(joke.punchline)} style={{ cursor: 'pointer', color: 'blue', textDecoration: 'underline' }} title='Click to reveal punchline' > Setup: {joke.setup} Here’s the event handler, defined inside the component function: export default function JokerSimple() { const handleSetupClick = (punchline: string) => { alert(punchline); }; //... Now when you click the setup line, the onClick property will cause the handleSetupClick to execute. Notice that this is defined as a template expression (enclosed with curly braces) and an anonymous inline function that hands the call off to our simple handleSetupClick function, which opens an alert with the punchline. The punchline is passed in from the template by accessing joke.punchline as the argument. When I click on “Where do programmers like to hang out?” I receive the answer, “The Foo Bar.” That joke is so bad it’s funny. A reactive checkbox Imagine you want to add the ability to toggle the display between, say, programming jokes and all jokes. The remote API handles this by the presence or absence of “programming” in the penultimate part of the URL path (…/jokes/programming/ten versus …/jokes/ten). We’ll add a checkbox to the top of the page to let the user toggle the display. We’ve already seen a reactive variable with the Counter component, but this example will give us a closer look. The first thing we’ll do is create a new signal: const [jokeType, setJokeType] = createSignal(''); Now we have a new signal called jokeType, with an initial value of an empty string. Next, we insert a checkbox element at the head of the main div: {setJokeType(jokeType()==''?'programming/':'')}}> The checked and onInput attributes are Solid-specific attributes. The checked attribute uses a token to check the value of the jokeType() signal against 'programming/'. In other words, the box is checked if the value of jokeType is 'programming/'. (We use a string for the checkbox instead of a boolean because createResource doesn’t trigger a reactive update for falsy values.) The attribute onInput handles the input event on the checkbox. When it fires, we change the value of jokeType so it swaps between an empty string and 'programming/'. We are going to use this changing value in the URL of the joke fetcher. Combining the signal and the resource Now we’ll combine the new signal and the resource. This is a type of derived reactivity, where the resource watches the state of the signal and updates itself accordingly. Then, the parts of the app watching the resource will be updated in turn. In addition to the promise that does the fetching, createResource will accept a source signal as a first argument. This makes it easy to daisy chain them together, which works perfectly for our use case: const [jokes] = createResource(jokeType, fetchJokes); Now whenever jokeType changes its value, the jokes() resource will also update its dependents. The joke fetcher function will also receive the result of the source signal, so it can make use of the new value: const fetchJokes = async (jokeType) => { return (await fetch(`https://official-joke-api.appspot.com/jokes/${jokeType}ten`)).json(); } Notice that the jokeType signal is a straight variable in the argument to fetchJokes (the outcome of the jokeType promise resolution). The fetch URL makes use of the value of jokeType. When the signal is changed via the checkbox, Solid will notice and automatically re-fetch the list of jokes (with the updated URL). Conclusion It can be overwhelming to try to absorb everything Solid has to offer all at once. But you can trust that Solid—true to its name—has well-thought-out features to cover most of your needs. A good way to get to know it is to gradually introduce yourself to its features, using them only as you need them. AI-assisted coding is another approach, but it tends to spew out a bunch of complex code using every available feature. I think a tool like Roo Code is better used once you know enough about Solid to work in tandem with it rather than over-relying on it.
https://www.infoworld.com/article/2271109/hands-on-with-the-solid-javascript-framework.html
Related News |
25 sources
Current Date
Aug, Thu 28 - 10:49 CEST
|