In my last blog post, , I discussed one of the most important benefits of using Redux — debuggability. By using front end logging tools like LogRocket, developers can easily understand and fix tricky bugs in production by reviewing the and changes leading up to a bug. Redux Logging in Production actions state While this information is immediately useful in any Redux app, there is a lot more we can achieve by architecting an app with logging in mind. In this post I’m going to look at a few libraries and abstractions that make Redux logs useful by putting as much application data through Redux as possible. even more Data Fetching Fetching/sending data over the network is one of the most bug-prone parts of any app. Issues can arise from connectivity, unexpected data or incorrect logic. And things get extra complicated with polling, retry logic, optimistic mutations, etc. Libraries like for GraphQL, and for REST both facilitate fetching data from the network via Redux. They use Redux as a persistence layer, meaning that when debugging issues, you can inspect your Redux logs to see what data these clients have fetched and what the status is of in-flight requests. apollo-client redux-query Lets take a look at the Redux logs generated by redux-query: Here we see the action, which corresponds with a query being initialized. Looking at the action payload, we see all the information in the request, making it easy to debug. Once a response is received, emits a action with all the information about the response. REQUEST_START redux-query REQUEST_SUCCESS Logging requests and responses is only part of the magic of . Digging into the we see a key which is where persists its internal state. redux-query store queries redux-query Above, we see objects for each of the queries the app carried out (one to and one to ). When debugging issues, we can dig into this state object to see information on in-flight requests, (if we’re polling on a query), and timings. reddit.com/r/frontend.json reddit.com/r/reactjs.json queryCount Storing this information in Redux is critical, since it puts full context on all network activity in the Redux logs. Rolling your own data fetching “framework” If you’d prefer a simpler approach, you can roll your own data fetching “framework” by simply dispatching explicit actions when querying and receiving data from the network. For example, lets say we’re building a blogging app. When querying for posts, we would dispatch . The reducer could then update the state appropriately to indicate that the posts query is in progress. POSTS_QUERY_INIT postsQuery: {url: 'api.blog.com/posts',isPending: true,...} In a or , we would call and when the promise resolves, we’d dispatch an action like or . This would then update state appropriately to: thunk saga fetch POSTS_QUERY_SUCCESS POSTS_QUERY_FAILURE postsQuery: {url: 'api.blog.com/posts',isPending: true,data: [...],} This example is far from thorough, but the idea is that by being explicit with Redux actions for each part of the request lifecycle, it becomes easy to debug any potential race condition or network error. Handling other sources of non-determinism In addition to network fetching, there are lots of other sources of non-determinism that can cause bugs. Luckily, we can use Redux for these as well to leave thorough logs in the event of a bug. websockets When listening on a websocket, we can dispatch an action whenever we receive data and reduce it into the store appropriately. For example: myWebSocket.onmessage = function (event) { store.dispatch({ type: 'BLOG_POST_UPDATE_RECEIVED', payload: event, } } That way, when looking at the Redux logs for an error or user-reported issue, we can see all the data that was received over the websocket and, crucially, relate it in time to other redux actions and network requests. Local Storage Often, an app needs to read from local storage when it first starts up. To do this, you can use a handy library that facilitates dumping state to local storage and reading/merging it back into state. redux-storage Whenever loads or saves state from redux, it emits an action showing exactly the payload that will get reduced into the store. redux-storage Everything else… The pattern of dispatching Redux actions for sources of non-determinism applies to most APIs like IndexedDB, or even functions like Date() and Math.random()- consider dispatching Redux actions with the result, so that you can easily debug these in the future. React Router Using lets you synchronize react-router state into Redux. Adding the integration is trivial, and doesn’t require any changes to how you use . Once you get the library set up, you’ll see a new key in your Redux store called with information on the current router state. react-router-redux react-router routing In addition, dispatches actions like when its state changes. react-router-redux @@router/LOCATION_CHANGE Also of note is that using lets you rewind router state when time-traveling in , since its state its state is derived from the state in Redux. react-router-redux redux-devtools A note about local vs Redux state I don’t want to get into the debate on local vs Redux state here, but production Redux logging does change the calculus of this decision in some cases. When deciding whether a given piece of state should be in Redux, ask yourself if seeing that state (and the actions that influenced it) could be helpful when debugging issues. If the answer is yes, consider putting that state in Redux so that it will be logged with crash reports and user issues. Production Redux Logging Logging Redux data in production helps you fix bugs and user-reported issues. Check out my previous blog post to learn more: _One of the greatest strengths of Redux is debuggability — by logging actions and state during an app’s execution…_blog.logrocket.com Redux Logging in Production TL;DR Using libraries and patterns that put data through Redux helps build more debuggable applications by leaving a rich audit trail. When architecting a new feature, ask yourself if it might be error-prone, and whether being able to view its state in the Redux logs would help solve a future bug. It’s tough to keep up-to-date on front-end dev. Join our weekly mailing list to learn about new tools, libraries and best practices that will help you build better apps: LogRocket is the JavaScript logging and replay tool that helps you fix bugs, faster. By capturing every log, network request and user session of your app, you can fix problems without back and forth.