Skip to content
Joey Wang
Menu

Search

Full Stack Development

Upgrading React 16 to 17: Dependencies, Jest, and ESLint Fixes

A practical walkthrough of upgrading a React 16 app to React 17, covering dependency updates, Jest and Enzyme test fixes, and new ESLint warnings.

· 2 min read

engineering #react#javascript#testing#debugging

Audio summary

React 17 doesn’t add much on its own; it exists to make React 18’s concurrent rendering possible without forcing every app to jump straight there. Upgrading is mostly dependency bumps and test cleanup, but a few of the failures are non-obvious enough to be worth writing down.

Updating dependencies

-   "react": "^16.9.0",
+   "react": "^17.0.2",

-   "react-dom": "^16.9.0",
+   "react-dom": "^17.0.2",

-   "react-scripts": "^3.2.0",
+   "react-scripts": "^5.0.1",

-   "jest-fetch-mock": "^2.1.1",
+   "jest-fetch-mock": "^3.0.0",

+   "@babel/plugin-proposal-private-property-in-object": "^7.21.11",

-   "enzyme-adapter-react-16": "^1.14.0",
+   "@wojtekmaj/enzyme-adapter-react-17": "^0.4.1",

-   "enzyme": "^3.10.0",
+   "enzyme": "^3.11.0",

Run npm install (or yarn install) after updating package.json.

Fixing tests

beforeAll to beforeEach

Tests that shared setup across cases via beforeAll need beforeEach instead, or state from one test leaks into the next:

    describe('index edge', () => {
-     beforeAll(() => {
+     beforeEach(() => {

Jest 24 to 27

jest.spyOn no longer calls the original implementation by default; you have to mock it explicitly:

-     jest.spyOn(_, 'shuffle') # call original but not with 27
+     jest.spyOn(_, 'shuffle').mockImplementation((items) => {
+       return items.reverse()
+     })

_.once needs mocking in test setup, or the “only runs once” behavior bleeds across tests that expect a fresh call each time:

+import _ from 'underscore';
+
+jest.mock('underscore', () => ({
+  ...jest.requireActual('underscore'),
+  once: jest.fn(fn => fn) // Replace _.once with a passthrough function
+}));

Enabling Jest fetch mocks

setupTests.js needs the new import and an explicit enable call:

# setupTests.js
-import fetchMock from 'jest-fetch-mock'
+import { enableFetchMocks } from 'jest-fetch-mock'

-global.fetch = fetchMock
+enableFetchMocks()

Mocked responses now go through doMock():

     beforeEach((done) => {
-      fetch.mockResponseOnce(response)
+      fetch.doMock().mockResponseOnce(response)
       _request(dispatch).then(done)
     })

Fixing ESLint warnings

Add this rule to .eslintrc to catch anonymous default exports:

{
  "rules": {
    "import/no-anonymous-default-export": ["error", {
      "allowArray": false,
      "allowArrowFunction": false,
      "allowAnonymousClass": false,
      "allowAnonymousFunction": false,
      "allowCallExpression": false,
      "allowNew": false,
      "allowLiteral": false,
      "allowObject": true
    }]
  }
}

To autofix what ESLint can fix:

npx eslint --fix --ext .js,.jsx src|grep .js> files; vim `cat files|sort|tr '\n' ' '`

Replace anonymous default exports with named ones:

-export default {
+const actions = {
   get: getConfiguration,
 }
+
+export default actions

Clean up imports that are now unused:

-import { CORRECT, INCORRECT } from '../../../../../constants'
+import { CORRECT } from '../../../../../constants'

The pattern

Most of what breaks on a 16-to-17 bump isn’t React itself, it’s Jest and Enzyme catching up to it: beforeAll sharing state where you meant beforeEach, spyOn losing its automatic passthrough, and fetch mocks needing an explicit enableFetchMocks() call. None of it is hard to fix once you know to expect it; the point of writing it down is not having to rediscover it project by project.