Skip to content

What's the Real Difference Between SolidJS 2.0 and React's Reactivity Model?

I spent three hours debugging why my React component kept re-rendering. I had useMemo, useCallback, and React.memo sprinkled everywhere. Still, one keystroke in an input field triggered a cascade of unnecessary renders. Then I tried SolidJS and realized: the problem wasn’t my code. It was React’s fundamental reactivity model.

The React Problem: Component Re-renders

In React, when state changes, the entire component function re-executes. This is by design:

ReactCounter.jsx
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
// This console.log runs on EVERY state change
// Even when only 'name' changes
console.log('Component re-rendered');
return (
<div>
<h1>Count: {count}</h1>
<input value={name} onChange={e => setName(e.target.value)} />
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}

Type in the input, and React:

  1. Calls setName with the new value
  2. Re-runs the entire Counter function
  3. Creates a new virtual DOM tree
  4. Diffs old vs new virtual DOM
  5. Patches the actual DOM

Every. Single. Keystroke.

For small apps, this is fine. But I worked on a dashboard with 50+ components sharing state. A single dropdown selection triggered re-renders across the entire tree. My performance profiling looked like a Christmas tree of wasted cycles.

The Mental Burden of Optimization

React forces you to think about optimization constantly:

OptimizedReact.jsx
const ExpensiveList = memo(({ items }) => {
// Only re-renders if items reference changes
return items.map(item => <Item key={item.id} {...item} />);
});
function Dashboard() {
const [filter, setFilter] = useState('');
const [data, setData] = useState([]);
// Must memoize to prevent ExpensiveList re-renders
const filteredItems = useMemo(() => {
return data.filter(item => item.name.includes(filter));
}, [data, filter]);
// Must useCallback to maintain reference stability
const handleItemClick = useCallback((id) => {
console.log('Clicked:', id);
}, []);
return (
<ExpensiveList
items={filteredItems}
onItemClick={handleItemClick}
/>
);
}

I found myself spending more time optimizing re-renders than writing actual features. This is the React tax: you pay it in mental overhead and debugging time.

SolidJS’s Solution: Fine-Grained Reactivity

SolidJS takes a fundamentally different approach. Components run once. Signals track dependencies automatically. Only the specific DOM nodes that changed update.

SolidCounter.jsx
function Counter() {
const [count, setCount] = createSignal(0);
const [name, setName] = createSignal('SolidJS');
// This runs ONCE on mount, never again
console.log('Component rendered once');
return (
<div>
{/* Only this h1 updates when count changes */}
<h1>Count: {count()}</h1>
{/* Only this input updates when name changes */}
<input value={name()} onInput={e => setName(e.target.value)} />
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}

Type in the input, and SolidJS:

  1. Calls setName with the new value
  2. Updates ONLY the input’s value attribute
  3. Done. No virtual DOM. No diffing. No re-renders.

The count() expression in the h1 doesn’t even run. SolidJS tracks that the h1 depends on count, but since count didn’t change, it skips that DOM update entirely.

How Signals Actually Work

Signals are the core primitive. They’re like useState but smarter:

SignalsExplained.jsx
function UserProfile() {
const [user, setUser] = createSignal({ name: 'Alice', age: 30 });
const [theme, setTheme] = createSignal('dark');
// This creates a derived signal - automatically tracks user
const displayName = createMemo(() => {
return user().name.toUpperCase();
});
createEffect(() => {
// This effect runs when theme() or user() changes
// SolidJS tracks dependencies automatically
document.body.className = theme();
console.log('User:', user().name);
});
return (
<div>
{/* Only re-renders when displayName changes */}
<h1>{displayName()}</h1>
{/* Only re-renders when theme changes */}
<button onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
Toggle Theme
</button>
</div>
);
}

No dependency arrays. No useCallback. No memo. SolidJS tracks what each DOM node depends on and updates only what’s necessary.

The Architecture Difference

Here’s the fundamental difference in how updates flow:

React Update Flow:
State Change -> Component Re-render -> Virtual DOM Diff -> DOM Patch
SolidJS Update Flow:
Signal Change -> Direct DOM Update

React’s approach adds layers. Virtual DOM diffing is fast, but it’s not free. In complex apps, those layers compound.

Where React’s Model Breaks Down

I hit the React wall when building a real-time data table with 500 rows. Each row had editable cells. The state management was:

ProblemTable.jsx
function DataTable() {
const [rows, setRows] = useState(/* 500 rows */);
const updateCell = (rowId, column, value) => {
setRows(prev => prev.map(row =>
row.id === rowId
? { ...row, [column]: value }
: row
));
};
return rows.map(row => (
<Row
key={row.id}
row={row}
onUpdate={updateCell}
/>
));
}
const Row = memo(({ row, onUpdate }) => {
// Even with memo, this re-renders when ANY row changes
// because the onUpdate callback reference changes
return (
<tr>
{Object.entries(row).map(([key, value]) => (
<Cell
key={key}
value={value}
onChange={(v) => onUpdate(row.id, key, v)}
/>
))}
</tr>
);
});

The onUpdate callback reference changed on every parent render, breaking memo. I needed useCallback, but useCallback needed setRows in its dependency array, which… you get the idea.

SolidJS Handles This Naturally

The same problem in SolidJS:

SolidTable.jsx
function DataTable() {
const [rows, setRows] = createStore(/* 500 rows */);
return (
<table>
<For each={rows}>
{(row) => (
<Row
row={row}
onUpdate={(column, value) =>
setRows(row.id, column, value)
}
/>
)}
</For>
</table>
);
}
function Row({ row, onUpdate }) {
// This runs once per row, never re-runs
return (
<tr>
<For each={Object.keys(row)}>
{(key) => (
<Cell
value={row[key]}
onChange={(v) => onUpdate(key, v)}
/>
)}
</For>
</tr>
);
}

SolidJS’s For component is keyed by default. Each row renders once. Cell updates don’t cascade. The onUpdate callback is stable because it references setRows directly, not wrapped in a state setter.

Effect Handling: Manual vs Automatic

This is where the mental models diverge most.

React: Manual Dependency Tracking

ReactEffect.jsx
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
fetchResults(query).then(setResults);
}, [query]); // Don't forget this array!
useEffect(() => {
document.title = `Searching: ${query}`;
}, [query]); // Same dependency, different effect
return <ResultsList results={results} />;
}

Miss a dependency? Stale data. Include too many? Unnecessary runs. The React team even built an ESLint rule to catch this, because it’s so error-prone.

SolidJS: Automatic Dependency Tracking

SolidEffect.jsx
function SearchResults() {
const [query, setQuery] = createSignal('');
const [results, setResults] = createSignal([]);
createEffect(() => {
fetchResults(query()).then(setResults);
// SolidJS automatically tracks query() as a dependency
});
createEffect(() => {
document.title = `Searching: ${query()}`;
// Same automatic tracking
});
return <ResultsList results={results()} />;
}

SolidJS tracks which signals each effect reads. When query() changes, both effects run. No manual arrays. No stale closures.

The Trade-offs Nobody Talks About

SolidJS sounds perfect. It’s not. I learned this the hard way.

Ecosystem Gap

React has 10+ years of solutions. SolidJS doesn’t. When I needed:

- Form library: React has React Hook Form, Formik, Final Form. SolidJS has?
- UI components: React has Radix, MUI, Chakra. SolidJS has Solid-UI, but smaller ecosystem.
- Auth: React has NextAuth, Auth0 SDKs. SolidJS requires custom solutions.
- Testing: React Testing Library is mature. SolidJS Testing Library exists but less documented.

For a personal project? I could work around this. For a team project at work? The ecosystem gap became a real blocker.

Mental Model Shift

React’s model is: “What should the UI look like given this state?” SolidJS’s model is: “Where does this piece of data flow to the UI?”

This seems subtle, but it changes how you structure code:

ReactThinking.jsx
// React: Declarative, state-driven
function TodoList() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
// Derived state computed on each render
const filteredTodos = todos.filter(t =>
filter === 'all' || t.status === filter
);
return <List items={filteredTodos} />;
}
SolidThinking.jsx
// SolidJS: Reactive, dependency-driven
function TodoList() {
const [todos, setTodos] = createSignal([]);
const [filter, setFilter] = createSignal('all');
// Memoized signal that automatically updates
const filteredTodos = createMemo(() =>
todos().filter(t => filter() === 'all' || t.status === filter())
);
return <List items={filteredTodos()} />;
}

In React, I think in renders. In SolidJS, I think in data flow. Unlearning React patterns took me weeks.

The Debugging Difference

React debugging: “Why is this component re-rendering?”

  • Check props with React DevTools
  • Verify memo dependencies
  • Use why-did-you-render library
  • Add console.logs in render

SolidJS debugging: “Why is this effect running?”

  • Check signal dependencies
  • Verify reactive scope
  • Use SolidJS DevTools (less mature)
  • Usually simpler: fewer moving parts

I spent less time debugging reactivity in SolidJS, but more time learning the mental model.

When SolidJS Wins

After six months with SolidJS, I found clear winners:

1. Complex dashboards with frequent updates

React’s re-render tax compounds. Every dropdown, every filter, every cell edit triggers virtual DOM work. SolidJS handles thousands of reactive updates per second because it skips the intermediate layers.

2. Real-time collaborative apps

When multiple users edit simultaneously, React’s re-renders become a bottleneck. SolidJS’s surgical updates handle concurrent changes gracefully.

3. Performance-critical UIs

Animation-heavy interfaces, data visualizations, interactive editors—these need 60fps. React can do it with careful optimization. SolidJS does it by default.

When React Still Makes Sense

I still use React for:

1. Team projects

If I’m the only one who knows SolidJS, I’m a single point of failure. React’s hiring pool is enormous.

2. Ecosystem-dependent projects

Need a specific library? React probably has three options. SolidJS might have none.

3. Quick prototypes

For a throwaway prototype, React’s familiarity wins. I can scaffold faster in React because I’ve done it hundreds of times.

The Verdict

SolidJS’s fine-grained reactivity eliminates the virtual DOM overhead and mental model complexity of React’s re-render approach. But the ecosystem gap and mental model shift are real costs.

For new projects where performance matters and I control the stack? SolidJS 2.0 is my choice.

For team projects at work? React remains pragmatic—its maturity and talent pool outweigh the reactivity benefits.

The framework war isn’t about which is “better.” It’s about matching the tool to the problem. Now that I understand both models, I choose based on constraints, not hype.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments