Migration from React
This guide walks you through migrating React apps to Flexium step by step.
Overview
Flexium provides a React-like API but with some important differences:
- ✅ Single API: One
state()for all state management - ✅ No dependency arrays: Automatic tracking
- ✅ No Virtual DOM: Faster rendering
- ⚠️ Proxy comparison warning: Direct comparison not possible
API Mapping Table
| React | Flexium | Notes |
|---|---|---|
useState | state | Almost identical pattern |
useMemo | state(() => ...) | computed state |
useEffect | effect | Almost identical, cleanup same |
useCallback | Unnecessary | Auto-optimized |
useRef | ref | Same |
useContext | context | Same |
useReducer | state + setter | Implement directly |
React.memo | Unnecessary | Auto-optimized |
React Router | flexium/router | Similar API |
Step-by-Step Migration
Step 1: Replace Dependencies
bash
# Remove React
npm uninstall react react-dom @types/react @types/react-dom
# Install Flexium
npm install flexiumStep 2: Change Imports
tsx
// ❌ Before (React)
import { useState, useEffect, useMemo } from 'react'
// ✅ After (Flexium)
import { state, effect } from 'flexium/core'Step 3: Convert Components
Basic Component
tsx
// ❌ Before (React)
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
)
}
// ✅ After (Flexium)
import { state } from 'flexium/core'
function Counter() {
const [count, setCount] = state(0)
return (
<div>
<p>Count: {count}</p>
<button onclick={() => setCount(count + 1)}>+</button>
</div>
)
}Changes:
useState→stateonClick→onclick(lowercase)- Rest is the same
useMemo → computed state
tsx
// ❌ Before (React)
import { useState, useMemo } from 'react'
function Calculator() {
const [price, setPrice] = useState(100)
const [quantity, setQuantity] = useState(2)
const total = useMemo(() => price * quantity, [price, quantity])
return <div>Total: {total}</div>
}
// ✅ After (Flexium)
import { state } from 'flexium/core'
function Calculator() {
const [price, setPrice] = state(100)
const [quantity, setQuantity] = state(2)
const [total] = state(() => price * quantity) // Automatic dependency tracking
return <div>Total: {total}</div>
}Changes:
useMemo→state(() => ...)- No dependency array needed (automatic tracking)
useEffect → effect
tsx
// ❌ Before (React)
import { useState, useEffect } from 'react'
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1)
}, 1000)
return () => clearInterval(interval)
}, [])
return <div>Count: {count}</div>
}
// ✅ After (Flexium)
import { state, effect } from 'flexium/core'
function Timer() {
const [count, setCount] = state(0)
effect(() => {
const interval = setInterval(() => {
setCount(c => c + 1)
}, 1000)
return () => clearInterval(interval) // Cleanup same
})
return <div>Count: {count}</div>
}Changes:
useEffect→effect- No dependency array needed (automatic tracking)
- Cleanup function is the same
useEffect with dependencies
tsx
// ❌ Before (React)
import { useState, useEffect } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data))
}, [userId])
return user ? <div>{user.name}</div> : <div>Loading...</div>
}
// ✅ After (Flexium)
import { state, effect } from 'flexium/core'
function UserProfile({ userId }) {
const [user, setUser] = state(null)
effect(() => {
const id = userId // Reading automatically tracks
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => setUser(data))
})
return user ? <div>{user.name}</div> : <div>Loading...</div>
}Changes:
- Instead of dependency array, reading inside effect automatically tracks
useCallback → Unnecessary
tsx
// ❌ Before (React)
import { useState, useCallback } from 'react'
function Parent() {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => {
setCount(c => c + 1)
}, [])
return <Child onClick={handleClick} />
}
// ✅ After (Flexium)
import { state } from 'flexium/core'
function Parent() {
const [count, setCount] = state(0)
// useCallback unnecessary - auto-optimized
const handleClick = () => {
setCount(c => c + 1)
}
return <Child onclick={handleClick} />
}Changes:
useCallbackunnecessary (auto-optimized)
useRef → ref
tsx
// ❌ Before (React)
import { useRef } from 'react'
function Input() {
const inputRef = useRef<HTMLInputElement>(null)
const focus = () => {
inputRef.current?.focus()
}
return <input ref={inputRef} />
}
// ✅ After (Flexium)
import { ref } from 'flexium/core'
function Input() {
const inputRef = ref<HTMLInputElement>()
const focus = () => {
inputRef.current?.focus()
}
return <input ref={inputRef} />
}Changes:
useRef→ref- Usage is the same
useContext → context
tsx
// ❌ Before (React)
import { createContext, useContext } from 'react'
const ThemeContext = createContext('light')
function App() {
return (
<ThemeContext.Provider value="dark">
<Child />
</ThemeContext.Provider>
)
}
function Child() {
const theme = useContext(ThemeContext)
return <div>Theme: {theme}</div>
}
// ✅ After (Flexium) - Use state() with key
import { state } from 'flexium/core'
function App() {
// Set theme globally - no Provider needed
const [theme, setTheme] = state('dark', { key: 'app:theme' })
return <Child />
}
function Child() {
// Access theme from anywhere
const [theme] = state('light', { key: 'app:theme' })
return <div>Theme: {theme}</div>
}Changes:
createContext→state()withkeyoptionuseContext→state()with samekey- No Provider needed - state is global
useReducer → state + setter
tsx
// ❌ Before (React)
import { useReducer } from 'react'
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
return state
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
)
}
// ✅ After (Flexium)
import { state } from 'flexium/core'
function Counter() {
const [state, setState] = state({ count: 0 })
const increment = () => setState(s => ({ ...s, count: s.count + 1 }))
const decrement = () => setState(s => ({ ...s, count: s.count - 1 }))
return (
<div>
<p>Count: {state.count}</p>
<button onclick={increment}>+</button>
<button onclick={decrement}>-</button>
</div>
)
}Changes:
useReducer→state+ setter function- Convert reducer logic to regular functions
Step 4: Routing Conversion
React Router → Flexium Router
tsx
// ❌ Before (React Router)
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</BrowserRouter>
)
}
// ✅ After (Flexium Router)
import { Router, Route, Link } from 'flexium/router'
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={UserDetail} />
</Router>
)
}Changes:
BrowserRouter→RouterRoutes→ Remove (unnecessary)Route'selement→componentLink'stoprop is the same
useNavigate → useRouter
tsx
// ❌ Before (React Router)
import { useNavigate, useParams } from 'react-router-dom'
function UserDetail() {
const { id } = useParams()
const navigate = useNavigate()
const goBack = () => navigate(-1)
const goHome = () => navigate('/')
return <div>User {id}</div>
}
// ✅ After (Flexium Router)
import { router } from 'flexium/router'
function UserDetail() {
const r = router()
const id = r.params().id
const goBack = () => window.history.back()
const goHome = () => r.navigate('/')
return <div>User {id}</div>
}Changes:
useNavigate→router()useParams→router().params()navigate(-1)→window.history.back()orrouter().navigate('/previous-path')navigate('/')→router.push('/')
Step 5: Event Handler Conversion
tsx
// ❌ Before (React)
<button onClick={handleClick}>Click</button>
<input onChange={handleChange} />
<form onSubmit={handleSubmit} />
// ✅ After (Flexium)
<button onclick={handleClick}>Click</button>
<input onchange={handleChange} />
<form onsubmit={handleSubmit} />Changes:
- All event handlers are lowercase (
onclick,onchange,onsubmit)
Step 6: Conditional Rendering
tsx
// ❌ Before (React)
{isLoading && <Spinner />}
{error && <Error message={error} />}
{user ? <UserProfile user={user} /> : <Login />}
// ✅ After (Flexium)
{isLoading && <Spinner />}
{error && <Error message={error} />}
{user ? <UserProfile user={user} /> : <Login />}Changes:
- Conditional rendering is the same
Step 7: List Rendering
tsx
// ❌ Before (React)
{items.map(item => (
<Item key={item.id} data={item} />
))}
// ✅ After (Flexium) - Option 1: Regular map
{items.map(item => (
<Item key={item.id} data={item} />
))}
// ✅ After (Flexium) - Option 2: Optimized items.map()
{items.map((item) => <Item data={item} />)}Changes:
- Regular
mapworks butForcomponent is more optimized
Key Differences
1. Proxy Comparison Warning
tsx
// ❌ Wrong approach
const [count, setCount] = state(0)
if (count === 5) { ... } // Always false
// ✅ Correct approach
import { equals } from 'flexium/core'
if (equals(count, 5)) { ... }
// Or
if (+count === 5) { ... } // Explicit conversion2. No Dependency Arrays Needed
tsx
// React: Dependency array required
useEffect(() => {
console.log(count)
}, [count])
// Flexium: Automatic tracking
effect(() => {
console.log(count) // Reading count automatically tracks
})3. No Virtual DOM
Flexium doesn't use Virtual DOM, so:
- Faster rendering
- Lower memory usage
- But React DevTools cannot be used
4. Component Memoization Unnecessary
tsx
// React: React.memo needed
const MemoizedComponent = React.memo(Component)
// Flexium: Unnecessary (auto-optimized)
function Component() { ... }Complete Example: Todo App Conversion
Before (React)
tsx
import { useState, useEffect } from 'react'
interface Todo {
id: number
text: string
completed: boolean
}
function TodoApp() {
const [todos, setTodos] = useState<Todo[]>([])
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all')
useEffect(() => {
const saved = localStorage.getItem('todos')
if (saved) {
setTodos(JSON.parse(saved))
}
}, [])
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos))
}, [todos])
const addTodo = (text: string) => {
setTodos([...todos, { id: Date.now(), text, completed: false }])
}
const toggleTodo = (id: number) => {
setTodos(todos.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
))
}
const filteredTodos = todos.filter(t => {
if (filter === 'active') return !t.completed
if (filter === 'completed') return t.completed
return true
})
return (
<div>
<input
onKeyDown={(e) => {
if (e.key === 'Enter') {
addTodo(e.currentTarget.value)
e.currentTarget.value = ''
}
}}
/>
<div>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('active')}>Active</button>
<button onClick={() => setFilter('completed')}>Completed</button>
</div>
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
</li>
))}
</ul>
</div>
)
}After (Flexium)
tsx
import { state, effect, sync } from 'flexium/core'
interface Todo {
id: number
text: string
completed: boolean
}
function TodoApp() {
const [todos, setTodos] = state<Todo[]>([])
const [filter, setFilter] = state<'all' | 'active' | 'completed'>('all')
// Load from local storage
effect(() => {
const saved = localStorage.getItem('todos')
if (saved) {
setTodos(JSON.parse(saved))
}
})
// Save to local storage
effect(() => {
localStorage.setItem('todos', JSON.stringify(todos))
})
const addTodo = (text: string) => {
setTodos([...todos, { id: Date.now(), text, completed: false }])
}
const toggleTodo = (id: number) => {
setTodos(todos.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
))
}
// Filtering with computed state
const [filteredTodos] = state(() => {
if (filter === 'active') return todos.filter(t => !t.completed)
if (filter === 'completed') return todos.filter(t => t.completed)
return todos
})
return (
<div>
<input
onkeydown={(e) => {
if (e.key === 'Enter') {
addTodo(e.currentTarget.value)
e.currentTarget.value = ''
}
}}
/>
<div>
<button onclick={() => setFilter('all')}>All</button>
<button onclick={() => setFilter('active')}>Active</button>
<button onclick={() => setFilter('completed')}>Completed</button>
</div>
<ul>
{filteredTodos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onchange={() => toggleTodo(todo.id)}
/>
{todo.text}
</li>
))}
</ul>
</div>
)
}Key Changes:
useState→stateuseEffect→effectuseMemo→state(() => ...)onClick→onclickonChange→onchangeonKeyDown→onkeydown
Common Problem Solving
Problem 1: State comparison doesn't work
tsx
// ❌ Problem
if (count === 5) { ... }
// ✅ Solution
import { equals } from 'flexium/core'
if (equals(count, 5)) { ... }Problem 2: effect runs too frequently
tsx
// ❌ Problem: Multiple state updates cause duplicate execution
setA(1)
setB(2)
setC(3) // Effect runs 3 times
// ✅ Solution: Use sync()
import { sync } from 'flexium/advanced'
sync(() => {
setA(1)
setB(2)
setC(3) // Effect runs only once
})Problem 3: Type errors
tsx
// ✅ Explicit type specification
const [user, setUser] = state<User | null>(null)
const [count, setCount] = state<number>(0)Performance Considerations
1. Use Sync Updates
tsx
import { sync } from 'flexium/advanced'
// Multiple state updates at once
sync(() => {
setA(1)
setB(2)
setC(3)
})2. Use items.map()
tsx
// Optimized list rendering
{items.map((item) => <Item data={item} />)}3. Use Global State Appropriately
tsx
// State shared across multiple components should be global
const [user] = state(null, { key: 'auth:user' })