(With his hopefully mutual permission) I’m replicating the document as checkboxes to see those points I’ve met, and the ones where I might catch up.
You can do the same exercise too by forking the list from the source code of this article.
Getting familiar with a new project
Review package.json first to understand deps, scripts, and config.
Draw tree on the whiteboard, or use React dev tools. Helps to visualize state.
Quickly nav to component or func: CMD click in JSX (VSCode)
Quickly nav to parent: CMD+SHIFT+F to Search for <ComponentName (VSCode)
Quickly view list of parents: Use React dev tools
Create a component state checklist. Use it for every component. (error, no data, lots of data, long values, full list here)
Debugging? type debugger. console.assert also handy.
Work against mock data and mock API (I like json-server, json-graphql-server)
Centralize mock data for Storybook, tests, and mock API.
Pass an object to a function and destructure to create named parameters. Reads more clearly. And can destructure in the function signature too. This keeps the calls in the func short and documents the expected object properties.
Storybook driven development – Build and test each component in isolation. Document each state in a separate story. Then use Percy or Chromatic to snapshot.
You can only write expressions within a return. This limits what you can do in JSX. Options:
Return early. (good for loaders and errors)
Do the most convenient thing. It’ll probably be fast enough. Inline func? Fine. Worried about renders? Don’t be. Worried about context performance? Okay, then maybe you’re misusing context (should rarely change). Sure, perf test (set Chrome perf to 6x), but don’t speculate. Degrade ergonomics after establishing a perf issue.
Remember, a render != DOM change. With virtual DOM, the diff is in-memory. Flow: render -> reconcile -> commit. If the DOM doesn’t change, there’s likely no perf issue. So stop worrying about needless re-renders. React is smart enough to only change the DOM when needed, so it’s typically fast enough.
Don’t slap useMemo, shouldComponentUpdate, PureComponent everywhere. Only where needed. They have overhead because it’s an extra diff. If they were typically faster, they’d be the default!
Keep state as low as you can. Lift when needed.
Avoid state that can be derived. Calc on the fly. Reference objects by id instead of duplicating.
Use _myVar convention to resolve state naming conflicts.
Don’t sync state, derive it. Example, calculate full name on the fly by concatenating firstName and lastName in render. Don’t store fullName separately. Doing so risks out of sync issues and requires extra code to keep it in sync.
State that changes together, should live together. Reducers help. So does grouping via useState. Consider state machines – they describe valid states, which makes invalid state impossible (as new customer with 5 previous purchases, or an admin with no rights shouldn’t be possible. If separate states, they can get outta sync)
Probably don’t need Redux. Lifting state scales nicely and is easy to understand. Prop drilling pain is overblown. Keep prop names the same. Spread props. Pass child. Memoize. Use context and useReducer cover the rare global needs well. Show slides of diff data approaches from my updated Redux course.
Context isn’t just useful for global data. Useful for compound components. Can be useful for performance.
setLoading(false) in finally to assure it’s called
Require all props at first
Destructure props in func signature to shorten calls below. Useful on event handler funcs too. But what about props with dashes in name like aria-label? Well, don’t destructure that by using spread: …otherProps
Make your props as specific as possible
Standardize naming. onX for eventHandler props. handleX for the func.
Centralize your propTypes
Document propTypes via JSDoc style comments = autocomplete and docs in Storybook. Can even use markdown!
Spread props or pass children to reduce the pain of prop drilling
Prop existence conveys truth. So <Input required /> is sufficient.
Honor the native API in your reusable component designs. Pass the full event to event handlers, not merely the value. Then you can use a centralized change handler. Honor the native names (onBlur, onChange, etc). Doing so maximizes flexibility and minimizes the learning curve.
Note: Here I have a completely different approach based on styled-components best practices and inspired by Material UI.
Mix styling approaches.
Inline styles for dynamic styles.
Namespace via CSS modules.
Use plain Sass for global styles.
CSS in JS remains a hard sell – too many horses in the race.
Use classnames to apply multiple styles
Use flexbox and CSS Grid over floating styles
Create abstraction over flexbox to abstract breakpoints for consistency (bootstrap gives ya this)
3 keys to easy reuse
Consider dedicating a person/team to this. Why? Speed. Less decision fatigue. Smaller bundles. Consistency = better UX. Less code = fewer bugs.
Look for repeated code – opportunity for reuse. Every reuse is a perf enhancement.
DRY out your forms by combining custom hooks, context, and reusable components to create an opinionated custom approach that encapsulates your app’s business rules. These tools are the foundation.
Accept both a simple string and an element. Use React.isValidElement to tell which you’re getting.
Create an “as” prop to specify the top-level element.
Create a reusable AppLayout using the slot pattern.
Centralize alerts in AppLayout and provide function for showing the alerts via context.
Gen custom docs via react-docgen
Consider creating separate mobile and desktop components if they differ significantly. Lazy load the relevant size.
Prefer RTL over Enzyme. Simpler API = pit of success. Encourages a11y. Easy to debug. Can use same queries for Cypress.
JSDOM doesn’t render, so can’t test responsive design there. Use Cypress to test responsive design behavior.
Avoid Jest snapshot tests. They’re brittle, they test implementation details, they’re often poorly named, they all fail when a single line changes, and they’re hard to fix when they fail. Instead, prefer Percy or Chromatic to test visuals
Use the scenario selector pattern to run your app against different data. Automate these tests via Cypress/Selenium
Use Cypress testing library so your Cy selectors match your React Testing library selectors = No need to change code to support Cypress tests!
Cypress driven development – TDD for integration testing. Use Cypress to navigate to the spot you need to test. Use cy.only to call a single test. It should fail first. Make it pass.
Consider customizing create-react-app (CRA).
Use react-app-rewired to tweak the config without ejecting
Customize linting rules.
Add webpack-bundle-analyzer. Know what’s in your bundle.
Consider forking at least react scripts. Consider forking CRA. Create a company framework that generates a project with a single dependency: Your react-scripts fork that includes your company’s components, tweaks, dependencies, linting rules, webpack.config, etc.
Use Prettier. Convert in one big commit. You’ll look like the hero!
Lean on ESLint. Use as a teaching tool. Object shorthand. No var. Disallow certain imports (jquery, lodash, moment). Require triple equals. Don’t form a committee. Assign someone you trust and enable a lot of good stuff. Can always back off later. Add plugins like jsx-a11y/recommended.
Require strict propTypes (or TS). I don’t get many type issues. (see link for list)
Use .vsextensions to encourage extensions.
Keep client and server separate. If embedding React in a server-side tech, use Storybook to develop components in isolation.
Consider a monorepo
Why? Rapid feedback. Continuous integration.
CI integration tests projects on every PR
Use Lerna, Bolt, Yarn Workspaces, or even simply a relative file reference to your reusable components to manage. I typically use Lerna.
Have a system for organizing your knowledge. Find a new tool? Technique? Document it. I use GitHub issues here on my reactjsconsulting repo.