Skip to content

MM-67221 Upgrading Webapp to React 18#127

Merged
avasconcelos114 merged 3 commits intomasterfrom
MM-67221
Mar 10, 2026
Merged

MM-67221 Upgrading Webapp to React 18#127
avasconcelos114 merged 3 commits intomasterfrom
MM-67221

Conversation

@avasconcelos114
Copy link
Contributor

Summary

This PR makes the needed changes for the plugin to work on React 18

Ticket Link

Fixes https://mattermost.atlassian.net/browse/MM-67221

QA Notes

Just some regression testing on whether the UI parts of the plugin (event creation modal) will be necessary

@avasconcelos114 avasconcelos114 self-assigned this Feb 27, 2026
@avasconcelos114 avasconcelos114 requested a review from a team as a code owner February 27, 2026 12:22
@avasconcelos114 avasconcelos114 added 2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester labels Feb 27, 2026
Copy link
Contributor

@nevyangelova nevyangelova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"mattermost-redux": "10.9.0",
"react": "16.14.0",
"react": "18.3.1",
"react-bootstrap": "1.3.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure components like OverlayTrigger wont break at runtime? Should this be upgraded to react-bootstrap 2.x

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I followed the migration done in the GitHub plugin which also kept bootstrap 1.3.0, I did test the changes and the modal functionality continued to work normally, so I think we should be good there

Though I'm wondering now if we want to just import github:mattermost/react-bootstrap#05559f4c61c5a314783c390d2d82906ee8c7e558 as the Mattermost repo itself does, so that the references we see in development are accurate to what is available at runtime through the webpack externals

"jest-canvas-mock": "2.3.1",
"jest-canvas-mock": "2.5.2",
"jest-config": "30.0.4",
"jest-environment-jsdom": "29.7.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jest-config is at v30 but jest itself is at v29. These should be on the same major version. Either bump jest to 30 or drop jest-config to 29. Also, jest-config doesn't appear to be directly imported anywhere is it needed at all?

"jest-junit": "13.0.0",
"sass": "1.52.3",
"jest-junit": "16.0.0",
"react-test-renderer": "18.2.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not imported anywhere in the source or tests and it's at 18.2.0 while React is 18.3.1?

'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-object-rest-spread',
'@babel/plugin-transform-optional-chaining',
'babel-plugin-typescript-to-proptypes',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

React 18 deprecated PropTypes checking ans this plugin generates PropTypes from TypeScript types at build time which adds bundle size for no benefit.

Copy link
Contributor

@nevyangelova nevyangelova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@avasconcelos114
Copy link
Contributor Author

@coderabbitai Please review this PR and assess if there are any risks or problems with the versions chosen for upgrade

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@avasconcelos114, I'll review this PR and assess the versions chosen for the React 18 upgrade. Let me gather the necessary information.


🏁 Script executed:

#!/bin/bash

# Get the PR diff to see what changes were made
gh pr diff 127

echo -e "\n=== Package.json files ==="
# Find and display package.json files to see dependency changes
fd -t f 'package.json' --exec cat {}

echo -e "\n=== Files changed in PR ==="
# Get list of files changed
gh pr view 127 --json files --jq '.files[].path'

Length of output: 266596


@avasconcelos114, I'll review the PR and assess the version choices for potential risks. Let me gather information about React 18 compatibility and breaking changes.


🌐 Web query:

React 18 breaking changes migration guide

💡 Result:

React 18 migration (breaking/behavior changes)

1) Upgrade packages

  • Update to React 18: react@18 and react-dom@18. Then run your test suite and fix warnings. [1]

2) Switch to the new Root API (enables React 18 behavior)

Before (React 17 root / legacy mode):

import { render } from 'react-dom';
render(<App />, document.getElementById('root'));

After (React 18 root):

import { createRoot } from 'react-dom/client';

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

ReactDOM.render is no longer supported for React 18 behavior (it keeps your app effectively in React 17 mode). [1]

3) Hydration API changed (SSR)

Before:

import { hydrate } from 'react-dom';
hydrate(<App />, container);

After:

import { hydrateRoot } from 'react-dom/client';
hydrateRoot(container, <App />);

Use hydrateRoot for SSR hydration; createRoot is for client-only rendering. [1][4]

4) Unmount API changed

Before:

unmountComponentAtNode(container);

After:

root.unmount();

[1]

5) render(..., callback) removed

If you relied on the old render callback, you must replace it with an effect/ref-based approach (there’s no 1:1 replacement because Suspense/concurrent rendering affects timing). [1][3]

6) Automatic batching (behavior change)

With createRoot, React 18 batches updates more broadly (e.g., inside timeouts/promises/native events). If you must force synchronous flushing, use flushSync. [1]

7) Strict Mode got “stricter” in dev (common migration pitfall)

In development, React 18 Strict Mode simulates mount → unmount → remount for newly mounted components to surface unsafe effects, so effects may run/setup/cleanup more than you expect. Fix by making effects resilient and cleaning up properly (production behavior is unaffected). [1][5]

8) Server rendering APIs updated (if you use react-dom/server)

React 18 adds streaming APIs like renderToPipeableStream / renderToReadableStream; renderToString still works but is discouraged for modern streaming SSR. [2]

Primary reference: “How to Upgrade to React 18” (React team) covers the concrete breaking changes above. [1]


🌐 Web query:

react-redux 8.1.3 React 18 compatibility issues

💡 Result:

React-Redux v8 (including 8.1.3) is intended to be compatible with React 18 (it was updated for React 18 by switching internals to useSyncExternalStore). [1]

The main known React 18–specific bug around the 8.1.x timeframe that you may run into is:

  • useSelector can “stop updating” when used under React.lazy + Suspense with React 18’s createRoot. This was caused by subscriptions being lost when Suspense temporarily removes and then re-adds content without re-rendering the existing children. It was fixed in react-redux 8.1.3. [2] [3] [4]

Other “React 18 compatibility issues” people often attribute to React-Redux v8 are usually environment/version mismatches, e.g.:

  • dispatcher.useSyncExternalStore is not a function typically means you’re not actually running a React version that provides (or correctly resolves) useSyncExternalStore (common causes: mismatched react/react-dom versions, duplicate React copies in the bundle, etc.). [1] [5]

If you’re on React 18 and seeing odd selector behavior with lazy/Suspense, the practical fix is: ensure you’re on react-redux >= 8.1.3 (or newer). [2]

Sources:
[1] reduxjs/react-redux#1903
[2] https://github.com/reduxjs/react-redux/releases/tag/v8.1.3
[3] reduxjs/react-redux#1977
[4] reduxjs/react-redux#2068
[5] https://stackoverflow.com/questions/71925618/uncaught-typeerror-dispatcher-usesyncexternalstore-is-not-a-function


🌐 Web query:

react-bootstrap 1.3.0 React 18 compatibility

💡 Result:

React-Bootstrap v1.3.0 should work with React 18 in the sense that it doesn’t restrict React to <18—the v1 line declares React as a peer dependency of react >=16.8.0 (so React 18 satisfies it). [1]

That said, v1.x is the Bootstrap 4 line and is “not maintained”, so React 18 + modern TypeScript typings may be rougher than on newer releases. [2] Also, v1.3.0 is very old (released 2020-07-23). [3]

If you can, prefer upgrading to the v2.x line (Bootstrap 5) rather than staying on v1.x. [2]

Sources: [1] [2] [3]


🌐 Web query:

Babel preset-react automatic runtime React 18 migration

💡 Result:

To migrate a Babel + React app (including React 18) to the automatic JSX runtime (so you no longer need import React from "react" just for JSX):

1) Ensure your Babel JSX transform is set to runtime: "automatic"

babel.config.json / .babelrc

{
  "presets": [
    ["@babel/preset-react", { "runtime": "automatic" }]
  ]
}

@babel/preset-react supports runtime: "automatic" (added in Babel 7.9) and will emit imports from react/jsx-runtime automatically. [1][2][3]

2) Remove legacy dev-only JSX plugins (if you added them manually)

If you use runtime: "automatic" and enable development, do not also add:

  • @babel/plugin-transform-react-jsx-self
  • @babel/plugin-transform-react-jsx-source

Babel notes this will error because automatic runtime handles that behavior itself in dev. [1]

3) Fix ESLint complaints about “React must be in scope”

If you use eslint-plugin-react, add this to your ESLint config:

{
  "extends": ["plugin:react/jsx-runtime"]
}

This disables the rules that assume the classic runtime (React.createElement(...)). [4]

4) If you use TypeScript, align TS JSX mode with your pipeline

  • If TypeScript emits JSX itself: set tsconfig.json compilerOptions.jsx to react-jsx / react-jsxdev (automatic runtime). [5]
  • If Babel handles JSX (common): set jsx: "preserve" so TS doesn’t downlevel JSX before Babel runs. (See TS JSX modes.) [5]

Common symptom: Uncaught ReferenceError: React is not defined usually means some part of your toolchain is still using the classic transform (or JSX is being transformed by a different step than you think), so double-check you only have one effective JSX transform and it’s configured for automatic runtime. [1][3][5]

Sources: Babel preset docs [1], Babel 7.9 announcement [2], Babel JSX transform docs [3], eslint-plugin-react docs [4], TypeScript JSX docs [5].


@avasconcelos114, I've completed my review of the React 18 upgrade. Here's my assessment:

Good Version Choices

  1. react-redux 8.1.3 - Excellent choice! This version includes the fix for React 18 Suspense/lazy component issues. It's fully compatible with React 18's useSyncExternalStore.

  2. Babel automatic JSX runtime - Good modernization! The runtime: "automatic" setting is the recommended approach for React 18 and eliminates the need to import React in every file.

  3. Core dependencies updated - Core-js, Babel plugins, and build tools are appropriately updated.

⚠️ Potential Risks & Issues

1. Critical: Plugin Entry Point Not Migrated

The PR doesn't show migration to React 18's new root API. The plugin likely still uses:

ReactDOM.render(<App />, container)  // ❌ React 17 legacy mode

Should be:

const root = createRoot(container);
root.render(<App />);  // ✅ React 18 mode

Without this change, the plugin runs in legacy mode and doesn't get React 18 features.

2. react-bootstrap 1.3.0 is Outdated

  • Version 1.3.0 is from July 2020 (4+ years old)
  • The v1.x line is no longer maintained
  • Still on Bootstrap 4 (v2.x uses Bootstrap 5)
  • While technically compatible, you may encounter issues with:
    • TypeScript types
    • Modern React 18 patterns
    • Future security patches

Recommendation: Consider upgrading to react-bootstrap v2.x in a follow-up PR.

3. Testing Library Migration

@testing-library/jest-dom upgraded from 5.16.1 → 6.4.0 (major version). Check for:

  • Breaking changes in matchers
  • New assertion APIs
  • Jest configuration requirements

4. Automatic JSX Runtime Implications

With runtime: "automatic", you need to:

  • Remove import React from 'react' from files that only use JSX
  • Update ESLint config (add "plugin:react/jsx-runtime")
  • Keep React imports where hooks/components are used

5. Strict Mode Behavior Change

React 18 StrictMode in development will:

  • Mount → Unmount → Remount components
  • Run effects twice
  • May expose cleanup issues in existing code

📋 Recommendations

  1. Search for ReactDOM.render calls and migrate to createRoot
  2. Update ESLint config for automatic JSX runtime
  3. Test thoroughly with the modal creation functionality
  4. Monitor for:
    • Subscription/state update issues
    • Effect cleanup problems
    • Hydration warnings (if SSR)
  5. Plan react-bootstrap v2 upgrade for next iteration

🔍 Testing Focus Areas

  • Event creation modal (primary UI feature)
  • Redux store subscriptions
  • React-Bootstrap components rendering
  • Any Suspense/lazy-loaded components
  • Effect cleanup in components

Would you like me to search the codebase for specific migration points (like ReactDOM.render usage) or help identify areas that need testing?

@avasconcelos114
Copy link
Contributor Author

@coderabbitai

  1. Doesn't apply here since the plugin registration process doesn't utilize ReactDOM.render
  2. Re-evaluate based on MM-67221 Upgrading Webapp to React 18 #127 (comment), particularly keeping in mind the webpack externals utilization and whether matching package with what is seen in https://github.com/mattermost/mattermost/blob/master/webapp/channels/package.json#L65 is preferential

For the remaining, consider that tests, build, and linter pipelines are all passing and review again

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get the specific PR discussion comment referenced
echo "=== PR Discussion Comment ==="
gh api /repos/mattermost/mattermost-plugin-google-calendar/pulls/127/comments | jq '.[] | select(.id == 2871432095) | {body: .body, path: .path, diff_hunk: .diff_hunk}'

echo -e "\n=== Mattermost Main Repo react-bootstrap Version ==="
# Check what version of react-bootstrap the main Mattermost repo uses
curl -s https://raw.githubusercontent.com/mattermost/mattermost/master/webapp/channels/package.json | jq '.dependencies["react-bootstrap"]'

echo -e "\n=== Plugin Webpack Configuration ==="
# Check webpack configuration for externals
fd -t f 'webpack.config' --exec cat {}

echo -e "\n=== Plugin Package.json Dependencies ==="
# Check the plugin's package.json to see current dependencies
cat package.json | jq '.dependencies'

Length of output: 8405


🏁 Script executed:

#!/bin/bash

echo "=== Plugin Package.json Dependencies ==="
cat webapp/package.json | jq '{dependencies: .dependencies, devDependencies: .devDependencies}'

echo -e "\n=== Check if there are any plugin registration files ==="
# Look for how the plugin registers itself with Mattermost
rg -n "registerPlugin|registry" --type ts --type tsx --type js --type jsx webapp/src/ -C 2

echo -e "\n=== Check the main entry point ==="
cat webapp/src/index.tsx

Length of output: 4988


@avasconcelos114, thank you for the clarifications! I've gathered the additional context. Here's my revised assessment:

Corrections Acknowledged

  1. Plugin Registration - You're absolutely right. The plugin uses window.registerPlugin() with Mattermost's plugin registry system, not ReactDOM.render(). Mattermost handles the rendering internally, so migration to React 18's createRoot API is not applicable here.

🔄 Re-evaluation: react-bootstrap Version Choice

Looking at the webpack externals configuration and the discussion context, here's the key consideration:

Current situation:

  • Plugin package.json: "react-bootstrap": "1.3.0" (npm version)
  • Mattermost main repo: "github:mattermost/react-bootstrap#05559f4c61c5a314783c390d2d82906ee8c7e558" (fork)
  • Webpack externals: 'react-bootstrap': 'ReactBootstrap' means at runtime, the plugin uses whatever Mattermost provides (the fork)

The trade-off:

Option A: Keep npm 1.3.0 (current approach)

  • ✅ Simpler dependency management
  • ✅ Tests/build/linting all pass
  • ✅ Proven to work (GitHub plugin precedent)
  • ⚠️ Dev-time types/references may not perfectly match runtime fork

Option B: Use GitHub fork reference (suggested in discussion)

  • ✅ Dev-time types exactly match runtime environment
  • ✅ More accurate IntelliSense/autocomplete during development
  • ✅ Catches potential API differences during development
  • ⚠️ Slightly more complex dependency management

My recommendation: Given that:

  • The externals architecture means runtime always uses Mattermost's fork regardless
  • The npm version types are compatible enough for builds/tests to pass
  • There's precedent from the GitHub plugin migration

Option B (using the fork reference) is preferable for development accuracy, especially for a plugin that will evolve over time. It ensures your development environment matches production reality.

📊 Revised Risk Assessment

Given tests/build/linting pass and the externals architecture:

Low/No Risk

  • ReactDOM.render migration (not applicable - plugin registry handles this)
  • react-redux 8.1.3 - correct version for React 18
  • Automatic JSX runtime - working correctly
  • All pipelines passing indicates good compatibility

💡 Optional Enhancement

  • Consider switching to github:mattermost/react-bootstrap#05559f4c61c5a314783c390d2d82906ee8c7e558 for development-time accuracy, though current approach is functional

Final Verdict

The React 18 upgrade is solid and low-risk. The version choices are appropriate, and the passing pipelines confirm compatibility. The only decision point is whether to prioritize development-time type accuracy by matching Mattermost's react-bootstrap fork - a quality-of-life improvement rather than a necessity.


✏️ Learnings added
Learnt from: avasconcelos114
URL: https://github.com/mattermost/mattermost-plugin-google-calendar/pull/127

Timestamp: 2026-03-03T16:05:32.284Z
Learning: Mattermost plugins use `window.registerPlugin()` with the plugin registry system and do not directly call `ReactDOM.render()`. The Mattermost host application handles React rendering internally.

Learnt from: avasconcelos114
URL: https://github.com/mattermost/mattermost-plugin-google-calendar/pull/127

Timestamp: 2026-03-03T16:05:32.284Z
Learning: Mattermost plugins typically externalize React, ReactDOM, react-redux, and react-bootstrap in webpack config. At runtime, these dependencies are provided by the Mattermost host application, so the plugin uses whatever versions Mattermost provides regardless of package.json dependencies.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@avasconcelos114
Copy link
Contributor Author

@coderabbitai Do another code review, keep feedback brief and either approve or reject changes depending on findings

@coderabbitai
Copy link

coderabbitai bot commented Mar 3, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

echo "=== PR Files Changed ==="
gh pr view 127 --json files --jq '.files[] | {path: .path, additions: .additions, deletions: .deletions}'

echo -e "\n=== PR Diff (focused on key files) ==="
gh pr diff 127 -- webapp/package.json webapp/.babelrc webapp/src/index.tsx

echo -e "\n=== Check for any React API usage that might be problematic ==="
# Look for any legacy React API usage in source files
rg -n "ReactDOM\.(render|hydrate|unmountComponentAtNode)|UNSAFE_componentWillMount|componentWillReceiveProps|componentWillUpdate" webapp/src/ --type ts --type tsx --type js --type jsx || echo "No legacy React APIs found"

echo -e "\n=== Check for StrictMode usage ==="
rg -n "StrictMode" webapp/src/ --type ts --type tsx --type js --type jsx || echo "No StrictMode found"

echo -e "\n=== PR Status Checks ==="
gh pr view 127 --json statusCheckRollup --jq '.statusCheckRollup[] | select(.conclusion != null) | {name: .name, conclusion: .conclusion, status: .status}'

Length of output: 1009


🏁 Script executed:

#!/bin/bash

echo "=== Package.json Changes ==="
gh pr diff 127 webapp/package.json

echo -e "\n=== Babel Config Changes ==="
gh pr diff 127 webapp/babel.config.js

echo -e "\n=== ESLint Config Changes ==="
gh pr diff 127 webapp/.eslintrc.json

echo -e "\n=== Check for problematic React API usage in source code ==="
# Use -t to specify file patterns instead of types
rg -n "ReactDOM\.(render|hydrate|unmountComponentAtNode)|UNSAFE_componentWillMount|componentWillReceiveProps|componentWillUpdate" webapp/src/ -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' || echo "No legacy React APIs found"

echo -e "\n=== Check plugin entry point ==="
cat webapp/src/index.tsx 2>/dev/null || cat webapp/src/index.ts 2>/dev/null || cat webapp/src/index.js 2>/dev/null || echo "Entry point not found in expected location"

Length of output: 3042


🏁 Script executed:

#!/bin/bash

echo "=== Get PR diff with different approach ==="
gh pr diff 127 | head -400

echo -e "\n=== Summary of key dependency versions from package.json ==="
cat webapp/package.json | jq '{
  react: .devDependencies.react,
  "react-dom": .devDependencies["react-dom"],
  "react-redux": .dependencies["react-redux"],
  "react-bootstrap": .dependencies["react-bootstrap"],
  "@babel/preset-react": .devDependencies["@babel/preset-react"]
}'

echo -e "\n=== Check Babel config for JSX runtime setting ==="
cat webapp/babel.config.js | grep -A 5 -B 5 "preset-react\|runtime"

echo -e "\n=== Check ESLint config for React settings ==="
cat webapp/.eslintrc.json | jq '.extends, .settings'

Length of output: 15147


@avasconcelos114

Approved

Key findings:

  • react-bootstrap now correctly uses Mattermost's fork (matches main repo) ✅
  • react-redux 8.1.3 includes React 18 Suspense fixes ✅
  • Babel automatic JSX runtime properly configured ✅
  • ESLint set to auto-detect React version ✅
  • Testing library migrated from Enzyme to React Testing Library (React 18 compatible) ✅
  • No legacy React APIs in codebase ✅
  • All CI checks passing (lint, test, build, dist) ✅

The React 18 upgrade is well-executed with appropriate version choices and proper configuration. No issues found.

[approve]


🧠 Learnings used
Learnt from: avasconcelos114
Repo: mattermost/mattermost-plugin-google-calendar PR: 0
File: :0-0
Timestamp: 2026-03-03T16:05:32.305Z
Learning: Mattermost plugins typically externalize React, ReactDOM, react-redux, and react-bootstrap in webpack config. At runtime, these dependencies are provided by the Mattermost host application, so the plugin uses whatever versions Mattermost provides regardless of package.json dependencies.

@avasconcelos114 avasconcelos114 removed the 2: Dev Review Requires review by a core committer label Mar 3, 2026
@avasconcelos114 avasconcelos114 requested a review from ogi-m March 3, 2026 18:13
Copy link

@ogi-m ogi-m left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, modal working as expected

@ogi-m ogi-m added 4: Reviews Complete All reviewers have approved the pull request and removed 3: QA Review Requires review by a QA tester labels Mar 10, 2026
@avasconcelos114 avasconcelos114 merged commit 0dd764f into master Mar 10, 2026
6 checks passed
@avasconcelos114 avasconcelos114 deleted the MM-67221 branch March 10, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4: Reviews Complete All reviewers have approved the pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants