Object is possibly 'undefined'. TS2532 - reactjs

I keep getting Object is possibly 'undefined'. TS2532 and I know that it's due to assignment of setViewer to undefined, which I have done for the purpose of login matching but I came across with another problem. Right now, I have 2 data coming in homeviewer and one of them is announcements and when I try to access the data, it gives me TS2532 error. Here are the code below. I am new to these and I don't know what to change for the setViewer (setViewer: () => undefined) in order it for to work.
homeViewer.ts
import { createContext } from 'react';
import { HomeViewer } from '../types/home_viewer';
export interface ViewerContextType {
viewer?: HomeViewer;
setViewer: (home: HomeViewer) => void;
}
export const ViewerContext = createContext<ViewerContextType>({
viewer: {},
setViewer: () => undefined,
});
That takes interface from
export interface HomeViewer {
user?: User;
announcements?: Announcement;
}
The part where I use it
const { viewer } = useContext(ViewerContext);
const data = viewer?.announcements;
console.log(data[0]);

If I understand correctly, your issue is revolving around accessing elements at specific indexes from data – where data possibly has a value of undefined (as per your HomeViewer interface);
export interface HomeViewer {
user?: User;
announcements?: Announcement;
}
To me, it appears that the Announcement type is indicative of an array of some elements (judging by the way you're accessing its data), or undefined (since it's optional).
Meaning, it will never certainly be an instance of Announcement's type declaration, so you'll have to chain an optional access operator for accessing array elements that possibly don't exist: ?.;
const { viewer } = useContext(ViewerContext);
const data = viewer?.announcements;
console.log(data?.[0]) // -> Ok!
console.log(data[0]) // -> Err! Object is possibly undefined.

It could be because if viewer is undefined then data will also be undefined, and you can't do data[0] because is the same as undefined[0]

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>;
}

Using Antd's message with typescript

I am using
"antd": "^4.21.3", "typescript": "^4.7.4" with react 18, and I am facing the problem of error config type! The error message is below..
// current code
import { message } from 'antd'
function AlertModal({ type = 'success', duration = 3, ...props }) {
const config = {
duration: duration,
...props,
}
return message[type as keyof typeof message](config)
}
export default AlertModal
Does anyone have any idea how can I solve this annoying problem??
I'll just rewrite what's in the comments with some extra details, so that this question can be treated as answered.
The message import references the default export of this file which is typed as MessageApi, and the expression message[type as keyof typeof message] is evaluated as follows:
(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose) => MessageType
| (args: ArgsProps) => MessageType
| (options: ConfigOptions) => void
| (messageKey?: React.Key) => void
| () => [MessageInstance, React.ReactElement]
TypeScript in this scenario regards all of these function typings as equally possible so it tries to cast the config object as the first argument as each of those function types.
The problem arises when trying to cast an object to React.Key which is defined as string | number. config (or any object type) is just not a valid argument for that case and TypeScript is doing its job in warning you about that case.
However, in the implementation we can probably assume that certain special message types will not be called, such as the message type destroy. MessageApi['destroy'] is the only MessageApi function property that uses the React.Key argument, so removing (i.e. omitting) that typing would solve the issue. This can be easily done just by changing the keyof message cast to:
return message[type as keyof Omit<typeof message, 'destroy'>](config)

How to require interface members for response data?

In the code below (it compiles just fine), it was my expectation that any response data that did not conform to the interface IStory, would throw an error. However, this is not the case, and all the data is put in state normally. But there is certainly no thisPropertyDoesNotExist property on any of the response objects. Why isn't the interface being required here? How would that standard be implemented?
The end goal is for the response data to have at least the data defined in the interface (I'm really interested in checking if url's are not present).
import React, { useState, useEffect } from "react";
import { getStory } from "../services/hnAPI";
interface Props {
storyId: number;
}
interface IStory {
id: number;
title: string;
url: string;
thisPropertyDoesNotExist: string;
}
export const Story: React.FC<Props> = (props) => {
const [story, setStory] = useState<IStory | null>(null);
useEffect(() => {
getStory(props.storyId)
.then((data: Required<IStory>) => {
data && setStory(data);
})
.catch((error) => {
console.log(error);
setStory(null);
});
}, [props.storyId]);
return story ? (
<p>
<a href={story.url}>{story.title}</a>
</p>
) : null;
};
The end goal is for the response data to have at least the data defined in the interface (I'm really interested in checking if url's are not present).
Interfaces for json objects are just your promises (not validated at runtime) that the data will match what you say it will. TypeScript does not exist at runtime (its just JavaScript) and you cannot use interfaces are runtime.
Solution
You can however write JavaScript validation code that
* Returns a type to TypeScript
* Ensures the object matches the type
An example of such a library is: https://github.com/pelotom/runtypes

Object key optional with typescript — but being able to call it

I have the following interfaces declared
export interface User {
...
first: string;
...
}
export interface UserDataState {
credentials: User | {};
..
}
In my react component, I want to be able to access the first in the UserDataState like this:
const userData = useSelector((state: RootState) => state.user.credentials);
Welcome, { userData.first }
However I'm getting this error
Property 'first' does not exist on type '{} | User'.
So I tried the following:
let userFirst = '';
if (userData.hasOwnProperty('first')) {
userFirst = userData.first;
}
...
return ( <span>{userFirst}</span>)
and..
return ( {userData && userData.first && (<span>{userData.first}</span>)})
I don't want to make the user first optional and I need to be able to return an empty object to as the credentials. This is very much annoying.
I suggest that you use optional chaning.
For example, in your case, I would do the following:
export interface UserDataState {
credentials: User | undefined;
}
To access first from crendentials from state (suppose state is nullable), instead of doing:
state && state.credentials && state.credentials.first
You just need to use the optional chaining operator ?.
state?.credentials?.first
By doing this you won't compromise the type safety of your codebase with explicit casting, for example casting to any.
This idiom is pretty standard with Typescript 3.7 and above.
I got it working with
const user = useSelector((state: RootState) => state.user.credentials);
const userData: any = user;
// or this works to
import { User as UserType } from '../types';
const userData: { first?: UserType['first'] } = user;
Not sure if this is best practice though, if anyone has a better method would love to hear it
You can do something like this, so basically what you are doing is enforcing the type that it is going to be a User type:
const userData = useSelector((state: RootState) => state.user.credentials) as User;

Flow not crashing when passing empty object to a type

I got this definition:
export type Entry = {|
id: string,
title: string,
|};
...
export type Props = {
entries: Array<Entry>,
...
// The following is not crashing
const rowGetter = ({ index }): Entry => props.entries[index] || {};
Using flow-bin#0.67.1, the above code is not crashing. I'd have expected for it to crash, as the definition states the return value is an Entry type, not an empty object.
Of course if I change the definition of the arrow function as follows, it crashes as expected, saying that inexact literal doesn't match, and that properties Id and title are missing:
const rowGetter = ({ index }): Entry => ({});
Is this a bug, or am I missing something here?
This is Flow's design regarding potentially undefined array elements.
Any index into props.entries is considered to resolve to an Entry, and not undefined in the case of the index being out of bounds (or a sparse array).
As a result the || {} is not considered a viable branch and is therefore not evaluated. In your refactoring, you've removed the array access and {} is correctly reported as being invalid for Entry.
If you change our method to:
const rowGetter = ({ index }): Entry => {
if (typeof props.entries[index] !== 'undefined') {
return props.entries[index]
}
return {}
}
Then flow will correctly report the return {}.

Resources