Type 'undefined' is not assignable to type 'Element | null' - reactjs

I use a component :
const data: any[] = []
<Tiers data={data}/>
My component is built like this:
const Tiers = ({
data,
}: {
data?: any;
}) => {
console.log('data', data?.length!);
if(!data.length) return undefined;
};
export default Tiers;
I get the following message automatically :
'Tiers' cannot be used as a JSX component.
Its return type 'undefined' is not a valid JSX element.

Use
return null
instead of
return undefined
to make Tiers a valid JSX component.
You need two return statements in the component like below:
const Tiers = ({
data,
}: {
data?: any;
}) => {
console.log('data', data?.length!);
if(!data.length) return null;
return null
};
export default Tiers;

Your code has two problems,
It needs at least one condition that returns a JSX.Element(even a very simple one like Hello), if not, you probably needed a function instead.
A component cannot return undefined. If you want it to return nothing, you should return null instead of undefined.

Related

Constraint on return and error to make sure one is defined

I have a simple case, hook function that returns {data, error} For simplicity lets assume that both are strings. While both error and data can be undefined when the second is returned, It's not possible that both are undefined together.
Do I have a way in typescript to set constraint that it will never happen that both are undefined? I use function in the React component and I would like this code
const myComponent: React.FC= function(){
const { data, error } = useMyCustomHook();
if ( error ) return<div> error </div>
if (data) return <div> data </div>
}
to be ok. Instead it throws
Type 'Element | undefined' is not assignable to type 'ReactElement<any, any> | null'.
Which wouldnt be here if there would be a way to tell typescript that error || data is always true.
One solution would be to make two interfaces one that require error with optional dataanother that require data leaving error optional and then type useMyCustomHook return to be union of those two, but it sounds like a bad solution that doesnt work in the scale. Imagin what will happen if I would like to have one property out of five, or even two out of five to be defined.
It would be useful to see what type useMyCustomHook returns.
If data can be falsy, such as undefined, the current code will miss one condition. In this case, you should handle the case where data is falsy.
You can modify the code like this:
const MyComponent: React.FC = () => {
const { data, error } = useMyCustomHook();
if (error) return <div>error</div>;
if (!data) return <div>empty</div>;
// you can safely access to the data
return <div>data</div>;
};
Alternatively, you can use a regular function as a component, so as not to be restricted to the React.FC type. This should still be a valid component:
const MyComponent = () => {
const { data, error } = useMyCustomHook();
if (error) return <div>error</div>;
if (data) return <div>data</div>;
}

Why is encumbranceByType object possible undefined?

I have a react component that takes in data of type EncumbranceData[] and restructures that data before mapping through it. This restructuring involves creating an empty object of type EncumbrancesByType and creating keys and values for it based on encumbranceData
However, I keep getting an "Object possible undefined" error where it says encumbrancesByType[e.type].push(e.suite). I don't understand how this could be undefined, as none of the fields on these types are optional. This component will always receive data of type EncumbranceData that will always have a type, id, and suite. EncumbranceData cannot be undefined. The object starts as an empty one, but each iteration through the forEach method should initialize each key with a truthy empty array.
How can the object possible be undefined, then, at the push(e.suite) statement?
I can get it to work by just using a bunch of optional operators (encumbrancesByType?.push()), but I feel like that defeats the point of type safety.
type EncumbranceData = {
id: string;
suite: string;
type: string;
};
interface EncumbrancesByType {
[key: string]: string[];
}
type EncumbranceAlertByTypeProps = {
id: string;
suites: string[];
type: string;
};
const EncumbranceAlertByType: React.FC<EncumbranceAlertByTypeProps> = ({ id, suites, type }) => {
const renderSuites = suites.map((s) => <span>{s}</span>);
return (
<div>
<div>{type}</div>
{renderSuites}
</div>
);
};
type ConflictingEncumbrancesAlertProps = {
encumbranceData: EncumbranceData[];
isOpen: boolean;
onContinue(): void;
onCancel(): void;
suiteIdsToCheck: string[];
encumbranceTypesToConsider?: EncumbranceType[];
};
const ConflictingEncumbrancesAlert: React.FC<ConflictingEncumbrancesAlertProps> = ({
encumbranceData,
isOpen,
onContinue,
onCancel,
suiteIdsToCheck,
encumbranceTypesToConsider,
}) => {
const encumbrancesByType: EncumbrancesByType = {}
encumbranceData.forEach((e) => {
if (!encumbrancesByType[e.type]) encumbrancesByType[e.type] = [e.suite]
else encumbrancesByType[e.type].push(e.suite)
})
const encumbrancesContent = Object.keys(encumbrancesByType).map((type) => (
<EncumbranceAlertByType suites={encumbrancesByType[type]} type={type} />
));
return <div>{encumbrancesContent}</div>;
};
export default ConflictingEncumbrancesAlert;
You likely have the noUncheckedIndexedAccess rule enable in your tsconfig. When you have this rule the compiler will always complain on unchecked index access.
Also, TS won't narrow down (remove the undefined) on index access. In order to have the compiler do that, you'll have to use an intermidiate variable.
encumbranceData.forEach((e) => {
const foo = encumbrancesByType[e.type]; // intermediate variable
if (foo) {
foo.push(e.suite); // ok
}
});
Playgroud

React Query - Combine properties from multiple custom hooks that use useQuery with type safety

I have an arbitrary amount of custom hooks that return the query result from useQuery. What I'm trying to do is combine the return values from those hooks into one object with this structure:
{
data,
isLoading,
isFetching,
isSuccess,
}
The data property will be an array created from combining the data properties on all the results.
This is my current code:
export function isAnyProperty(
property: keyof QueryObserverBaseResult<unknown, unknown>,
...queries: UseQueryResult<unknown, unknown>[]
) {
return queries.some((query) => query[property]);
}
export function combineQueryResults<TData extends unknown[], TQueries extends UseQueryResult<TData>[]>(
...queries: TQueries
) {
const data = queries.reduce<TData[]>((acc, query) => {
if (query.data === undefined) {
return acc;
}
return [...acc, query.data];
}, []);
return {
data,
isLoading: isAnyProperty('isLoading', ...queries),
isFetching: isAnyProperty('isFetching', ...queries),
isSuccess: isAnyProperty('isSuccess', ...queries),
};
}
I can't get TypeScript to infer the values for TData when I am using rest parameters, and I am not quite sure why. One thing to note is that I did manage to get TypeScript to infer the correct types when data was an array of UseQueryResult, just not one level down with TData.
For reference, what I get back currently from data is unknown[][]. What I am expecting to get back is of course a tuple with the data values from UseQueryResult. I thought maybe the reduce function removing all undefined values might mess with TypeScript being able to infer the correct values - so I tried it with a simple map as well, which results in data: (unknown[] | undefined)[] - so that didn't work either.
I also tried this:
export function combineQueryResults<TData extends unknown[]>(...queries: UseQueryResult<TData>[]) {
const data = queries.map((query) => query.data);
return {
data,
isLoading: isAnyProperty('isLoading', ...queries),
isFetching: isAnyProperty('isFetching', ...queries),
isSuccess: isAnyProperty('isSuccess', ...queries),
};
}
But with this code, typescript will only infer the type for the first variable passed to combineQueryResult and results in a Type 'TypeOne[]' is not assignable to type 'TypeTwo[]' error.
Looks like typescript will infer the type TData when used with UseQueryResult, but not on its own:
unknown[], Vod.ConstructedAsset[], and Group.Union[] are all TData.
I created this function to combine my queries:
const combineResults = <T extends object>(
data: UseQueryResult<T[], unknown>[]
): T[] =>
data.reduce((acc, curr) => {
if (!curr.data) return acc;
return [...acc, ...curr.data];
}, []);
//my hook
const useMyHook = (teams: TeamInfo[]) => {
const result = useQueries(
//Your queries here...
);
return {
data: combineResults<MyInterface>(result),
isLoading: result.some((query) => query.isLoading),
isFetching: result.some((query) => query.isFetching),
};
};
I hope this helps you.

How to write a Typescript React Component Wrapper

A custom ReactJS wrapper component should be used to wrap code and only render it, if the user has the required permission. Therefore, it needs to accept two parameters, the children that shall be rendered as well as the permission it needs to check for.
The idea is to use it like this:
const AdminTools = () => {
return (
<RequirePermission require_permission="ui:see_admin_menu">
<>
<h1>Sudo mode</h1>
<Link href="/intern/users/">
<a>
<ActionCard link>User management</ActionCard>
</a>
</Link>
</>
</RequirePermission>
);
};
What I came up with so far is the following code:
const RequirePermission = (children: React.FC<{}>, required_permission: string) => {
const user = useContext(AuthContext);
let hasPermission = false;
if (user.state == AuthState.Authorized) {
user.user?.realm_access?.roles.forEach(role => {
if (permissions[role].includes(required_permission)) {
hasPermission = true;
}
});
}
if (hasPermission) {
return children;
} else {
return <div />;
}
};
export default RequirePermission;
When using the code snippet as described above, the following error is thrown:
Type '{ children: Element; require_permission: string; }' is not assignable to type 'IntrinsicAttributes & FC<{}>'.
Property 'children' does not exist on type 'IntrinsicAttributes & FC<{}>'.ts(2322)
'RequirePermission' cannot be used as a JSX component.
Its return type 'FC<{}> | Element' is not a valid JSX element.
Type 'FunctionComponent<{}>' is missing the following properties from type 'Element': type, props, keyts(2786)
I don't really understand the error message to be frank. Any help would be much appreciated.
//Edit:
Error messages given by proposed code:
This JSX tag's 'children' prop expects a single child of type 'ReactChildren', but multiple children were provided.ts(2746)
and
'RequirePermission' cannot be used as a JSX component.
Its return type 'Element | ReactChildren' is not a valid JSX element.
Type 'ReactChildren' is missing the following properties from type 'Element': type, props, key
Try this, importing ReactChildren from react.
I wish i could explain this better, but i know typescript is expecting a JSX/TSX element, so we could use ReactElement for the return type.
For a better explanation on the children type
https://stackoverflow.com/a/58123882/7174241
import React, { ReactChildren, ReactElement, ReactNode } from "react";
interface RequireType {
children: ReactChildren | ReactNode | ReactElement;
required_permission: string;
}
const RequirePermission = ({ children, required_permission }: RequireType):ReactElement => {
const user = useContext(AuthContext);
let hasPermission = false;
if (user.state == AuthState.Authorized) {
user.user?.realm_access?.roles.forEach((role) => {
if (permissions[role].includes(required_permission)) {
hasPermission = true;
}
});
}
return <>{hasPermission ? children : null}</>
};
export default RequirePermission;

Type argument at moment of use

I'm trying to build a generic tool for handling the response to React Query useQuery requests in my app, and build this out using TypeScript, so that the data is correctly typed.
In a parent component, the use case would be something like this:
const queryResult = useQuery<MyResponseType, Error>('whatever');
return (
<ReactQueryLoader useQueryResult={queryResult}>
{data => {
// Data should be of MyResponseType type, but is unknown
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}}
</ReactQueryLoader>
);
I tried defining this ReactQueryLoader as so:
import React from "react";
import { UseQueryResult } from "react-query";
import { Loading } from "./Loading";
interface ReactQueryLoaderProps {
useQueryResult: UseQueryResult<unknown, Error>;
handleError?: (error: Error) => void;
children: (data: unknown) => JSX.Element;
}
export const ReactQueryLoader = ({
useQueryResult,
handleError = error => {
console.error(error);
throw new Error(error.message);
},
children,
}: ReactQueryLoaderProps): null | JSX.Element => {
if (useQueryResult.isError) {
handleError(useQueryResult.error);
return null;
}
if (useQueryResult.isLoading) {
return <Loading />;
}
return children(useQueryResult.data);
};
But when I inspect the data passed to the child, instead of being the type of the expected response data, it is unknown. I'm having trouble getting this to work, as someone with a mid-low-level understanding of TypeScript. I thought I could apply the same pattern as in this answer to another question I had, but I can't figure out how to do it in this context.
I tried this:
interface ReactQueryLoaderProps {
// ...
children: <T extends unknown>(data: T) => JSX.Element;
}
But got this in the parent element:
(parameter) data: T extends unknown
I also tried:
interface ReactQueryLoaderProps {
useQueryResult: UseQueryResult<T extends unknown, Error>;
But that provoked two errors:
Under the T: Cannot find name 'T'.
Under the comma: '?' expected.
Is this possible to do?
The ReactQueryLoader function needs to be generic here, as well as the props type.
That might look something like this:
interface ReactQueryLoaderProps<T extends UseQueryResult<unknown, Error>> {
useQueryResult: T;
handleError?: (error: Error) => void;
children: (data: T['data']) => JSX.Element;
}
And
export function ReactQueryLoader<T extends UseQueryResult<unknown, Error>>({
useQueryResult,
handleError,
children,
}: ReactQueryLoaderProps<T>): null | JSX.Element {
//...
}
Here ReactQueryLoader is now a generic function that captures the query result type as T and passes that to the generic props type ReactQueryLoaderProps.
ReactQueryLoaderProps then uses that type as the type of useQueryResult, and pulls the ['data'] property from that type for the type of the children function parameter.
See playground

Resources