Actual behavior:
interface Nestedthing { a: { b: { c: string, d: number } } } const _example: Nestedthing = { a: { b: { c: "hi" // missing: d } } }
Gives an error like:
$ /Users/orta/dev/projects/typescript/ts_examples/nested_interfaces/node_modules/.bin/tsc example.ts(10,7): error TS2322: Type '{ a: { b: { c: string; }; }; }' is not assignable to type 'Nestedthing'. Types of property 'a' are incompatible. Type '{ b: { c: string; }; }' is not assignable to type '{ b: { c: string; d: number; }; }'. Types of property 'b' are incompatible. Type '{ c: string; }' is not assignable to type '{ c: string; d: number; }'. Property 'd' is missing in type '{ c: string; }'.
This example is pretty small, but the deeper the nesting, the longer the error message. These massages can get quite hard to dig right to the point:
An example from our codebase
src/lib/Components/Gene/__tests__/About-tests.tsx(45,40): error TS2322: Type '{ gene: { description: string; trending_artists: { __id: string; href: string; name: string; coun...' is not assignable to type 'IntrinsicAttributes & RelayProps & { children?: ReactNode; }'.
Type '{ gene: { description: string; trending_artists: { __id: string; href: string; name: string; coun...' is not assignable to type 'RelayProps'.
Types of property 'gene' are incompatible.
Type '{ description: string; trending_artists: { __id: string; href: string; name: string; counts: { fo...' is not assignable to type '{ trending_artists: (string | number | boolean)[]; }'.
Types of property 'trending_artists' are incompatible.
Type '{ __id: string; href: string; name: string; counts: { for_sale_artworks: number; artworks: number...' is not assignable to type '(string | number | boolean)[]'.
Type '{ __id: string; href: string; name: string; counts: { for_sale_artworks: number; artworks: number...' is not assignable to type 'string | number | boolean'.
Type '{ __id: string; href: string; name: string; counts: { for_sale_artworks: number; artworks: number...' is not assignable to type 'false'.
We started teaching JS developers learning TypeScript to always «start at the end of a compiler message» — which isn’t great ( this is compounded by VS Code only supporting single-line error messages in the problems section )
I wonder if it’s possible to improve the error messaging to include a summary before the tree of ‘how we got to that error’?
For example:
A pretty simple attack could be to take all the leaf nodes and show them beforehand in a separate section:
/Users/orta/dev/projects/typescript/ts_examples/nested_interfaces/node_modules/.bin/tsc example.ts(10,7): error TS2322: Type '{ a: { b: { c: string; }; }; }' is not assignable to type 'Nestedthing'. example.ts(13,4): Property 'd' is missing in type '{ c: string; }' From incompatible property `a` in: Type '{ b: { c: string; }; }' is not assignable to type '{ b: { c: string; d: number; }; }'. From incompatible property `b` in: Type '{ c: string; }' is not assignable to type '{ c: string; d: number; }'.
This:
- Highlights the exact place the problem occurs first as you’re scanning
- Could (one day) work nice with (Multi-line diagnostics vscode#1927), which means editors could highlight the exact place where something needs work
- Is less visually dense, but still shows the same error
A similar example based error output based on the real life error include above also:
src/lib/Components/Gene/__tests__/About-tests.tsx(45,40): error TS2322: Type '{ gene: { description: string; trending_artists: { __id: string; href: string; name: string; coun...' is not assignable to type 'IntrinsicAttributes & RelayProps & { children?: ReactNode; }'. Type '{ __id: string; href: string; name: string; counts: { for_sale_artworks: number; artworks: number...' is not assignable to type 'false'. Type '{ __id: string; href: string; name: string; counts: { for_sale_artworks: number; artworks: number...' is not assignable to type 'string | number | boolean'. Type '{ __id: string; href: string; name: string; counts: { for_sale_artworks: number; artworks: number...' is not assignable to type '(string | number | boolean)[]'. From incompatible property `trending_artists` in: Type '{ description: string; trending_artists: { __id: string; href: string; name: string; counts: { fo...' is not assignable to type '{ trending_artists: (string | number | boolean)[]; }'. From incompatible property `gene` in: Type '{ gene: { description: string; trending_artists: { __id: string; href: string; name: string; coun...' is not assignable to type 'RelayProps'.
We’re happy to take a stab at implementing this, but you all have the most context around edge cases and what you want the messages to say.
/cc @damassi @alloy
TS2322 error happens when trying to add a property to an anonymous object. Haven’t you faced this case where you need to create an object first with some values and then, add additional property depending on the values.
interface Result {
subject: string;
score: number;
result: "Pass" | "Failure";
}
let result = {
score: 21,
subject: "Math",
};
if (result.score > 60) {
result = {
...result,
// Type '{ result: string; score: number; subject: string; }' is not assignable to type '{ score: number; subject: string; }'.
// Object literal may only specify known properties, and 'result' does not exist in type '{ score: number; subject: string; }'.ts(2322)
result: "Pass",
}
// Element implicitly has an 'any' type because expression of type '"result"' can't be used to index type '{ score: number; subject: string; }'.
// Property 'result' does not exist on type '{ score: number; subject: string; }'.ts(7053)
result["result"] = "Pass";
}
This doesn’t work because of TS2322 error or TS7053 error. For TS7053 error, the object needs to have a key-value definition like the following.
type KeyValue = { [key: string]: string; };
You can learn the detail in the following post.
But the solution is not good because the type doesn’t match the original type “Result”.
Make only desired properties optional
The solution is to create a type that matches the implementation. In this case, only “result” property needs to be optional.
TypeScript offers some utility types but none of them can make the specified properties optional. Let’s create our own utility type if it doesn’t exist. We use the following utility types.
- Partial makes all properties optional.
- Omit removes specified properties
// Type definition
type Optional<T, K extends keyof T> = Partial<T> & Omit<T, K>;
// Usage
type OptionalResult = Optional<Result, "result">;
// subject
// score
// result? <--- optional
Optional<T, K extends keyof T>
requires a type on the first argument. The second argument is the keys of the type.
Partial<T>
makes all properties optional.Omit<T, K>
removes the specified properties but other properties are not optional.
When the two definitions are together, we can make only specified properties optional.
Partial<Result> Omit<Result, "result"> Result
subject? + subject -> subject
score? + score -> score
result? + - -> result?
Make only desired properties mandatory and others optional
Required
type makes all properties mandatory. If we want to make some of the properties mandatory, we need to create our own type.
We can create the type by using the following two types.
- Partial makes all properties optional.
- Pick picks specified properties
// Type definition
type Mandatory<T, K extends keyof T> = Partial<T> & Pick<T, K>;
// Usage
type MandatoryResult = Mandatory<Result, "subject" | "score">;
// subject
// score
// result? <--- optional
The behavior is the same as the previous type.
Partial<Result> Pick<Result, "subject" | "score"> Result
subject? + subject -> subject
score? + score -> score
result? + - -> result?
Using the our own utility types
By defining a proper type, we can add the additional property in both ways.
let mandatoryResult: MandatoryResult = {
subject: "Math",
score: 61
};
let optionalResult: OptionalResult = {
score: 11,
subject: "hoge",
};
function addResult(obj: OptionalResult | MandatoryResult) {
if (obj.score > 60) {
obj = {
...obj,
result: "Pass",
};
console.log(obj);
} else {
obj["result"] = "Failure";
console.log(obj);
}
}
addResult(optionalResult);
// { score: 11, subject: 'hoge', result: 'Failure' }
addResult(mandatoryResult);
// { subject: 'Math', score: 61, result: 'Pass' }
Conclusion
If we define the proper type, the code is clearer and more maintainable. If the type is not defined, IDE doesn’t rename the property name and thus, it takes a longer time to fix them.
Use the following utility types to make selected properties optional/mandatory.
type Optional<T, K extends keyof T> = Partial<T> & Omit<T, K>;
type Mandatory<T, K extends keyof T> = Partial<T> & Pick<T, K>;
type OptionalResult = Optional<Result, "result">;
type MandatoryResult = Mandatory<Result, "subject" | "score">;
Recently when I was trying to update the React Router v6 (react-router-dom 6.0.1
), it gave me an unexpected error, ‘TS2322: Type ‘{ render: () => Element; }’ is not assignable to type ‘IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)’. Below is a snippet of the error shown:
TS2322: Type '{ render: () => Element; }' is not assignable to type 'IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)'. Property 'render' does not exist on type 'IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)'.
I tried to do it based on the document on react rerouter, which is shown here .
<Redirect>
elements that are directly inside a <Switch>
.I would like to share the steps that helped me to fix the error, TS2322: Type ‘{ render: () => Element; }’ is not assignable to type ‘IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)’.
The error, TS2322: Type ‘{ render: () => Element; }’ is not assignable to type ‘IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)’ is seen because of a bug in the documents. Even though it looks like the section about react rerouting talks about upgrading from v5 to v5.1; they only removed render
in v6.
A simple solution to fix this is to use the no match route approach. You can check its documentation here.
As per the official Reactrouter tutorial, “no match route” method is explained as mentioned below:
A detailed solution to fix the ImportError: cannot import name ‘escape’ from ‘jinja2 error is provided below:
How to Fix TS2322: Type ‘{ render: () => Element; }’ is not assignable to type ‘IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)’ error?
Method 1: No route approach
To fix the TS2322: Type ‘{ render: () => Element; }’ is not assignable to type ‘IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)’
you should use the no match route approach. You can check its documentation here.
To use the no route approach, add the below-mentioned code to your project and run it:
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/lab" element={<Lab />} />
<Route
path="*"
element={<Navigate to="/" replace />}
/>
</Routes>
</BrowserRouter>
You can set the replace prop in your code to keep the history clean. This will avoid extra redirects after the user click back
Method 2: Direct method
To fix the TS2322: Type ‘{ render: () => Element; }’ is not assignable to type ‘IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)’
You can add the below-mentioned code to your project and run it. You can check its documentation here.
import { useNavigate } from "react-router-dom";
let navigate = useNavigate();
useEffect(() => {
if (LoggedIn){
return navigate("/");
}
},[LoggedIn]);
Conclusion
To fix the TS2322: Type ‘{ render: () => Element; }’ is not assignable to type ‘IntrinsicAttributes & (PathRouteProps | LayoutRouteProps | IndexRouteProps)’ error; you can use the no match route approach. If this method does not work for you and you are still facing the same error, you can try to avoid the error by directly adding a snippet of the above-mentioned code to fix your error.