Todo List
A todo list example demonstrating Stage Flow with dynamic data management using individual stage components and StageRenderer.
Overview
This example demonstrates a todo list with add, complete, and delete functionality using Stage Flow with individual stage components and StageRenderer.
Key Features to Observe
- Individual Stage Components: Each stage is a separate React component
- Props-based Communication: Stage components receive data and actions as props
- StageRenderer Integration: Uses StageRenderer for automatic stage switching
- Dynamic Data Management: Adding and removing items from the list
- State Persistence: Todo items persist across state changes
- Event Handling: Add, complete, and delete operations
- Data Validation: Input validation for new todos
Live Example
Live Editor
// import { StageFlowEngine } from '@stage-flow/core'; // import { StageFlowProvider, useStageFlow } from '@stage-flow/react'; function TodoList() { // Individual stage component for the todo list function IdleStage({ data, send }) { const { engine } = useStageFlow(); const handleAddTodo = React.useCallback(() => { if (data?.newTodo?.trim()) { const todo = { id: Date.now(), text: data.newTodo.trim(), completed: false, }; engine.setStageData({ todos: [...(data?.todos || []), todo], newTodo: "", }); } }, [data?.newTodo, data?.todos, engine]); const handleCompleteTodo = React.useCallback((id) => { const updatedTodos = (data?.todos || []).map((todo) => todo.id === id ? { ...todo, completed: !todo.completed } : todo ); engine.setStageData({ todos: updatedTodos }); }, [data?.todos, engine]); const handleDeleteTodo = React.useCallback((id) => { const updatedTodos = (data?.todos || []).filter((todo) => todo.id !== id); engine.setStageData({ todos: updatedTodos }); }, [data?.todos, engine]); const handleUpdateNewTodo = React.useCallback((value) => { engine.setStageData({ ...data, newTodo: value }); }, [data, engine]); const handleKeyPress = React.useCallback((e) => { if (e.key === "Enter") { handleAddTodo(); } }, [handleAddTodo]); const handleSave = React.useCallback(() => { send("save", { ...data }); }, [data, send]); return ( <div style={{ padding: "20px", border: "1px solid #ddd", borderRadius: "8px", maxWidth: "500px", backgroundColor: "white", }} > <h2 style={{ margin: "0 0 20px 0", color: "#2c3e50" }}> Todo List </h2> <div style={{ marginBottom: "20px" }}> <div style={{ display: "flex", gap: "10px", marginBottom: "10px" }}> <input type="text" value={data?.newTodo || ""} onChange={(e) => handleUpdateNewTodo(e.target.value)} onKeyPress={handleKeyPress} placeholder="Add a new todo..." style={{ flex: 1, padding: "10px", border: "1px solid #ddd", borderRadius: "4px", marginRight: "10px", fontSize: "14px", color: "#2c3e50", backgroundColor: "white", }} /> <button onClick={handleAddTodo} style={{ padding: "10px 20px", backgroundColor: "#28a745", color: "white", border: "none", borderRadius: "4px", cursor: "pointer", fontSize: "14px", fontWeight: "500", }} > Add </button> </div> <p style={{ color: "#6c757d", margin: 0, fontSize: "14px" }}> Current Stage: idle </p> </div> <div> <h3 style={{ margin: "0 0 15px 0", color: "#2c3e50" }}> Todos ({data?.todos?.length || 0}) </h3> {data?.todos?.length === 0 ? ( <p style={{ color: "#6c757d", textAlign: "center", fontStyle: "italic" }}> No todos yet. Add one above! </p> ) : ( <div style={{ display: "flex", flexDirection: "column", gap: "10px" }}> {data?.todos?.map((todo) => ( <div key={todo.id} style={{ display: "flex", alignItems: "center", padding: "10px", border: "1px solid #eee", borderRadius: "4px", backgroundColor: todo.completed ? "#f8f9fa" : "white", }} > <input type="checkbox" checked={todo.completed} onChange={() => handleCompleteTodo(todo.id)} style={{ marginRight: "10px" }} /> <span style={{ flex: 1, textDecoration: todo.completed ? "line-through" : "none", color: todo.completed ? "#6c757d" : "#495057", }} > {todo.text} </span> <button onClick={() => handleDeleteTodo(todo.id)} style={{ padding: "4px 8px", backgroundColor: "#dc3545", color: "white", border: "none", borderRadius: "4px", cursor: "pointer", fontSize: "12px", fontWeight: "500", }} > Delete </button> </div> ))} </div> )} </div> <div style={{ marginTop: "20px", textAlign: "center" }}> <button onClick={handleSave} style={{ padding: "12px 24px", backgroundColor: "#007bff", color: "white", border: "none", borderRadius: "4px", cursor: "pointer", fontSize: "16px", fontWeight: "500", }} > Save Todos </button> </div> </div> ); } // Saved stage component function SavedStage({ data, send }) { const { engine } = useStageFlow(); const handleBack = React.useCallback(() => { send("back", { ...data }); }, [data, send]); return ( <div style={{ padding: "20px", border: "1px solid #ddd", borderRadius: "8px", maxWidth: "500px", backgroundColor: "white", }} > <h2 style={{ margin: "0 0 20px 0", color: "#2c3e50" }}> ✅ Todos Saved Successfully! </h2> <div style={{ marginBottom: "20px" }}> <p style={{ color: "#28a745", margin: "0 0 10px 0", fontSize: "16px" }}> Your todos have been saved successfully. </p> <p style={{ color: "#6c757d", margin: 0, fontSize: "14px" }}> Current Stage: saved </p> </div> <div> <h3 style={{ margin: "0 0 15px 0", color: "#2c3e50" }}> Saved Todos ({data?.todos?.length || 0}) </h3> {data?.todos?.length === 0 ? ( <p style={{ color: "#6c757d", textAlign: "center", fontStyle: "italic" }}> No todos were saved. </p> ) : ( <div style={{ display: "flex", flexDirection: "column", gap: "10px" }}> {data?.todos?.map((todo) => ( <div key={todo.id} style={{ display: "flex", alignItems: "center", padding: "10px", border: "1px solid #eee", borderRadius: "4px", backgroundColor: todo.completed ? "#f8f9fa" : "white", }} > <input type="checkbox" checked={todo.completed} disabled style={{ marginRight: "10px" }} /> <span style={{ flex: 1, textDecoration: todo.completed ? "line-through" : "none", color: todo.completed ? "#6c757d" : "#495057", }} > {todo.text} </span> </div> ))} </div> )} </div> <div style={{ marginTop: "20px", textAlign: "center" }}> <button onClick={handleBack} style={{ padding: "12px 24px", backgroundColor: "#6c757d", color: "white", border: "none", borderRadius: "4px", cursor: "pointer", fontSize: "16px", fontWeight: "500", }} > Back to Edit </button> </div> </div> ); } // Create engine directly without useRef const engine = new StageFlowEngine({ initial: "idle", stages: [ { name: "idle", data: { todos: [], newTodo: "" }, transitions: [ { target: "saved", event: "save" }, ], }, { name: "saved", data: { todos: [], newTodo: "" }, transitions: [ { target: "idle", event: "back" }, ], }, ], }); engine.start(); return ( <StageFlowProvider engine={engine}> <StageRenderer stageComponents={{ idle: IdleStage, saved: SavedStage, }} /> </StageFlowProvider> ); }
Result
Loading...