React 18 new features
1) Concurrent React and suspense feature
Concurrent React is a set of features and improvements introduced in React 18 to improve the performance and user experience of React applications.
Concurrent React allows React to handle multiple tasks simultaneously without blocking the main thread, which can lead to a more responsive and smooth user experience. This is achieved through two main features:
Concurrent rendering: With concurrent rendering, React can work on multiple components and their updates at the same time, without waiting for the completion of one before starting another. This enables React to make better use of idle resources and reduces the time it takes for updates to become visible to the user.
Time slicing: With time slicing, React can prioritize and divide the rendering work into smaller chunks that can be spread across multiple frames. This allows React to keep the app responsive while it's performing complex updates, such as rendering large lists or trees.
Overall, Concurrent React is a significant improvement to React's performance and should lead to a better user experience for applications that implement it.
import { useState, useEffect } from 'react';
const ListItem = React.lazy(() => import('./ListItem'));
function LargeList() {
const [items, setItems] = useState([]);
useEffect(() => {
// simulate loading a large list of items
const loadItems = async () => {
const response = await fetch('https://example.com/large-list');
const data = await response.json();
setItems(data);
};
loadItems();
}, []);
return (
<ul>
{items.map(item => (
<React.Suspense key={item.id} fallback={<div>Loading...</div>}>
<ListItem item={item} />
</React.Suspense>
))}
</ul>
);
}
export default LargeList;
Using React.lazy()
and React.Suspense
enables Concurrent React to split the rendering work into smaller chunks that can be executed over multiple frames, without blocking the main thread. This allows the user to interact with the app while the list is being updated, leading to a more responsive and smooth user experience.
2) Automatic Batching
Automatic batching in React is a feature that groups multiple state updates into a single batch, reducing the number of re-renders and improving the performance of the app.
Normally, when you call setState()
to update the state of a component in React, it triggers a re-render of the component. If you call setState()
multiple times within the same event handler or lifecycle method, React will batch the updates together, reducing the number of re-renders.
However, if you call setState()
outside of an event handler or lifecycle method, such as in a setTimeout or Promise callback, React will not batch the updates together, and each call will trigger a separate re-render.
Automatic batching in React solves this problem by batching all state updates, regardless of where they occur. This means that even if you call setState()
outside of an event handler or lifecycle method, React will still batch the updates together, reducing the number of re-renders.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
export default Counter;
Without automatic batching, this would trigger three separate re-renders, which could be a performance issue if the component is complex or has many child components. But with automatic batching, React will batch the updates together and only trigger a single re-render, resulting in better performance.
3) Client and Server Rendering APIs
1) CreateRoot: createRoot()
is a new API introduced in React 18 that allows you to create a root for your React application that can be used for both client-side and server-side rendering. This method returns a Root
object, which can be used to manage the lifecycle of the React root.
import React from 'react';
import { createRoot } from 'react-dom';
function App() {
return <h1>Hello, world!</h1>;
}
const root = createRoot(document.getElementById('root'));
root.render(<App />);
The createRoot()
method provides an optimized way to render your React app, as it allows you to defer rendering until the client is ready. This can help to improve the performance of your app by reducing the amount of work that needs to be done on the server.
Note that the createRoot()
method should only be used if you plan to use React's new hybrid rendering capabilities. If you're only rendering your app on the client or the server, you can continue to use the render()
method from react-dom
as before.
2) HydrateRoot: When you render a React app on the server using something like renderToString()
, the resulting HTML will not have any event handlers or other client-side interactivity. The hydrate()
method is used to attach event handlers and other interactivity to the server-rendered HTML, effectively "hydrating" it into a fully-functional client-side React application.
import React from 'react';
import { hydrate } from 'react-dom';
function App() {
return <h1>Hello, world!</h1>;
}
hydrate(<App />, document.getElementById('root'));
In this example, we have a simple App
component that we want to render on both the server and client. We first render the App
component on the server using renderToString()
, which produces an HTML string. We then send the HTML string to the client and use the hydrate()
method to attach event handlers and other interactivity to the server-rendered HTML.
The hydrate()
method works very similarly to the render()
method, but it expects that the container element passed as the second argument already contains the markup that was rendered on the server. The hydrate()
method will then attach event handlers and other interactivity to this existing markup, effectively "hydrating" it into a fully-functional client-side React application.
Note that the hydrate()
method should only be used if you plan to use React's new hybrid rendering capabilities. If you're only rendering your app on the client or the server, you can continue to use the render()
method from react-dom
as before.
4) React DOM Server
1) renderToPipeableStream: Traditional server-side rendering in React involves rendering the entire application to a string using the renderToString()
method, and then sending that string to the client as a response. This can be inefficient for large applications, as it requires rendering the entire application in one go, which can be slow and memory-intensive.
With renderToPipeableStream()
, you can instead render your React application to a stream that can be sent to the client in chunks, as they are ready. This allows the client to start receiving the response more quickly, which can improve the perceived performance of your application.
import React from 'react';
import { renderToPipeableStream } from 'react-dom/server';
function App() {
return <h1>Hello, world!</h1>;
}
const stream = new Writable({
write(chunk, encoding, callback) {
console.log(chunk.toString());
callback();
},
});
renderToPipeableStream(<App />, stream);
In this example, we have a simple App
component that we want to render to a stream. We create a Writable
stream that we can pipe the output to, and then we use the renderToPipeableStream()
method to render the React application to that stream.
As the React application is rendered, it will write chunks of HTML to the stream, which we can then send to the client as they become available. This allows the client to start receiving the response more quickly, which can improve the perceived performance of your application.
Note that renderToPipeableStream()
should only be used if you plan to use React's new hybrid rendering capabilities. If you're only rendering your app on the client or the server, you can continue to use the traditional renderToString()
method or the renderToNodeStream()
method from react-dom/server
.
New Hooks....
1) useId
useId
is a new hook introduced in React 18 that provides a way to generate unique IDs for elements in a React component. This is useful for cases where you need to associate a label with an input, or when you need to reference an element in JavaScript.
import React, { useId } from 'react';
function Example() {
const inputId = useId();
const labelId = useId();
return (
<div>
<label htmlFor={inputId} id={labelId}>
Example Input:
</label>
<input type="text" id={inputId} aria-labelledby={labelId} />
</div>
);
}
We then use the generated IDs to associate the label with the input using the htmlFor
attribute on the label and the id
and aria-labelledby
attributes on the input. This makes it easier for users of assistive technologies, such as screen readers, to understand the relationship between the label and the input.
Note that the useId
hook should only be used for generating IDs for elements within a React component. If you need to generate unique IDs for elements across multiple components, you should use a more global solution, such as a unique ID generator or a library like nanoid
.
2) useTransition
createTransition
- A new API that allows developers to create custom transitions that can be applied to any component.useTransition
- A hook that allows components to defer rendering until a certain condition is met, such as a data fetch being completed or an animation finishing.<AnimatePresence>
- A new component that enables smooth transitions when components are added, removed, or replaced. This component automatically detects when a child component is entering or leaving the DOM and applies the appropriate animation.<AnimateSharedLayout>
- A new component that enables components to share a layout, making it easy to create complex animations that involve multiple components. This component also provides fine-grained control over the timing and duration of animations.SuspenseList
- A new component that enables developers to control the order in which components are loaded and rendered, allowing for more efficient and responsive data fetching.
Overall, these transitions make it easier than ever to build responsive, immersive user experiences in React 18. Whether you're building a complex animation or a simple loading spinner, these transitions provide the tools you need to create a smooth and engaging user experience.
import { useState, useTransition } from 'react';
function Spinner() {
const [isLoading, setIsLoading] = useState(false);
const [showSpinner, startTransition] = useTransition({
timeoutMs: 1000,
});
const fetchData = async () => {
setIsLoading(true);
await fetch('https://api.example.com/data');
setIsLoading(false);
startTransition(() => {
setIsLoading(false);
});
};
return (
<div>
<button onClick={fetchData}>Fetch Data</button>
{showSpinner && <div className="spinner" />}
{isLoading && <p>Loading...</p>}
</div>
);
}
When the user clicks the "Fetch Data" button, we set isLoading
to true
and start the transition by calling startTransition
. This will cause the showSpinner
value to transition from false
to true
, which will trigger the appearance of the loading spinner.
Once the data has been fetched, we set isLoading
to false
, which will display the fetched data. We also wait for the transition to finish before setting showSpinner
to false
, which will cause the loading spinner to disappear smoothly.
3) useDeferredValue
useDeferredValue
is a new hook introduced in React 18 that allows you to defer the update of a state value until a later time, which can help to improve performance and reduce jank in your application.
import React, { useState, useDeferredValue } from 'react';
function Example() {
const [value, setValue] = useState('');
const deferredValue = useDeferredValue(value, { timeoutMs: 1000 });
function handleChange(event) {
setValue(event.target.value);
}
return (
<div>
<input type="text" value={value} onChange={handleChange} />
<p>Deferred Value: {deferredValue}</p>
</div>
);
}
The useDeferredValue
hook takes two arguments: the current value of the state, and an options object that includes a timeoutMs
property. The timeoutMs
property specifies how long to defer the update of the state value in milliseconds. In this example, we're deferring the update for 1 second.
When the user types into the input, the handleChange
function updates the value
state variable. However, the deferredValue
variable will not update immediately. Instead, it will update after the specified timeout has elapsed. This can help to reduce jank in your application by allowing time for other updates to happen before updating the deferred value.
In the return statement, we render the value
state variable in the input, and we render the deferredValue
variable in the paragraph element. You'll notice that the paragraph element updates with a delay compared to the input element, as a result of the deferred update.
4) useSyncExternalStore
useSyncExternalStore
is a new hook introduced in React 18 that allows you to synchronize a React component's state with an external data store. This can be useful in cases where you need to synchronize your application state with a server, a database, or other external data source.
import React, { useState, useEffect } from 'react';
import { useSyncExternalStore } from 'react';
function Example() {
const [data, setData] = useState({});
const syncData = useSyncExternalStore('https://example.com/data', data);
useEffect(() => {
fetch('https://example.com/data')
.then(response => response.json())
.then(newData => setData(newData))
}, [syncData]);
return (
<div>
<p>Data: {JSON.stringify(data)}</p>
</div>
);
}
The useSyncExternalStore
hook takes two arguments: the URL of the external data store, and the current value of the state. In this example, we're using the URL https://example.com/data
as the external data store.
In the useEffect
hook, we retrieve the current data from the server using the fetch
API. We then update the data
state variable with the new data using the setData
function. The useEffect
hook is triggered whenever the syncData
variable changes, which ensures that the state is updated whenever the external data store changes.
In the return statement, we render the current value of the data
state variable in a paragraph element using the JSON.stringify
function.
5) useInsertionEffect
useInsertionEffect
is a new hook introduced in React 18 that allows you to perform an effect after a component has been inserted into the DOM. This can be useful in cases where you need to interact with the DOM or perform other side effects that require the component to be fully rendered.
import React, { useRef } from 'react';
import { useInsertionEffect } from 'react';
function Example() {
const ref = useRef(null);
useInsertionEffect(() => {
if (ref.current) {
ref.current.focus();
}
}, [ref]);
return (
<div>
<input type="text" ref={ref} />
</div>
);
}
We then use the useInsertionEffect
hook to perform an effect after the component has been inserted into the DOM. In this example, we're using the effect to focus on the input element using the focus
method. The effect is triggered whenever the ref
variable changes.
In the return statement, we render the input element with the ref
attribute set to the ref
variable. This allows us to reference the input element in the useInsertionEffect
hook.
Thanks for reading .... ❤️
keeping learning 👍
Subscribe to my newsletter
Read articles from Tushar Jagi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by