install
source · Clone the upstream repo
git clone https://github.com/ComeOnOliver/skillshub
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/ComeOnOliver/skillshub "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/TerminalSkills/skills/solid" ~/.claude/skills/comeonoliver-skillshub-solid && rm -rf "$T"
manifest:
skills/TerminalSkills/skills/solid/SKILL.mdsource content
SolidJS
SolidJS uses fine-grained reactivity with signals — no virtual DOM diffing. Components run once, and only the specific DOM nodes that depend on changed signals update. This makes it extremely fast.
Installation
# Create SolidJS project npx degit solidjs/templates/ts my-app cd my-app npm install npm run dev
Project Structure
# SolidJS project layout src/ ├── index.tsx # Entry point ├── App.tsx # Root component ├── routes/ # Page components │ ├── Home.tsx │ └── Articles.tsx ├── components/ # Shared components │ └── ArticleCard.tsx ├── stores/ # Stores for state │ └── articles.ts ├── lib/ # Utilities │ └── api.ts └── index.css
Signals (Primitives)
// src/components/Counter.tsx — basic signals demo import { createSignal, createEffect, createMemo } from 'solid-js'; export default function Counter() { const [count, setCount] = createSignal(0); const doubled = createMemo(() => count() * 2); createEffect(() => { console.log(`Count is now: ${count()}`); }); return ( <div> <p>Count: {count()} (doubled: {doubled()})</p> <button onClick={() => setCount((c) => c + 1)}>+1</button> </div> ); }
Components and Props
// src/components/ArticleCard.tsx — component with typed props import { Component } from 'solid-js'; interface Article { id: number; title: string; slug: string; excerpt: string; } interface Props { article: Article; onDelete?: (id: number) => void; } const ArticleCard: Component<Props> = (props) => { return ( <article> <a href={`/articles/${props.article.slug}`}> <h2>{props.article.title}</h2> </a> <p>{props.article.excerpt}</p> <button onClick={() => props.onDelete?.(props.article.id)}>Delete</button> </article> ); }; export default ArticleCard;
Resources (Data Fetching)
// src/routes/Articles.tsx — async data fetching with createResource import { createResource, For, Show, Suspense } from 'solid-js'; import ArticleCard from '../components/ArticleCard'; async function fetchArticles(): Promise<Article[]> { const res = await fetch('/api/articles'); return res.json(); } export default function Articles() { const [articles] = createResource(fetchArticles); return ( <div> <h1>Articles</h1> <Suspense fallback={<p>Loading...</p>}> <Show when={!articles.error} fallback={<p>Error loading articles.</p>}> <For each={articles()}> {(article) => <ArticleCard article={article} />} </For> </Show> </Suspense> </div> ); }
Stores (Complex State)
// src/stores/articles.ts — store for nested reactive state import { createStore, produce } from 'solid-js/store'; interface ArticlesState { items: Article[]; loading: boolean; filter: string; } const [state, setState] = createStore<ArticlesState>({ items: [], loading: false, filter: '', }); export function useArticles() { async function fetchAll() { setState('loading', true); const res = await fetch('/api/articles'); const data = await res.json(); setState({ items: data, loading: false }); } function removeArticle(id: number) { setState('items', (items) => items.filter((a) => a.id !== id)); } function setFilter(query: string) { setState('filter', query); } return { state, fetchAll, removeArticle, setFilter }; }
Control Flow
// src/components/ArticleList.tsx — control flow components import { For, Show, Switch, Match } from 'solid-js'; export default function ArticleList(props: { articles: Article[]; status: string }) { return ( <div> <Switch> <Match when={props.status === 'loading'}> <p>Loading...</p> </Match> <Match when={props.status === 'error'}> <p>Error loading articles.</p> </Match> <Match when={props.status === 'ready'}> <Show when={props.articles.length > 0} fallback={<p>No articles.</p>}> <For each={props.articles}> {(article) => <ArticleCard article={article} />} </For> </Show> </Match> </Switch> </div> ); }
Routing (SolidStart)
// src/routes/articles/[slug].tsx — SolidStart file-based route import { useParams } from '@solidjs/router'; import { createResource } from 'solid-js'; export default function ArticlePage() { const params = useParams(); const [article] = createResource(() => params.slug, async (slug) => { const res = await fetch(`/api/articles/${slug}`); if (!res.ok) throw new Error('Not found'); return res.json(); }); return ( <Suspense fallback={<p>Loading...</p>}> <Show when={article()}> {(a) => ( <article> <h1>{a().title}</h1> <div innerHTML={a().body} /> </article> )} </Show> </Suspense> ); }
Context (Dependency Injection)
// src/lib/AuthContext.tsx — context for shared auth state import { createContext, useContext, ParentComponent } from 'solid-js'; import { createStore } from 'solid-js/store'; const AuthContext = createContext<{ user: () => User | null; login: (u: User) => void }>(); export const AuthProvider: ParentComponent = (props) => { const [state, setState] = createStore<{ user: User | null }>({ user: null }); const value = { user: () => state.user, login: (u: User) => setState('user', u), }; return <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider>; }; export function useAuth() { return useContext(AuthContext)!; }
Key Patterns
- Signals are called as functions:
reads,count()
writes — parentheses mattersetCount() - Components run once; only signal-dependent expressions re-execute
- Use
for lists (keyed by reference),<For>
for index-based iteration<Index> - Use
for conditional rendering,<Show>
/<Switch>
for multiple branches<Match> - Use
for async data — it integrates withcreateResource<Suspense> - Use stores (
) for nested objects — signals are for primitivescreateStore - Don't destructure props — it breaks reactivity. Access
directlyprops.x