Maximizing Debuggability with Redux – LogRocket

Maximizing Debuggability with Redux  #javascript #react #logging #debugging #redux #reactjs

  • By using front end logging tools like LogRocket, developers can easily understand and fix tricky bugs in production by reviewing the actions and state changes leading up to a bug.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.
  • When debugging issues, we can dig into this state object to see information on in-flight requests, queryCount (if we’re polling on a query), and timings.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.
  • This would then update state appropriately to: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-determinismIn addition to network fetching, there are lots of other sources of non-determinism that can cause bugs.
  • For example:myWebSocket.onmessage = function (event) { store.dispatch({ type: ‘BLOG_POST_UPDATE_RECEIVED’, payload: 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 StorageOften, an app needs to read from local storage when it first starts up.
  • Once you get the library set up, you’ll see a new key in your Redux store called routing with information on the current router state.In addition, react-router-redux dispatches actions like @@router/LOCATION_CHANGE when its state changes.Also of note is that using react-router-redux lets you rewind router state when time-traveling in redux-devtools, since its state its state is derived from the state in Redux.A note about local vs Redux stateI 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.

In my last blog post, Redux Logging in Production, I discussed one of the most important benefits of using Redux — debuggability. By using front end logging tools like LogRocket, developers can…

@ReactDOM: Maximizing Debuggability with Redux #javascript #react #logging #debugging #redux #reactjs

changes leading up to a bug.

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 even more useful by putting as much application data through Redux as possible.

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 apollo-client for GraphQL, and redux-query 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.

action with all the information about the response.

persists its internal state.

(if we’re polling on a query), and timings.

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.

. The reducer could then update the state appropriately to indicate that the posts query is in progress.

postsQuery: {

url: ‘api.blog.com/posts’,

isPending: true,

. This would then update state appropriately to:

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.

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.

When listening on a websocket, we can dispatch an action whenever we receive data and reduce it into the store appropriately. For example:

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.

a handy library that facilitates dumping state to local storage and reading/merging it back into state.

loads or saves state from redux, it emits an action showing exactly the payload that will get reduced into the store.

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.

with information on the current router state.

when its state changes.

, since its state its state is derived from the state in Redux.

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.

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.

Maximizing Debuggability with Redux – LogRocket