A bug is reported. A user seems to have gotten your app in some unforeseen and untested limbo-state, which you cannot reproduce. He or she might even provide all the info you ask for: browser, screenshots, steps taken, et cetera, but the problem cannot be produced again and then debugged.
Solving this with Flux
Using Facebook’s Flux architecture with React, we can quite easily solve this problem. Since every application state mutation is triggered by an action, we can ‘record’ these actions and replay them later. We can get to the exact same state as the user when things went wrong, and we can see every step he or she took to get there.
It’s important to note that with React with Flux, the application state lives outside of the UI-components, in the store(s). The stores populate their state according to the actions and the UI merely reflects this state. A UI defined with React can be seen as a pure function: given the same input (state), it will always produce the same output (ui).
To record actions in Flux, we need to either extend the dispatcher with this functionality or create a new component which listens to the dispatcher. I’ve published a proof-of-concept on GitHub called FluxRecorder, which does the latter. It’s easy to setup:
var AppDispatcher = require('./dispatcher');
var FluxRecorder = require('./flux-recorder');
var recorder = new FluxRecorder(AppDispatcher);
The AppDispatcher is a reference to the dispatcher singleton used in your app. When we want to get the recorded actions back from the recorder we can do this as follows:
When a user files a bug or reports feedback, we could call this function and send the results along as a JSON. FluxRecorder also includes hotkeys, to easily copy the action list onto the clipboard while testing:
recorder.listenToHotKeys(); // listens to ALT-SHIFT-C and shows a prompt with the current action list as a serialised array
Once we have a list of actions to replay, we need to get the application into the same state as when the recording started. The only simple way to achieve this, is to start recording when the app is in its initial (empty) state. All the data coming into the app will be from server actions and user actions, which will all be recorded, allowing us to get to an identical state.
When we supply the FluxRecorder with the list of actions, it will start playing them step-by-step, with a supplied interval (default: 500ms).
If we’re listening to hotkeys, we can also press ALT-SHIFT-P to show a prompt, in which we can paste an earlier copied JSON. The application will then start playing the actions.
It’s important to turn off all API-communication during playbacks. All data coming into the system should have gone through some server action, which makes the API completely obsolete for playback.
Usage with React Router
If we use React Router (or any other), we need to make sure all route-changes are action-driven, and the route-state is saved to a RouterStore, to which the UI reacts. Doing this you’ll actually see how the user navigates the application.
There might be some application state that you are not capturing. Scrolling is not typically not something that goes through actions into stores. This is usually fine. Think about which state is import to capture. If your app is somehow reacting to scrolling, you might want to capture this, to be able to replay it later.
Possible Race Conditions
One problem I encountered when doing replays with no intervals, were race conditions. Some components in the app might inadvertently rely on delays that happen when waiting for external API’s or user input. This usually means a problem with the code. In the future we might want to save timestamps for each action and implement realistic playback, or even a timeline.
- Try out Facebook’s Flux Chat Demo with FluxRecorder enabled here.
- Or just watch the video: