With React 18 currently in the works, React Redux 8, the React state management library, is simultaneously being built to incorporate all the new features that React 18 will provide, including server-side rendering (SSR), transitions, and automatic batch rendering.
Another significant change made in React Redux 8 is the conversion of its entire codebase to TypeScript, making it easier for users to write type-safe code and produce bug-free applications.
In this article, we’ll explore how this change affects developers, and since React Redux 8 is currently in beta, we’ll test its compatibility with React 18’s beta version.
The goal here is to see what upgrading the React Redux version in an application does to it — we want to look out for breaking changes and unexpected behavior in the application after the upgrade.
Let’s clone and upgrade this simple to-do application by Jack Herrington to hand-inspect it and highlight the React Redux 8-specific changes that must be made.
Feel free to use one of the example apps from the official React Redux tutorials as an alternative or any of the example CodeSandboxes linked in the React Redux docs. Or, if you have an existing React Redux application that you want to update, you can use that instead.
The to-do application is built with React 17 and Redux 8, so we’ll update to the latest versions of these two packages. To add React Redux 8 to an existing React application, run the following command with npm:
npm i react-redux@next
To upgrade the application’s React version to React 18, run the following command:
npm install react@rc react-dom@rc
React 18 ships with a new root API, createRoot
, and runs updates more efficiently. The old one, ReactDOM.render
, is now deprecated.
To use it, navigate to the index.tsx
file in the src
folder of the application and take the following block of code:
ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
Then replace it with the following:
const root = ReactDOM.createRoot( document.getElementById("root") as HTMLDivElement ); root.render(<App />);
Next, navigate to your tsconfig.json
file and add the following line:
{ "compilerOptions": { ..., "types": ["react/next", "react-dom/next"] }, ... }
Now your app runs the new root API, createRoot
, making the improvements in React 18 accessible to your application. Note that React 18 ships with both the legacy root API and the new root API, so you have the choice of only using the new one if you want the performance improvements it comes with.
However, if you stick to the old API, you will receive the deprecation message recommending the new API. To upgrade React Redux to version 8, run the following command:
npm i react-redux@next
Now the application works with React Redux 8. To run the application and observe breaking changes and unexpected behavior, run the following command:
npm start
Next, navigate to http//localhost:3000
in your browser. Upon inspection, we see that the to-do list application functions properly. Filling the text box and clicking the Add Todo button updates the UI, and clicking the Remove button beside an item deletes it.
Here, there are no breaking changes or unexpected behavior because all the changes made are under the hood with the public APIs remaining the same (connect
and useSelector
).
No new features were added, no TypeScript types were changed, and no change in syntax is required. This version has the exact same functionalities as the previous version; as we’ve seen, the only step required to upgrade your application to React Redux 8 is to bump the package version.
Having confirmed that there are no syntax changes or elaborate steps required with the new React Redux version, let’s zoom in on the changes that the Redux team made internally, as well as the progress with React 18, which is currently in beta.
Previous versions of React Redux (version 7 and below) were written in plain JavaScript. To use TypeScript with those versions, the types must be imported from a different, community-maintained package, @types/react-redux
.
In version 8, though, all the types are imported into the main package, eliminating the need for the @types/react-redux
package. In fact, if you upgrade to React Redux 8 and your app uses the @types/react-redux
package, you must remove that package.
In version 8, React Redux’s two public APIs (connect
and useSelector
) are rewritten to support React 18.
One of the new APIs that comes with React 18 is useSyncExternalStore
. This gives developers access to new features like opt-in support for concurrent rendering and automatic render batching.
React Redux’s connect
and useSelector
have also been reworked to call this new API internally.
Modern JavaScript code produces very small files and runs very fast. However, since legacy browsers like Internet Explorer 11 only supported ES5 and not the later versions, previous versions of React Redux compiled to ES5 to run on Internet Explorer 11.
Now that Internet Explorer 11 is no longer really functional, React Redux compiles to ES2017, takes advantage of some built-in support in modern browsers, and creates bundles that are much smaller in size. If you still need support for legacy browsers, you’ll need to compile your own dependencies.
With the advent of Hooks, React Redux’s connectAdvanced
API became pretty much irrelevant and only added unnecessary complexity to the package. It has since been removed in version 8.
Another legacy code that’s hardly ever used anymore is connect
’s pure
option. As React has grown over the years, the pure
flag, much like the connectAdvanced
API, has become mostly irrelevant. It has also been removed in version 8.
Batching is when multiple state updates trigger at once and their response rendering occurs at the same time. With previous versions of React, this only happened when the trigger is a normal function.
If the multiple state updates are triggered by a promise or a callback, their responses are not rendered at the same time. In React 18, all renders are batched, meaning they’ll occur at the same time, regardless of their trigger. Here’s an example with a regular function:
function App() { const [stateOne, setstateOne] = useState(false); const [stateTwo, setstateTwo] = useState(false); function handleClick() { setstateOne(val => !val); // set the first state setstateTwo(val => !val); // set the second state // In React 18, both states will be changed from false to true at //the same time } return ( <div> <button onClick={handleClick}>Click Me</button> </div> ); }
Here’s an example with a promise:
fetchData() .then(()=> { setstateOne(val => !val); // set the first state setstateTwo(val => !val); // set the second state // In React 18, both states will be changed from false to //true at the same time })
With Suspense
, React 18 makes major performance improvements to SSR by making serving parts of an app asynchronously possible. Server-side rendering allows you to generate HTML texts from the React components served, after which the JavaScript code loads and merges with the HTML (known as hydration).
Now, with Suspense
, you can break your app into small, stand-alone units that can be rendered on their own without the rest of the app, allowing content to be available to your user even much faster than before.
Say you have two components: a text component and an image component. If you stack them on each other like this:
<Text /> <Image />
Then, the server tries to render them at once, slowing the entire page down. If text is more important to your readers, you can give it priority over images by wrapping the Image
component in Suspense
tags:
<Text /> <Suspense fallback={<Spinner />}> <Image /> </Suspense>
This time around, the server serves your text component first, and a spinner displays while your image waits to load.
React apps are interactive, but if you want your app to update itself continuously as users interact with it, this constant updating can greatly slow your app down and provide a poor user experience.
This is where the startTransition
API comes in. With it, you can define which update is urgent and which one is not (secondary). This way, you can prioritize urgent updates over secondary updates, and secondary updates can move to the background or be skipped altogether if they are no longer needed.
Your existing React Redux applications are expected to function properly if you upgrade them. However, if you experience any breaking changes or unexpected behavior, the Redux team is currently taking feedback here to sand out any issues before the final version is released.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
4 Replies to "React 18, React Redux 8, and TypeScript: What you need to know"
Nice summary! One nitpick: It’s not “Redux 8”, it’s “React-Redux 8”. The `redux` core library is still at version 4.1.x and is unrelated to these changes. (If you could update the title and descriptions to say “React-Redux” I’d appreciate it!)
Thank you for the great post. I have a few suggestions.
1. I think Redux 8 is a bit misleading because the latest version of Redux is 4 and 8 is the version of React Redux.
2. Updates in `handleClidk` funciton of “Automatic render batching” have been batched before 18. React has processed updates in event handlers as batched before 18. React 18 is going to apply batching more places other than event handlers and lifecycle methods.
Thank you!
Hi Koba04, glad you loved the post! I believe this part of the article covered your second point:
“With previous versions of React, this only happened when the trigger is a normal function.
If the multiple state updates are triggered by a promise or a callback, their responses are not rendered at the same time. In React 18, all renders are batched, meaning they’ll occur at the same time, regardless of their trigger.”
So multiple state updates in a regular event handler function are batched in React 17. But if the state updates are in a promise, they don’t get batched.
However, in React 18, all state updates – whether in an event handler function or a promise – get batched automatically.
Perhaps, I could have worded it better.
Thanks for the feedback!
Did you face any error like below when you tried to upgrade to react-redux-8 ? This error is coming when I try to load my application. I am using React @18, latest typescript and react-redux@8. This issue is not observed when I use React @18, latest typescript and react-redux@7.
Uncaught TypeError: Cannot read properties of undefined (reading ‘call’)
at __webpack_require__ (bundle-285f38a0450f604bbf91.js:1:7899911)
at l (bundle-285f38a0450f604bbf91.js:1:7904162)
at eval (6172:22:22)
at 6172 (bundle-285f38a0450f604bbf91.js:1:6911952)
at __webpack_require__ (bundle-285f38a0450f604bbf91.js:1:7899911)
at l (bundle-285f38a0450f604bbf91.js:1:7904162)
at eval (5656:144:34)
at 5656 (bundle-285f38a0450f604bbf91.js:1:6941346)
at __webpack_require__ (bundle-285f38a0450f604bbf91.js:1:7899911)
at l (bundle-285f38a0450f604bbf91.js:1:7904162)
__webpack_require__ @ bundle-285f38a0450f604bbf91.js:1
l @ bundle-285f38a0450f604bbf91.js:1
eval @ 6172:22
6172 @ bundle-285f38a0450f604bbf91.js:1
__webpack_require__ @ bundle-285f38a0450f604bbf91.js:1
l @ bundle-285f38a0450f604bbf91.js:1
eval @ 5656:144
5656 @ bundle-285f38a0450f604bbf91.js:1
__webpack_require__ @ bundle-285f38a0450f604bbf91.js:1
l @ bundle-285f38a0450f604bbf91.js:1
eval @ 7866:12
7866 @ bundle-285f38a0450f604bbf91.js:1
__webpack_require__ @ bundle-285f38a0450f604bbf91.js:1
(anonymous) @ bundle-285f38a0450f604bbf91.js:1
(anonymous) @ bundle-285f38a0450f604bbf91.js:1
client.js:96