React

Terms

  • trigger: A prop you pass in whose only purpose is to change when some functionality needs to be triggered
    • Parallel to cache busting in URLs

Reference

Key Principles

The nine best recommendations in the new React docs

React State

STATE UPDATES ARE ASYNCHRONOUS AND MAY BE BATCHED

https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

React may batch multiple setState() calls into a single update for performance.

Or, may not. Described in the context of using a function for setState (applies to hooks too?)

https://reactjs.org/docs/react-component.html#setstate

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.

https://stackoverflow.com/questions/48563650/does-react-keep-the-order-for-state-updates/48610973#48610973

Currently (React 16 and earlier), only updates inside React event handlers are batched by default. There is an unstable API to force batching outside of event handlers for rare cases when you need it.

In future versions (probably React 17 and later), React will batch all updates by default so you won’t have to think about this. As always, we will announce any changes about this on the React blog and in the release notes.

no matter how many setState() calls in how many components you do inside a React event handler, they will produce only a single re-render at the end of the event.

useEffect

https://reactjs.org/docs/hooks-effect.html

Why should you specify dependencies when you only need it to run once?

Probably:

  • It’s not the right mental model
  • It risks bad things happening
  • Even in cases where it works fine either way,

Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects.

So they do explicitly list data fetching, even first here. But it’s not the main consideration in the rest of their docs.

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

But I think I read later you should not think of it as a one-to-one mapping.

In React class components, the render method itself shouldn’t cause side effects. It would be too early — we typically want to perform our effects after React has updated the DOM.

This is not mainly considering network requests.

Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”.

This leads us to ask “which renders do I want this to happen after?”

React guarantees the DOM has been updated by the time it runs the effects.

This is why flicker can happen.

Unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen.

This is one reason it’s best not to think of it as identical to them.

Earlier, we looked at how to express side effects that don’t require any cleanup. However, some effects do. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we don’t introduce a memory leak!

Another reason is you might not want to have duplicate things running, like duplicate intervals.

We’ll continue this page with an in-depth look at some aspects of useEffect that experienced React users will likely be curious about. Don’t feel obligated to dig into them now.

Still baffling to me that they don’t consider the dependency array a core part of the API.

This behavior ensures consistency by default and prevents bugs that are common in class components due to missing update logic.

You can tell React to skip applying an effect if certain values haven’t changed between re-renders.

Effects happen on every render, but you can skip some if values haven’t changed.

If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect. Otherwise, your code will reference stale values from previous renders.

This is from the mental model of “effects run on every render, skipping them is only a performance optimization.” So it doesn’t directly relate to the approach “I have a requirement to only run this a certain number of times.

If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.

So if you want to tell React “this does depend on values from props or state, but I still don’t want it to re-run when they change”, that is not the React mental model.

While passing [] as the second argument is closer to the familiar componentDidMount and componentWillUnmount mental model, there are usually better solutions to avoid re-running effects too often.

  • https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
    • It’s not safe to omit functions, because they can use props
    • “Safe” here means “do we ensure it runs every time they change”, which may not be what you want if you aren’t the author of the React docs
    • “If you specify a list of dependencies as the last argument to useEffect, useLayoutEffect, useMemo, useCallback, or useImperativeHandle, it must include all values that are used inside the callback and participate in the React data flow. That includes props, state, and anything derived from them.”
      • Must to get the kind of behavior React recommends, or to have React work at all?
      • With the ambiguity, it’s better to go with the React way
  • https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often
    • You need the effect to rerun upon renders, because otherwise it’s captured variables in scope that have changed. But, again, if it’s not running every time, it’s fine
    • But that assumption could lead to issues if someone changes that effect in the future
    • The reason to stick with the convention is so the effect works like an effect

As a last resort, if you want something like this in a class, you can use a ref to hold a mutable variable. Then you can write and read to it.

So a ref is not just for storing DOM elements.

So if you say “I only want this to run once, so I want to pass the empty array, but the eslint rule is making me pass the props I use,” this is not the useEffect mental model. Instead, think:

  • Effects run on every render
  • We can opt in to skipping when the values we use are the same. In general it’s not safe to skip more than that. The lint rule enforces that
  • The values I use are X and Y. These values should not change over the lifetime of the component
  • Therefore, specifying these as the dependency should be correct

I wish the useEffect API had been designed differently, but since it’s the React way, it’s better for us to use and learn it in the conventional way, than to do our own thing. And it may be there are benefits to their way that I don’t realize.

Another way to think about it is, the dependency array also indicates what values your effect depends on. If you omit them, that is communicating something incorrect to the reader and to React. That could cause any number of problems in the future.

So, why leave the ESLint rule on when turning it off and specifying no dependencies would work?

  • Getting into the habit of turning that rule off can mask other cases where leaving a dependency out does cause a bug now
  • Turning that rule off communicates something incorrect to developers and to React, which could cause issues in the future
  • Turning that rule off prevents you from learning the useEffect mindset more deeply, and learning it will pay off

Production React

  • Husky hooks
    • Pre-commit: lint-staged
      • “*.js”: “eslint –fix”
      • Will errors make it fail? Will warnings? With CRA there are a lot of warnings
    • Pre-push: run unit tests
      • If they fail, you can just do no-verify before pushing. This should be rare
  • GitHub
    • CODEOWNERS file to assign/alert for code review
    • pull_request_template.yml
  • Analytics
    • GA: react-ga
    • Routing: react-router-dom
      • history module, createBrowserHistory
      • history.listen()
  • Base path
    • package.json - homepage property
    • Jest: --testURL=http://example.tld/myPath
    • react-router-dom, history module, createBrowserHistory, basename attribute
  • Dependency injection and test overrides
  • Version number
    • In start and build, REACT_APP_VERSION=$npm_package_version
    • %REACT_APP_VERSION% in index.html
  • Minimal feature flag design (save file)
  • SVG
    • Just import them
    • To color them, in the SVG set fill=”currentColor”
  • Bundle analysis and size checks
    • bundlesize package
    • source-map-explorer
      • Run on main branch and PR branch, compare them
    • Commit reason for increase each time, for history
  • SEO
    • react-helmet for head tags
  • A11y
    • Reference
      • WCAG
    • Tools
      • eslint-plugin-jsx-a11y
      • @axe-core/react
      • SiteImprove
      • Keyboard testing
      • VoiceOver testing
    • VoiceOver
      • role=group
      • role=list and role=listitem
      • aria-hidden
      • aria-label
      • aria-labelledby
  • Performance
    • Google PageInsights
  • Persistence
    • whitelist/blacklist to prevent some transient/secure things from being persisted
  • Route guards
    • Can useEffect to check for some data and history.push if not
    • Render children (maybe not when need to redirect)
  • Bug tracking, Analytics
    • Disable in dev, unit test, and E2E test
  • JavaScript window size tracking
    • useState to only store the minimal value, not full pixel width
    • setting the same value won’t cause a rerender
  • Dependencies
    • Document rationale in a file (would be comments if package.json supported it)
  • Favicons and App Icons
  • Sharing
    • Test slack, messages, Twitter, Facebook
    • Twitter Cards to improve look

ESLint Rule Overrides

curly: ['warn', 'all'],
'import/no-anonymous-default-export': 'off',
'import/order': ['warn', {alphabetize: {order: 'asc'}}], // group and then alphabetize lines - https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/order.md
'sort-imports': ['warn', {ignoreDeclarationSort: true, ignoreMemberSort: false}], // alphabetize named imports - https://eslint.org/docs/rules/sort-imports
'jest/no-focused-tests': 'warn',
'jest/no-identical-title': 'warn',
'jest/no-standalone-expect': 'warn',
'jest/no-test-prefixes': 'warn',
'jest/prefer-called-with': 'warn',
'jest/valid-expect-in-promise': 'warn',
// jsx-a11y exceptions/disabling not respected by react-scripts; disable by file instead
'no-duplicate-imports': 'warn', // https://eslint.org/docs/rules/no-duplicate-imports
'no-param-reassign': ['warn', { props: true }],
'no-shadow': 'warn',
'prefer-const': 'warn',
'prettier/prettier': 'warn',
'react/default-props-match-prop-types': 'warn',
'react/jsx-uses-react': 'off', // indicates that a use of JSX counts as using the React import; only needed if no-unused-vars is used; https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
'react/jsx-key': 'warn',
'react/no-unused-prop-types': 'warn',
'react/prop-types': 'warn',
'react/react-in-jsx-scope': 'off', // requires an explicit React import; https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
'react/require-default-props': 'warn',

JSX and React Import

  • Previously an explicit import React from 'react' was required in any file using JSX
  • This was changed with the “new JSX transform”, added in React 17
  • With the new transform, a special function import is performed by the compiler; this is why the import is not needed
  • It also enables more optimizations
  • The compiler you use must support the new JSX transform. Supported by:
    • CRA 4.x+
    • Next 9.5.3+
    • TypeScript 4.1+
  • With the new transform, the eslint rules react/jsx-uses-react and react/react-in-jsx-scope aren’t needed (and are misleading) and can be turned off
  • React Native
    • CLI
      • As of 2022-08-18 (RN 0.69.4), an app is generated with a React import. The file can be replaced with a simple file without the React import; ESLint will fail but the app runs. But if you use SafeAreaView, for some reason it says you need the React import
      • Disabling the two ESLint rules works
    • Expo
      • As of 2022-08-18 (Expo 46.0.7), an app is generated with no React import. But ESLint is not enabled by default

Sanitization

  • dompurify
  • Make a reusable component that sanitizes then passes to dangerouslySetInnerHTML