React Offline-First State Management
Building modern web applications often requires a seamless user experience, even in the face of intermittent network connectivity. This challenge focuses on implementing an offline-first state management strategy in a React application using TypeScript, ensuring data is always available and changes are synchronized when connectivity is restored.
Problem Description
Your task is to build a simple to-do list application that functions seamlessly offline. The application should allow users to add, mark as complete, and delete to-do items. When the application is offline, these changes should be stored locally and then synchronized with a remote backend (simulated for this challenge) once the network connection is re-established.
Key Requirements:
- Offline Data Storage: All to-do items and their states (e.g., completed, deleted) must be persisted locally so they are accessible when offline.
- Online/Offline Detection: The application needs to detect when it is online or offline.
- Local State Management: React components should always display the current state of the to-do list, whether from local storage or the remote backend.
- Change Queueing: Any mutations (add, complete, delete) performed while offline should be queued locally.
- Synchronization: When the application comes back online, the queued changes must be sent to the remote backend in the correct order.
- Conflict Resolution (Basic): For simplicity, assume that if a to-do item is modified both locally and remotely before synchronization, the remote change takes precedence for now. In a real-world scenario, more sophisticated conflict resolution would be needed.
- UI Feedback: Provide clear visual feedback to the user indicating whether the application is online, offline, or synchronizing.
Expected Behavior:
- When online, the app fetches data from the "remote backend" and displays it.
- When the network connection drops, the app continues to function using locally stored data. New items can be added, marked complete, or deleted.
- When the network connection is restored, the locally queued changes are sent to the "remote backend" and applied. The UI should then reflect the synchronized state.
- If a to-do item is deleted offline and then marked complete online before sync, the deletion should win.
Edge Cases:
- Initial load when the device is offline.
- Rapidly switching between online and offline states.
- User performing multiple actions while offline.
Examples
Example 1: Adding an item while offline
Input:
- App starts online, fetches initial todos: [{ id: '1', text: 'Buy milk', completed: false }]
- User goes offline.
- User adds a new todo: 'Walk the dog'
Output (Local Storage):
- Todos: [{ id: '1', text: 'Buy milk', completed: false }, { id: '2', text: 'Walk the dog', completed: false }]
- Pending Operations Queue: [{ type: 'ADD', payload: { id: '2', text: 'Walk the dog', completed: false } }]
Explanation: The new todo is added to the local state and queued for synchronization. The UI displays both items.
Example 2: Completing an item while offline and syncing
Input:
- User is offline.
- Locally stored todos: [{ id: '1', text: 'Buy milk', completed: false }]
- User marks 'Buy milk' as complete.
- User comes back online.
Output (After Sync):
- Remote Backend receives: [{ type: 'UPDATE', payload: { id: '1', text: 'Buy milk', completed: true } }]
- Remote Backend State: [{ id: '1', text: 'Buy milk', completed: true }]
- Local Storage (after successful sync): [{ id: '1', text: 'Buy milk', completed: true }]
- Pending Operations Queue: []
Explanation: The completion is queued, then sent to the backend once online. The remote backend is updated, and the local state reflects the synchronized version.
Example 3: Deleting an item offline, then updating it online (before sync)
Input:
- App is online, initial todos: [{ id: '1', text: 'Buy milk', completed: false }]
- User goes offline.
- User deletes 'Buy milk'.
- While still offline, user somehow (e.g., through another device, or a delayed effect) changes 'Buy milk' to be completed remotely.
- User comes back online.
Output (After Sync - assuming delete takes precedence):
- Remote Backend receives: [{ type: 'DELETE', payload: { id: '1' } }]
- Remote Backend State: []
- Local Storage (after successful sync): []
- Pending Operations Queue: []
Explanation: The delete operation was queued first. When the app comes online, the queued delete operation is sent to the backend. Even though there was a pending online update to the same item, the delete operation is processed, effectively removing it from the backend and local storage. (Note: This is a simplified conflict. In a real app, you might merge states or use more complex resolution).
Constraints
- Backend Simulation: You will simulate the backend using a simple JavaScript object or an array. Network requests will be simulated using
setTimeoutto mimic network latency. - Local Storage: Use the browser's
localStorageAPI for persistent offline storage. - State Management: You can use React's built-in
useStateanduseReducer, or a library like Zustand or Jotai. The core challenge is the offline-first logic, not the choice of state management library. - Network Detection: Use the browser's
navigator.onLineproperty and potentially listen toonlineandofflineevents. - Performance: The application should remain responsive even with a moderate number of to-do items (e.g., up to 100).
Notes
- Consider how you will generate unique IDs for new to-do items.
- Think about the data structure for your local storage and the queue of pending operations.
- The
navigator.onLineproperty can sometimes be unreliable. For a more robust solution, you might implement periodic "heartbeat" checks to the backend. - When synchronizing, ensure operations are sent in the order they were performed to avoid race conditions and logical errors.
- For this challenge, you don't need to implement complex authentication or authorization. Focus purely on the offline-first data synchronization aspect.
- You'll need to create mock API functions for
fetchTodos,addTodo,updateTodo, anddeleteTodothat simulate network latency and success/failure responses.