Skip to content

Commit 55ef35a

Browse files
Add React/Dart tutorial blog post and fix CSS (#50)
## TLDR; Blog post tutorial for building React apps with Dart + CSS fix for inline code ## What Does This Do? - Adds a new blog post "Build a React Website With Dart" - step-by-step tutorial teaching how to build React web apps using dart_node_react - Fixes inline code CSS color from ugly red/orange to teal that matches the theme - Fixes incorrect package versions in the tutorial (^0.1.0 → ^0.11.0-beta) ## Brief Details? The blog post covers: - Setting up a Dart project with dart_node_react - Creating React components in Dart - State management with useState - Side effects with useEffect - Building forms - Compiling and running CSS change: `code { color: var(--color-secondary) }` → `code { color: var(--color-primary-light) }` ## How Do The Tests Prove The Change Works? - Followed the tutorial step-by-step and verified the app compiles and renders correctly - Visually verified the CSS fix in dark mode - inline code now displays in teal instead of red/orange 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 41bd751 commit 55ef35a

3 files changed

Lines changed: 353 additions & 1 deletion

File tree

cspell-dictionary.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ nullables
111111
async
112112
awaitable
113113
awaited
114+
unawaited
114115
pubsub
115116
socketio
116117
retryable

website/src/assets/css/styles.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ code {
228228
padding: 0.2em 0.4em;
229229
background: var(--code-bg);
230230
border-radius: var(--radius-sm);
231-
color: var(--color-secondary);
231+
color: var(--color-primary-light);
232232
word-break: break-word;
233233
}
234234

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
---
2+
layout: layouts/blog.njk
3+
title: "Build a React Website With Dart"
4+
description: "Learn how to build type-safe React web applications using Dart and the dart_node_react package. Step-by-step tutorial with working code examples."
5+
date: 2026-01-28
6+
author: "dart_node team"
7+
category: tutorials
8+
tags:
9+
- react
10+
- dart
11+
- frontend
12+
- tutorial
13+
- web-development
14+
---
15+
16+
What if you could build React apps without the existential dread of `undefined is not a function`? What if your types actually meant something at runtime? What if you never had to debug another `Cannot read property 'map' of null` error at 2 AM?
17+
18+
Good news: you can. With **dart_node_react**, you write React applications entirely in Dart. Same React patterns you know. Real type safety you've been dreaming about.
19+
20+
## Why Dart? (Besides the Obvious Joy of Not Using JavaScript)
21+
22+
Let's be honest. TypeScript was a massive improvement over JavaScript. But its types are like a bouncer who checks IDs at the door and then goes home. Once you're past the compiler, anything goes.
23+
24+
Dart takes a different approach. Types exist at runtime. Null safety is sound. When your code compiles, you know your `String` is actually a `String` and not secretly `undefined` wearing a fake mustache.
25+
26+
Already know Flutter? You already know Dart. Now you can use those same skills to build React web apps. One language. Full stack. No context switching between "Dart brain" and "TypeScript brain."
27+
28+
## Setting Up Your Project
29+
30+
Getting started takes about 30 seconds. Create a new Dart project:
31+
32+
```bash
33+
mkdir my_react_app && cd my_react_app
34+
dart create -t package .
35+
```
36+
37+
Add the dependencies to your `pubspec.yaml`:
38+
39+
```yaml
40+
name: my_react_app
41+
environment:
42+
sdk: ^3.0.0
43+
44+
dependencies:
45+
dart_node_core: ^0.11.0-beta
46+
dart_node_react: ^0.11.0-beta
47+
```
48+
49+
Run `dart pub get`. Done. No webpack config. No babel. No 47 dev dependencies fighting each other.
50+
51+
## Your First Component
52+
53+
Create `web/app.dart`. This is where the magic happens:
54+
55+
```dart
56+
import 'dart:js_interop';
57+
import 'package:dart_node_react/dart_node_react.dart';
58+
59+
void main() {
60+
final root = Document.getElementById('root');
61+
(root != null)
62+
? ReactDOM.createRoot(root).render(App())
63+
: throw StateError('Root element not found');
64+
}
65+
66+
ReactElement App() => createElement(
67+
((JSAny props) {
68+
return div(
69+
className: 'app',
70+
children: [
71+
h1('Hello from Dart!'),
72+
pEl('Look ma, no JavaScript!'),
73+
],
74+
);
75+
}).toJS,
76+
);
77+
```
78+
79+
The `createElement` function wraps your component logic. Inside, you return React elements using helper functions like `div`, `h1`, and `pEl`. It feels like React because it *is* React, just with better types.
80+
81+
## State Management: useState Without the Guesswork
82+
83+
Here's where Dart really shines. The `useState` hook returns a `StateHook<T>` with actual, honest-to-goodness type safety:
84+
85+
```dart
86+
ReactElement Counter() => createElement(
87+
((JSAny props) {
88+
final count = useState(0);
89+
90+
return div(
91+
className: 'counter',
92+
children: [
93+
h2('Count: ${count.value}'),
94+
button(
95+
text: 'Increment',
96+
onClick: (_) => count.setWithUpdater((c) => c + 1),
97+
),
98+
button(
99+
text: 'Reset',
100+
onClick: (_) => count.set(0),
101+
),
102+
],
103+
);
104+
}).toJS,
105+
);
106+
```
107+
108+
Three ways to update state:
109+
110+
- `count.value` - read the current value
111+
- `count.set(5)` - set a new value directly
112+
- `count.setWithUpdater((old) => old + 1)` - update based on previous value
113+
114+
No more `useState<number | undefined>(undefined)` gymnastics. Just `useState(0)`. The compiler knows it's an `int`.
115+
116+
## Building Forms (The Part Everyone Dreads)
117+
118+
Forms don't have to be painful. Here's a login form that actually works:
119+
120+
```dart
121+
ReactElement LoginForm() => createElement(
122+
((JSAny props) {
123+
final emailState = useState('');
124+
final passwordState = useState('');
125+
final errorState = useState<String?>(null);
126+
127+
void handleSubmit() {
128+
if (emailState.value.isEmpty || passwordState.value.isEmpty) {
129+
errorState.set('Please fill in all fields');
130+
return;
131+
}
132+
print('Logging in: ${emailState.value}');
133+
}
134+
135+
return div(
136+
className: 'login-form',
137+
children: [
138+
h2('Sign In'),
139+
if (errorState.value != null)
140+
div(className: 'error', child: span(errorState.value!)),
141+
input(
142+
type: 'email',
143+
placeholder: 'Email',
144+
value: emailState.value,
145+
className: 'input',
146+
onChange: (e) => emailState.set(getInputValue(e).toDart),
147+
),
148+
input(
149+
type: 'password',
150+
placeholder: 'Password',
151+
value: passwordState.value,
152+
className: 'input',
153+
onChange: (e) => passwordState.set(getInputValue(e).toDart),
154+
),
155+
button(
156+
text: 'Sign In',
157+
className: 'btn btn-primary',
158+
onClick: handleSubmit,
159+
),
160+
],
161+
);
162+
}).toJS,
163+
);
164+
```
165+
166+
The `getInputValue` helper extracts input values from events. Call `.toDart` to convert JavaScript strings to Dart strings. Clean and predictable.
167+
168+
## Side Effects with useEffect
169+
170+
Need to fetch data when a component mounts? `useEffect` works exactly like you'd expect:
171+
172+
```dart
173+
ReactElement UserList() => createElement(
174+
((JSAny props) {
175+
final usersState = useState<List<String>>([]);
176+
final loadingState = useState(true);
177+
178+
useEffect(() {
179+
Future<void> loadUsers() async {
180+
await Future.delayed(Duration(seconds: 1));
181+
usersState.set(['Alice', 'Bob', 'Charlie']);
182+
loadingState.set(false);
183+
}
184+
185+
unawaited(loadUsers());
186+
return null;
187+
}, []);
188+
189+
return div(
190+
className: 'user-list',
191+
children: [
192+
h2('Users'),
193+
if (loadingState.value)
194+
span('Loading...')
195+
else
196+
ul(
197+
children: usersState.value
198+
.map((user) => li(child: span(user)))
199+
.toList(),
200+
),
201+
],
202+
);
203+
}).toJS,
204+
);
205+
```
206+
207+
Pass an empty list `[]` to run the effect only on mount. Return a cleanup function or `null` if you don't need cleanup. No surprises here.
208+
209+
## All Your Favorite HTML Elements
210+
211+
dart_node_react provides functions for every HTML element you need:
212+
213+
```dart
214+
ReactElement PageLayout() => createElement(
215+
((JSAny props) {
216+
return div(
217+
className: 'layout',
218+
children: [
219+
header(
220+
className: 'header',
221+
child: h1('My Dart React App'),
222+
),
223+
mainEl(
224+
className: 'main-content',
225+
children: [
226+
section(
227+
className: 'hero',
228+
children: [
229+
h2('Welcome'),
230+
pEl('Build type-safe React apps with Dart.'),
231+
],
232+
),
233+
],
234+
),
235+
footer(
236+
className: 'footer',
237+
child: pEl('Built with dart_node_react'),
238+
),
239+
],
240+
);
241+
}).toJS,
242+
);
243+
```
244+
245+
You get `div`, `span`, `h1`-`h6`, `pEl`, `ul`, `li`, `button`, `input`, `form`, `header`, `footer`, `mainEl`, `section`, `nav`, `article`, and more. Everything you need to build real UIs.
246+
247+
## Compiling and Running
248+
249+
Create `web/index.html`:
250+
251+
```html
252+
<!DOCTYPE html>
253+
<html>
254+
<head>
255+
<meta charset="UTF-8">
256+
<title>My Dart React App</title>
257+
</head>
258+
<body>
259+
<div id="root"></div>
260+
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
261+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
262+
<script src="app.dart.js"></script>
263+
</body>
264+
</html>
265+
```
266+
267+
Compile your Dart to JavaScript:
268+
269+
```bash
270+
dart compile js web/app.dart -o web/app.dart.js
271+
```
272+
273+
Serve the `web` directory and open it in your browser. That's it. Your React app is running, and you didn't write a single line of JavaScript.
274+
275+
## Putting It Together: A Task Manager
276+
277+
Here's a complete example combining everything you've learned:
278+
279+
```dart
280+
ReactElement TaskManager() => createElement(
281+
((JSAny props) {
282+
final tasksState = useState<List<String>>([]);
283+
final newTaskState = useState('');
284+
285+
void addTask() {
286+
switch (newTaskState.value.trim().isEmpty) {
287+
case true:
288+
return;
289+
case false:
290+
tasksState.setWithUpdater(
291+
(tasks) => [...tasks, newTaskState.value],
292+
);
293+
newTaskState.set('');
294+
}
295+
}
296+
297+
void removeTask(int index) {
298+
tasksState.setWithUpdater((tasks) {
299+
final updated = [...tasks];
300+
updated.removeAt(index);
301+
return updated;
302+
});
303+
}
304+
305+
return div(
306+
className: 'task-manager',
307+
children: [
308+
h2('My Tasks'),
309+
div(
310+
className: 'add-task',
311+
children: [
312+
input(
313+
type: 'text',
314+
placeholder: 'New task...',
315+
value: newTaskState.value,
316+
onChange: (e) => newTaskState.set(getInputValue(e).toDart),
317+
),
318+
button(text: 'Add', onClick: addTask),
319+
],
320+
),
321+
ul(
322+
className: 'task-list',
323+
children: tasksState.value.indexed
324+
.map(
325+
(item) => li(
326+
children: [
327+
span(item.$2),
328+
button(
329+
text: 'Delete',
330+
onClick: (_) => removeTask(item.$1),
331+
),
332+
],
333+
),
334+
)
335+
.toList(),
336+
),
337+
],
338+
);
339+
}).toJS,
340+
);
341+
```
342+
343+
State management, event handling, list rendering. All type-safe. All Dart.
344+
345+
## What's Next?
346+
347+
You've got the basics. Now go build something. Explore [more hooks](/api/dart_node_react/) like `useMemo` and `useCallback`. Check out the [full-stack example](https://github.com/AstroCodez/dart_node/tree/main/examples/frontend) with authentication, API integration, and WebSocket support.
348+
349+
No more fighting with type coercion. No more `any` escape hatches. Just clean, type-safe React apps in a language that respects your time.
350+
351+
Welcome to the future. It compiles to JavaScript, but at least you don't have to write it.

0 commit comments

Comments
 (0)