Until React 18, if a component rendered undefined
,
React would throw an error at runtime.
//Shape.jsx
import React from 'react';
import Circle from './Circle';
import Square from './Square';
function Shape({type}) {
if(type === 'circle') {
return <Circle />
}
if(type === 'square') {
return <Square />
}
}
export default Shape;
//App.jsx
function App() : ComponentType {
return(<Shape type="rectangle"/>)
}
//Rectangle is passed as props in type which is not handled in the Shape component, so it will throw an error
Error: Shape(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
To fix the error, we had to return null
.
import React from 'react';
import Circle from './Circle';
import Square from './Square';
function Shape({type}) {
if(type === 'circle') {
return <Circle />
}
if(type === 'square') {
return <Square />
}
return null;
}
export default Shape;
With the changes in React 18,
no runtime error will be thrown
if nothing is returned from the component.
Let us look into the reasons why React
came up with this change.
Why does React allows components to render undefined?
-
Instead of throwing a runtime error, linting would be a better choice
Runtime error was added back in 2017
when type systems and linting were not very popular in the ecosystem.
Now, linting has evolved a lot.
We can use its powers to handle these types of errors.We cannot tell the difference between
forgetting to return and explicitly returningundefined
at runtime.
As linting operates at the code syntax, it can determine this difference.Linting can also give a more specific error message pointing to the source of the error.
This can help debug the issue more quickly than a generic message at runtime. -
Creating the correct type was hard
Not able to create a generic type for something
that React can render, which we can use in place of any prop or component.
It has been a long-standing issue in the ecosystem.Consider we want to render the
children
prop in ourShape
component.//Shape.jsx const Shape = ({children}: ComponentType): ComponentType => { return children; } //App.jsx function App() : ComponentType{ return(<Shape />); }
How can we manage to handle ComponentType as
children
andShape
?
We cannot includeundefined
because it would throw an error at runtime.
If we excludeundefined
, then everywhere we pass the children down to Shape,
we would need to check ifchildren
is undefined.This simplest solution was to allow ‘undefined’ to be rendered.
To add further,
for Server Components,
it is important to have a general type for things React can render.
This is because we may need to be able to accept children passed from Server Components. -
To maintain consistent behavior
Recently changes were made to Suspense
to handle null or undefined fallbacks.
These changes allowed undefined fallbacks to render
instead of throwing an error.To maintain consistency in React, it was necessary to allow components to render undefined.
Check out
the PR
and
the write-up
for more details.