@tour-kit/checklistsHooks
useTask
useTask hook: control individual task state, trigger completion, handle dependencies, and access task metadata in React
useTask
Hook for accessing and controlling individual task state.
Usage
import { useTask } from '@tour-kit/checklists';
function TaskItem({ checklistId, taskId }) {
const {
task,
isCompleted,
isLocked,
complete,
execute,
} = useTask(checklistId, taskId);
if (!task) return null;
return (
<button
onClick={execute}
disabled={isLocked}
>
{isCompleted ? '✓' : '○'} {task.config.title}
</button>
);
}Parameters
Prop
Type
Return Value
Prop
Type
Basic Examples
Simple Checkbox
function TaskCheckbox({ checklistId, taskId }) {
const { task, isCompleted, isLocked, toggle } = useTask(checklistId, taskId);
if (!task) return null;
return (
<label>
<input
type="checkbox"
checked={isCompleted}
disabled={isLocked}
onChange={toggle}
/>
{task.config.title}
</label>
);
}Task Card
function TaskCard({ checklistId, taskId }) {
const { task, isCompleted, isLocked, execute } = useTask(checklistId, taskId);
if (!task) return null;
return (
<div className={`task-card ${isCompleted ? 'completed' : ''}`}>
{task.config.icon && <span>{task.config.icon}</span>}
<div>
<h4>{task.config.title}</h4>
{task.config.description && (
<p>{task.config.description}</p>
)}
</div>
<button
onClick={execute}
disabled={isLocked || isCompleted}
>
{isCompleted ? 'Done ✓' : isLocked ? 'Locked 🔒' : 'Start'}
</button>
</div>
);
}Progress Indicator
function TaskProgress({ checklistId, taskId }) {
const { task, isCompleted, isLocked } = useTask(checklistId, taskId);
if (!task) return null;
let status = 'pending';
if (isCompleted) status = 'completed';
else if (isLocked) status = 'locked';
const icons = {
pending: '○',
locked: '🔒',
completed: '✓',
};
const colors = {
pending: 'gray',
locked: 'gray',
completed: 'green',
};
return (
<div style={{ color: colors[status] }}>
<span>{icons[status]}</span>
<span>{task.config.title}</span>
</div>
);
}Advanced Examples
With Dependencies Display
function TaskWithDependencies({ checklistId, taskId }) {
const { task, isLocked } = useTask(checklistId, taskId);
if (!task) return null;
const dependencies = task.config.dependsOn ?? [];
return (
<div>
<h4>{task.config.title}</h4>
{isLocked && dependencies.length > 0 && (
<div className="dependencies">
<p>Complete these first:</p>
<ul>
{dependencies.map((depId) => (
<li key={depId}>
<DependencyTask checklistId={checklistId} taskId={depId} />
</li>
))}
</ul>
</div>
)}
</div>
);
}
function DependencyTask({ checklistId, taskId }) {
const { task, isCompleted } = useTask(checklistId, taskId);
if (!task) return <span>{taskId}</span>;
return (
<span>
{isCompleted ? '✓' : '○'} {task.config.title}
</span>
);
}Manual vs Auto Complete
function TaskWithManualControl({ checklistId, taskId }) {
const { task, isCompleted, isLocked, execute, complete } = useTask(
checklistId,
taskId
);
if (!task) return null;
const hasAction = !!task.config.action;
const manualComplete = task.config.manualComplete !== false;
return (
<div>
<h4>{task.config.title}</h4>
{hasAction && (
<button
onClick={execute}
disabled={isLocked}
>
{task.config.action.type === 'navigate' && 'Go →'}
{task.config.action.type === 'tour' && 'Start Tour'}
{task.config.action.type === 'callback' && 'Execute'}
</button>
)}
{!manualComplete && !isCompleted && (
<button
onClick={complete}
disabled={isLocked}
>
Mark as complete
</button>
)}
{isCompleted && <span>✓ Completed</span>}
</div>
);
}Completion Timestamp
function TaskWithTimestamp({ checklistId, taskId }) {
const { task, isCompleted } = useTask(checklistId, taskId);
if (!task) return null;
return (
<div>
<h4>{task.config.title}</h4>
{isCompleted && task.completedAt && (
<p className="text-sm text-muted">
Completed {new Date(task.completedAt).toLocaleDateString()}
</p>
)}
</div>
);
}Conditional Rendering
function ConditionalTask({ checklistId, taskId }) {
const { task, isVisible, isCompleted } = useTask(checklistId, taskId);
// Don't render if task doesn't exist or isn't visible
if (!task || !isVisible) return null;
// Don't render if completed (optional)
if (isCompleted) return null;
return (
<div>
<h4>{task.config.title}</h4>
{/* Task content */}
</div>
);
}Task Actions
The execute function handles different action types:
Navigate Action
function NavigateTask({ checklistId, taskId }) {
const { task, execute } = useTask(checklistId, taskId);
if (!task || task.config.action?.type !== 'navigate') return null;
const action = task.config.action;
return (
<button onClick={execute}>
Go to {action.url}
{action.external && ' ↗'}
</button>
);
}Callback Action
function CallbackTask({ checklistId, taskId }) {
const { task, execute } = useTask(checklistId, taskId);
const [loading, setLoading] = useState(false);
if (!task || task.config.action?.type !== 'callback') return null;
const handleExecute = async () => {
setLoading(true);
try {
await execute();
} finally {
setLoading(false);
}
};
return (
<button onClick={handleExecute} disabled={loading}>
{loading ? 'Processing...' : 'Execute'}
</button>
);
}Custom Action Handling
function CustomActionTask({ checklistId, taskId }) {
const { task, complete } = useTask(checklistId, taskId);
if (!task) return null;
const handleCustomAction = async () => {
// Your custom logic
await myCustomHandler(task.config.action?.data);
// Manually complete if needed
if (task.config.manualComplete !== false) {
complete();
}
};
return (
<button onClick={handleCustomAction}>
Custom Action
</button>
);
}Optimistic Updates
function OptimisticTask({ checklistId, taskId }) {
const { task, isCompleted, toggle } = useTask(checklistId, taskId);
const [optimistic, setOptimistic] = useState(false);
if (!task) return null;
const handleToggle = async () => {
// Show optimistic state immediately
setOptimistic(true);
try {
// Perform async operation
await api.updateTask(taskId);
// Update actual state
toggle();
} catch (error) {
console.error('Failed to update task:', error);
} finally {
setOptimistic(false);
}
};
const checked = optimistic ? !isCompleted : isCompleted;
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={handleToggle}
disabled={optimistic}
/>
{task.config.title}
</label>
);
}Multiple Task Instances
When rendering the same task multiple times:
function TaskList({ checklistId, taskIds }) {
return (
<ul>
{taskIds.map((taskId) => (
<TaskListItem
key={taskId}
checklistId={checklistId}
taskId={taskId}
/>
))}
</ul>
);
}
function TaskListItem({ checklistId, taskId }) {
const { task, isCompleted, toggle } = useTask(checklistId, taskId);
if (!task) return null;
return (
<li>
<label>
<input
type="checkbox"
checked={isCompleted}
onChange={toggle}
/>
{task.config.title}
</label>
</li>
);
}Each useTask call with the same IDs returns the same state. React hooks handle this efficiently.
Error Handling
function SafeTask({ checklistId, taskId }) {
const { exists, task } = useTask(checklistId, taskId);
if (!exists) {
console.warn(`Task ${taskId} not found in ${checklistId}`);
return null;
}
return (
<div>
<h4>{task.config.title}</h4>
{/* Task content */}
</div>
);
}Performance Tips
Avoid Unnecessary Re-renders
// Bad: Entire list re-renders when any task changes
function BadTaskList({ checklistId, taskIds }) {
const tasks = taskIds.map((id) => useTask(checklistId, id));
return (
<ul>
{tasks.map((taskHook, i) => (
<li key={taskIds[i]}>
{/* All items re-render */}
</li>
))}
</ul>
);
}
// Good: Each task is isolated
function GoodTaskList({ checklistId, taskIds }) {
return (
<ul>
{taskIds.map((taskId) => (
<TaskItem
key={taskId}
checklistId={checklistId}
taskId={taskId}
/>
))}
</ul>
);
}
const TaskItem = memo(function TaskItem({ checklistId, taskId }) {
const { task, isCompleted, toggle } = useTask(checklistId, taskId);
// Only this item re-renders when its state changes
return <li>{/* ... */}</li>;
});Memoize Expensive Computations
function TaskWithStats({ checklistId, taskId }) {
const { task } = useTask(checklistId, taskId);
const stats = useMemo(() => {
if (!task) return null;
return {
hasDependencies: (task.config.dependsOn?.length ?? 0) > 0,
hasAction: !!task.config.action,
hasIcon: !!task.config.icon,
hasDescription: !!task.config.description,
};
}, [task]);
if (!task) return null;
return <div>{/* Use stats */}</div>;
}Related
- useChecklist - Access full checklist state
- ChecklistTask - Pre-built task component
- TaskHeadless - Headless task component