We use cookies to enhance your experience on the site
CodeWorlds
Back to collections
Guide15 min read

React Doctor, or one command and a project score

React Doctor scans a React project and returns a score from zero to a hundred with a list of issues. How the score works, what it catches, CI use, and limits.

React Doctor, or one command and a project score

React Doctor scans a React project and returns a score from zero to a hundred along with a list of concrete issues. It needs no installation and no configuration, since it runs from a single command and detects the framework and library version itself.

Code
Bash
npx -y react-doctor@latest .

The tool comes from the team known for a library that speeds up React rendering, and its code is public. The licence, though, is a modified permissive one, with two carve outs requiring written permission: using the code as training or evaluation data for models, and reselling the tool as a paid hosted service. For ordinary use in a project and in a build process none of that matters, while calling it simply "open source" would be imprecise. Underneath it runs a fast analyser written in Rust plus a separate pass detecting code nothing leads to.

Deterministic, meaning repeatable

That distinction deserves stating at the start, since code analysis tools now split into two families and material about them regularly blends them.

Tools built on language models read code and phrase their comments in their own words. They see context, can judge whether a solution makes sense, and may say something different on each run.

This tool belongs to the second family. It checks a fixed rule set, so the same code always yields the same result. It will not judge whether a component makes sense, while it will state with certainty that seventeen places hold a side effect replaceable by a computation during rendering.

The consequence is practical. A score from this tool suits a gate in the build process, since it is repeatable and will not block a deployment because a model had an off day. A score from a language model based tool suits review, where understanding intent matters.

The two approaches do not compete but cover different layers. A sensible arrangement includes both: a deterministic check in the build process and contextual review at pull request time.

One caveat applies, and only to the final number. The findings are produced locally and are repeatable, while the scoring arrives from the vendor's service, as the next section covers.

Where the score comes from

That number is not produced on your machine, and it is worth knowing before you wire the tool in anywhere. The scan is local, while the list of findings travels over the network to the vendor's service, which returns the score, a text label, and an assignment of rules to four urgency levels labelled P0 to P3.

The payload sent covers the findings alone after sanitising, meaning the rule identifier, the message, and the file path with sensitive fragments redacted, plus run metadata: repository, commit hash, framework, React version, source file count, and default branch. Source code does not travel in that payload.

There are two consequences and both are practical. Without network access no score is produced at all: the tool prints a warning that the service is unreachable, and the list of problems stays intact. If you do not want that traffic, one switch turns it off, and it also disables the share link and crash reporting.

Code
Bash
npx react-doctor@latest --no-score

The second consequence concerns comparison over time. The findings themselves are repeatable, since they come from a fixed rule set run locally, while the conversion into points sits on the vendor's side and can change without any change to the package version. The score therefore makes sense as a series of measurements taken with the same version over a short span rather than as a number compared with last quarter.

Take the fixing order from the service's response rather than from an occurrence counter. A rule's placement between P0 and P3 says what is urgent, while the number of places it was violated says only how much work it will be.

The number itself matters less than its direction anyway. A score of seventy four says nothing in isolation, while a score that fell from eighty to seventy four over the past week says a great deal.

What it catches

The rule set targets React rather than general coding principles, and that is its main value over an ordinary analyser.

Unnecessary side effects are the most common finding. A value derived from props and state needs no effect and no separate state, only an ordinary expression during rendering. That pattern recurs in almost every project and causes double renders and bugs that are hard to trace.

Passing props down through many levels is the second. A value handed through five components, none of which uses it, points to a missing context layer or a poor component split.

Dead code is the third and usually the most surprising. Components, helper functions, and whole files nothing leads to remain in a project after refactoring, and nobody removes them because nobody knows they are unnecessary.

Beyond those come imports pulling a whole library instead of one function, accessibility, where a missing label or a wrong semantic element follows directly from the code, and common security traps such as inserting raw page markup from an unvetted source.

A dependency check for suspicious packages sits apart from the rules. An external service performs it, so it needs network access and turns off with its own --no-supply-chain switch, which matters when building on a machine cut off from the internet.

The full rule list can be inspected before the first run against a project, and a single rule explained or silenced if it does not fit your agreed conventions.

Code
Bash
npx react-doctor@latest rules list
npx react-doctor@latest rules explain <rule-name>
npx react-doctor@latest rules disable <rule-name>

Read the explanation before disabling a rule. Some findings look like nitpicking until you see which bug they are meant to catch, and then the decision often goes the other way. A whole category can also be set at once, which is more convenient than disabling rules one by one.

Wiring it into the process

Running it by hand is good to start with and poor as a habit, since nobody remembers to run anything.

The right place is the build process at pull request time. The tool can scan only changed files and attach its comments at specific lines, which is far more effective than a report you must open separately.

The scan scope is set with one switch, and the variant comparing against the base branch reports only what this change introduced.

Code
Bash
npx react-doctor@latest --scope changed --base origin/main --blocking error

The second switch decides when the build should fail. The default stops on errors, the warning variant also stops on lower severity findings, and turning blocking off lets you gather data for the first few weeks without halting anybody's work. That last variant is the right starting point on an existing project.

On an existing project, set the gate against the current state rather than against an ideal. Record today's score as a reference and block only changes that lower it. A gate requiring ninety points in a project scoring sixty four will be disabled within a week and stop protecting anything.

A sensible rollout order runs like this. First run it once and read the list, since some rules may not suit your project. Then disable those you have deliberately decided against, since noise lowers the value of the rest. Only then enable the gate at the current score and raise it gradually.

A separate place is the check before committing, where execution time is all that matters. Scanning only the staged files, with a time budget imposed, fits into a few seconds.

Code
Bash
npx react-doctor@latest --staged --max-duration 15

The time budget matters more than it seems. A check taking half a minute gets bypassed at the first moment of hurry, and once bypassed it stops existing. A partial result in fifteen seconds beats a complete one nobody waits for.

What it costs

The price list is easy to miss, since the tool runs from a single command and asks for no account. The vendor splits usage in two: free personal use, covering unlimited command line runs and the GitHub action, and a team plan at thirty dollars a month covering commercial use of both.

A third option, meant for larger rollouts, is priced individually and adds priority support, onboarding help, early access to new rules, and rules written for a particular project.

Licence and price list speak about different things and are worth keeping apart. The licence describes what you may do with the tool's code, the price list what the vendor expects to be paid for at work. Before wiring the scan into a build process at a company, that is a question to settle rather than a detail to defer.

React Doctor against the alternatives

OptionStrengthWeaknessPick it when
React DoctorZero configuration, React specific rules, repeatable scoreDoes not judge whether a solution makes senseA quick read on project state and a build gate
A classic code analyserFull configuration, an enormous choice of rulesConfiguration takes daysA team with settled conventions
QodoPull request review, test generationRequires an account and quotasReviewing changes before merge
GreptileWhole repository contextNo self hosted variantA large repository with many dependencies

The first row wins where time to a first result matters. One command and a score within a minute is an entirely different situation from a day of configuring rules, particularly when you only want to see the state of a project you inherited.

The second row is not an alternative here but a layer alongside. A classic analyser watches style and basic correctness on every file save, while this tool assesses the whole project's state and catches architectural patterns.

Code written by assistants

One use deserves separate treatment, since it recently became the main reason people reach for tools like this.

Code generated by assistants looks correct and regularly contains exactly the patterns this tool catches. A side effect instead of a computation, props passed through four levels, imports pulling a whole library, a component left behind after a change of direction and never removed.

The reason is understandable. A model generates code fitting the fragment it sees, without the full picture of the project, so it reproduces patterns that are locally correct and globally redundant. Across two components that is invisible; across two hundred it adds up to a project that is hard to maintain.

An automated check against a fixed rule set is more effective here than human review, since such patterns are dull, repetitive, and easy to miss while reading the twentieth file that day. The tool finds all of them and does so identically every time.

The tool can also be attached as a skill available to an assistant, which closes the loop: the assistant writes code, runs the scan, and fixes what it introduced before a person even sees the pull request.

What to do with the result

A report listing sixty comments is paralysing, so having a plan for moving from a number to code changes pays off.

Start by splitting into three categories. The first is things to fix now: security and accessibility, since both carry consequences beyond the code. The second is things to fix in passing, meaning performance and architectural patterns, corrected when you are touching that file anyway. The third is things to skip deliberately, since not every rule suits every project.

Treat dead code separately, since it is the easiest gain in the whole report. Removing files nothing leads to requires no architectural thinking, breaks nothing, and immediately reduces what must be read, built, and maintained. Do verify the findings by hand, though, since dynamically loaded code or code used only in configuration is sometimes flagged as dead wrongly.

Leave unnecessary side effects for the end of the first pass, even though there are usually the most of them. Each such fix requires understanding where a value comes from and what happens when props change, so it is work for attention rather than for bulk replacement.

Record the starting score before changing anything. Without a reference point, after two weeks you cannot tell improvement from the impression of improvement, and that is the only measure saying whether the work was worthwhile.

For that measurement the tool has two outputs worth recording. The first returns the number alone, the second a full report in a format suited to further processing.

Code
Bash
npx react-doctor@latest --score >> score-history.txt
npx react-doctor@latest --json --json-out reports/$(git rev-parse --short HEAD).json

Naming the file after the commit hash gives a history showing not only whether the score is rising but which change dropped it. That is the difference between knowing something got worse and knowing what it was.

The limits of this approach

Worth stating plainly what such a tool will not do, since a numeric score tempts you to treat it as the whole truth about a project.

It will not judge architecture. A project with a clean layer split and one where business logic sits inside components can receive the same score if both respect the syntactic rules.

It will not judge tests. The absence of any test coverage does not lower the score, though for maintaining a project it matters more than most of the patterns caught.

It will not judge whether the code does what it should. A component computing a discount with a bug in its threshold passes the scan without comment, since by the rules it is written correctly.

Nor will it replace a conversation about where the project is heading. A score of eighty five in a module slated for removal next quarter is information without value, and the tool does not know that.

The conclusion is not "do not use it" but a statement of its place. This is a fast thermometer telling you whether something is getting worse and where the repeatable problems lie. Diagnosing the whole remains the work of people who know the project.

Common mistakes

The first is treating the score as a percentage of code quality. It is a figure computed on the vendor's side from the list of violated rules, not the share of the project that is fine.

The second is setting a high gate on an existing project. An unreachable gate gets disabled within a week and stops protecting anything.

The third is leaving every rule enabled despite a deliberate decision against some of them. Noise lowers the value of the remaining comments.

The fourth is running it manually instead of wiring it into the build. Nobody remembers to run tools, so automation beats good intentions.

The fifth is expecting a judgement on whether a solution makes sense. The tool checks rules, and whether a solution fits the architecture is a reviewer's question.

The sixth is ordering fixes by occurrence count. The service computing the score assigns rules to levels from P0 to P3, and that is a better order than the counter beside each rule.

FAQ

Does React Doctor use a language model?

No. It checks a fixed rule set, so results are repeatable and the same code always yields the same score. That makes it suitable as a build gate, unlike language model based tools, which fit better at review time.

Where does the score come from?

The scan runs locally, but the scoring itself is done by the vendor's service, which receives a sanitised list of findings and run metadata, without source code. No network means no score, and the --no-score switch cuts that upload along with the share link and crash reporting.

Will it replace a classic code analyser?

No, it is a layer alongside. A classic analyser watches style and basic correctness on every file save. This tool assesses the whole project's state and catches patterns specific to React that an ordinary rule set does not cover.

Does it work with Next.js and other frameworks?

Yes, it detects the framework itself and selects rules accordingly. Supported setups include Next.js projects, newer build tools, and React mobile applications.

Why run it on code written with an assistant?

Because an assistant sees a fragment rather than the whole project, so it reproduces patterns that are locally correct and globally redundant: effects instead of computations, props passed through many levels, code left behind after a change of direction. Automation finds them identically every time, while a person reading their twentieth file that day may not.

Documentation sits on the project site, and the package in the npm registry.