dart_node_react
dart_node_react provides type-safe React bindings for building web applications in Dart. If you know React, you'll feel right at home.
Installation
dependencies:
dart_node_react: ^0.2.0
Also install React via npm:
npm install react react-dom
Quick Start
import 'package:dart_node_react/dart_node_react.dart';
ReactElement app() {
return div(
className: 'app',
children: [
h1(children: [text('Hello, Dart!')]),
p(children: [text('Welcome to React with Dart.')]),
],
);
}
void main() {
final container = document.getElementById('root');
final root = ReactDOM.createRoot(container);
root.render(app());
}
Components
Functional Components
ReactElement greeting({required String name}) {
return div(
className: 'greeting',
children: [
text('Hello, $name!'),
],
);
}
// Usage
greeting(name: 'World');
Components with Props
ReactElement userCard({
required String name,
required String email,
String? avatarUrl,
}) {
return div(
className: 'user-card',
children: [
avatarUrl != null
? img(src: avatarUrl, alt: name)
: div(className: 'avatar-placeholder'),
h2(children: [text(name)]),
p(children: [text(email)]),
],
);
}
Hooks
useState
ReactElement counter() {
final (count, setCount) = useState(0);
return div(children: [
p(children: [text('Count: $count')]),
button(
onClick: (_) => setCount((c) => c + 1),
children: [text('Increment')],
),
button(
onClick: (_) => setCount((c) => c - 1),
children: [text('Decrement')],
),
]);
}
useEffect
ReactElement timer() {
final (seconds, setSeconds) = useState(0);
useEffect(() {
final timer = Timer.periodic(Duration(seconds: 1), (_) {
setSeconds((s) => s + 1);
});
// Cleanup function
return () => timer.cancel();
}, []); // Empty deps = run once on mount
return p(children: [text('Seconds: $seconds')]);
}
useRef
ReactElement focusInput() {
final inputRef = useRef<HTMLInputElement>(null);
void handleClick() {
inputRef.current?.focus();
}
return div(children: [
input(ref: inputRef, type: 'text'),
button(
onClick: (_) => handleClick(),
children: [text('Focus Input')],
),
]);
}
useMemo
ReactElement expensiveList({required List<int> numbers}) {
// Only recalculate when numbers changes
final sorted = useMemo(
() => numbers.toList()..sort(),
[numbers],
);
return ul(
children: sorted.map((n) => li(children: [text('$n')])).toList(),
);
}
useCallback
ReactElement searchBox({required void Function(String) onSearch}) {
final (query, setQuery) = useState('');
// Memoize the callback
final handleSubmit = useCallback(
() => onSearch(query),
[query, onSearch],
);
return form(
onSubmit: (_) => handleSubmit(),
children: [
input(
value: query,
onChange: (e) => setQuery(e.target.value),
),
button(type: 'submit', children: [text('Search')]),
],
);
}
Elements
HTML Elements
// Divs and spans
div(className: 'container', children: [...])
span(className: 'highlight', children: [...])
// Headings
h1(children: [text('Title')])
h2(children: [text('Subtitle')])
// Paragraphs and text
p(children: [text('Some text')])
text('Raw text content')
// Links
a(href: 'https://example.com', children: [text('Click me')])
// Images
img(src: '/image.png', alt: 'Description')
// Forms
form(onSubmit: handleSubmit, children: [...])
input(type: 'text', value: value, onChange: handleChange)
button(type: 'submit', children: [text('Submit')])
Lists
ReactElement todoList({required List<Todo> todos}) {
return ul(
className: 'todo-list',
children: todos.map((todo) =>
li(
key: todo.id,
children: [
input(
type: 'checkbox',
checked: todo.completed,
),
text(todo.title),
],
)
).toList(),
);
}
Conditional Rendering
ReactElement userStatus({required User? user}) {
return div(children: [
user != null
? span(children: [text('Welcome, ${user.name}!')])
: span(children: [text('Please log in')]),
]);
}
Event Handling
ReactElement interactiveButton() {
void handleClick(MouseEvent e) {
print('Button clicked at (${e.clientX}, ${e.clientY})');
}
void handleMouseEnter(MouseEvent e) {
print('Mouse entered');
}
return button(
onClick: handleClick,
onMouseEnter: handleMouseEnter,
children: [text('Hover and Click Me')],
);
}
Form Events
ReactElement loginForm() {
final (email, setEmail) = useState('');
final (password, setPassword) = useState('');
void handleSubmit(Event e) {
e.preventDefault();
print('Login: $email / $password');
}
return form(
onSubmit: handleSubmit,
children: [
input(
type: 'email',
value: email,
onChange: (e) => setEmail(e.target.value),
placeholder: 'Email',
),
input(
type: 'password',
value: password,
onChange: (e) => setPassword(e.target.value),
placeholder: 'Password',
),
button(type: 'submit', children: [text('Log In')]),
],
);
}
Styling
Inline Styles
div(
style: {
'backgroundColor': '#f0f0f0',
'padding': '1rem',
'borderRadius': '8px',
},
children: [...],
)
CSS Classes
div(
className: 'card card-primary',
children: [...],
)
Complete Example
import 'package:dart_node_react/dart_node_react.dart';
ReactElement todoApp() {
final (todos, setTodos) = useState<List<Todo>>([]);
final (input, setInput) = useState('');
void addTodo() {
if (input.trim().isEmpty) return;
setTodos((prev) => [
...prev,
Todo(id: DateTime.now().toString(), title: input, completed: false),
]);
setInput('');
}
void toggleTodo(String id) {
setTodos((prev) => prev.map((todo) =>
todo.id == id
? Todo(id: todo.id, title: todo.title, completed: !todo.completed)
: todo
).toList());
}
return div(
className: 'todo-app',
children: [
h1(children: [text('Todo List')]),
form(
onSubmit: (e) {
e.preventDefault();
addTodo();
},
children: [
input(
value: input,
onChange: (e) => setInput(e.target.value),
placeholder: 'What needs to be done?',
),
button(type: 'submit', children: [text('Add')]),
],
),
ul(
children: todos.map((todo) =>
li(
key: todo.id,
className: todo.completed ? 'completed' : '',
onClick: (_) => toggleTodo(todo.id),
children: [text(todo.title)],
)
).toList(),
),
p(children: [
text('${todos.where((t) => !t.completed).length} items left'),
]),
],
);
}
class Todo {
final String id;
final String title;
final bool completed;
Todo({required this.id, required this.title, required this.completed});
}
void main() {
final root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(todoApp());
}
API Reference
See the full API documentation for all available functions and types.