How to define typescript interface for array on objects - reactjs

Here is the data from the server.
I want to display it with map on UI.
Here is the interface I did -
export interface IHistory {
reports: Readonly<{
readonly id?: string,
status?: Status,
readonly created_at?: Date,
}>
}[];
The map I'm doing:
{props.history!.map((index, idx) => {}
The error:
TypeError: Cannot read properties of null (reading 'map')
What am I doing wrong?
I want to display only the reports.
Added -
Interface -
export interface IHistory {
reports: Array<{
id?: string;
status?: Status;
created_at?: string;
}>;
};
const [ hitoryState, setHistoryState ] = useState<IHistory | null>(null);
useEffect(() => {
backendAPIAxios.get('/history')
.then((response: AxiosResponse<IHistoryResponse>) => {
if (!response.data) {
return alert('Failed to get history');
}
setHistoryState(() => response.data);
})
.catch((e: AxiosError) => {
// alert(`Failed to get history with error: ${e}`);
});
}, [setHistoryState])
console.log(props.history!.reports.map((hist) => <p>{hist.created_at}</p>))
This is the error I'm getting:

You are making IHistory an array of reports objects, when reports is the field with the array. Also, created_at will likely be a string and not a date if it's being returned from the backend.
type Status = "fair" | "unfair";
interface IHistory {
reports: Array<{
id?: string;
status?: Status;
created_at?: string;
}>;
};
const backendHistory: IHistory = {
reports: [
{ id: "123", status: "fair", created_at: new Date().toISOString() },
{ id: "456", status: "unfair", created_at: new Date().toISOString() },
]
};
const result = backendHistory.reports.map(({ id }, _idx) => id);
console.log("result", result);
React code:
import React from "react";
type Status = "fair" | "unfair";
interface IHistory {
reports: Array<{
id?: string;
status?: Status;
created_at?: string;
}>;
}
async function fakeFetch(): Promise<IHistory> {
const backendHistory: IHistory = {
reports: [
{ id: "123", status: "fair", created_at: new Date().toISOString() },
{ id: "456", status: "unfair", created_at: new Date().toISOString() }
]
};
return new Promise((resolve) =>
setTimeout(() => resolve(backendHistory), 1000)
);
}
export default function App() {
const [backendHistory, setBackendHistory] = React.useState<IHistory>();
React.useEffect(() => {
let isAlive = true;
(async function () {
const result = await fakeFetch();
if (isAlive) {
setBackendHistory(result);
}
})();
return () => {
isAlive = false;
};
}, []);
return (
<div className="App">
<h1>Backend History</h1>
{backendHistory ? (
backendHistory.reports.map((hist) => <p>{hist.id}</p>)
) : (
<span>loading</span>
)}
</div>
);
}

Related

Pass object array to function using typescript

I'm trying to pass an object array to another function component using typescript. When I do this I get the error below.
Main.tsx:
interface KeyWords {
key: number;
label: string;
}
export default function Main() {
const [text, setText] = useState<string | "">("");
const [textArray, setTextArray] = useState<KeyWords[]>([]);
const newKeyword = (e: KeyboardEvent<HTMLDivElement | HTMLInputElement>) => {
if (e.key === "Enter") {
setTextArray((prev) => [
...prev,
{ key: textArray.length + 1, label: text },
]);
}
};
}
...
return(
...
<SearchField textArray={textArray}></SearchField>
)
SearchField.tsx:
interface ChipData {
textArray: { key: number; label: string };
}
export default function SearchField({ textArray }: ChipData) {
const [chipData, setChipData] = useState<readonly ChipData[]>(textArray);
...
}
TS2345: Argument of type '{ key: number; label: string; }' is not assignable to parameter of type 'SetStateAction<readonly ChipData[]>'.

Type 'void | undefined' is not assignable to type 'ReactNode' when trying to render string

I have a custom hook which returns an array of objects(navMenuOptions) and a function which can return a specific navMenuOption based on a string.
export const useNavMenuOptions = () => {
const intl = useIntl()
const profile = intl.formatMessage({ id: 'profile.navMenu.profile' })
const addresses = intl.formatMessage({ id: 'profile.navMenu.addresses' })
const myOrders = intl.formatMessage({ id: 'profile.navMenu.myOrders' })
const navMenuOptions: NavMenuOption[] = [
{
id: 0,
label: profile,
icon: BiUser,
url: '/account/profile',
},
{
id: 1,
label: addresses,
icon: BiHomeAlt,
url: '/account/address',
},
{
id: 2,
label: myOrders,
icon: BsBoxSeam,
url: '/account/my-orders',
},
]
const findSelectedOption = (url: string) => {
const selectedOption: NavMenuOption | undefined = navMenuOptions.find(
(option) => option.url === url
)
if (selectedOption?.label) {
return selectedOption?.label
}
return -1
}
return { navMenuOptions, findSelectedOption }
}
I am then trying to use the findSelectedOption() function in a component and render the string it returns
export const NavMenuMobile: React.FC<NavMenuProps> = ({
navMenuOptions,
changeMenuOption,
currentOption,
findSelectedOption,
}) => {
const selectedOption = useMemo(() => {
return findSelectedOption?.(currentOption)
}, [currentOption])
console.log('selected option =>', selectedOption)
return (
<Box p={4} borderBottomWidth="2px" borderBottomColor="shading.200">
{selectedOption}
</Box>
)
}
selectedOption is returning the correct string but I have a TS lint error:
Type 'void | undefined' is not assignable to type 'ReactNode'.
what is the fix here?
React Native really doesn't like undefined being rendered. Try null instead.
const selectedOption = useMemo(() => {
return findSelectedOption?.(currentOption) ?? null
}, [currentOption])
Or:
{selectedOption ?? null}

React Typescript error Type '{ id: number; title: string; function(): void; }' is not assignable to type 'null'

I followed this tutorial to try and recreate a button group with an active on and off state, but I am getting the following error as I am using typescript:
Type '{ id: number; title: string; function(): void; }' is not assignable to type 'null'.
code:
import React from 'react';
import Button from '../button/Button'
const ButtonGroup: React.FunctionComponent = () => {
const [active, setActive] = React.useState({
activeObject: null,
objects: [{
id: 1 as number,
title: '1' as string,
function() {
alert('yes 1')
}
}, {
id: 2 as number,
title: '2' as string,
function() {
alert('yes 2')
}
}, {
id: 3 as number,
title: '3' as string,
function() {
alert('yes 3')
}
}]
})
const toggleActive = (index: number) => {
setActive({ ...active, activeObject: active.objects[index] })
}
function toggleActiveStyles(index: number) {
if (active.objects[index] === active.activeObject) {
return "btn-tertiary"
} else {
return "btn-tertiary-active"
}
}
return (
<> <div >
{active.objects.map((inner, index) => {
return (
<Button variant="btn btn-tertiary" className={toggleActiveStyles(index)} onClick={() => toggleActive(index)} key={inner.id}>{inner.title}</Button>
)
})}
</div>
</>
)
}
export default ButtonGroup;
Any idea's?
useState will default to the "type" of the parameter if you do not specify it directly.
const [active, setActive] = React.useState({
activeObject: null,
/* ... */
});
So in this case, activeObject can only be inferred as being null and nothing else.
Instead, I would suggest defining a type or interface and telling useState that is the type of the state object.
type AO = {
activeObject: { id: number; title: string; function(): void; };
/* and all of your other data */
}
Then
const [active, setActive] = React.useState<AO>({ /* ... */ });
Will specify that active's type is AO. You also then do not need to do the as number or as string as those types will be required to be an AO type.
You are getting the error because of this line
const toggleActive = (index: number) => {
setActive({ ...active, activeObject: active.objects[index] })
}
when you do active.objects[index] you trying to assign an object of type { id: number; title: string; function(): void; } to your activeObject where the initial value is null.
You can provide the type for your state,
type ActiveObject = {
id: number;
title: string;
function(): void;
};
type ButtonGroupState = {
activeObject: ActiveObject | null;
objects: ActiveObject[];
};
And use this type in your useState
const [active, setActive] = React.useState<ButtonGroupState>({ ... })
As per react-docs, in the case of complex state, it is better to use useReducer hook
// Get a hook function
const {useState,useReducer} = React;
function reducer(state, action) {
return {
activeObject: {
id: 1,
title: '1',
function() {
alert('yes 1')
}
}
}
}
const Example = () => {
const [active, setActive] = useReducer(reducer, {
activeObject: null
});
return (
<div>
<p>{JSON.stringify(active)}</p>
<button onClick={() => setActive()}>
Click me
</button>
</div>
);
};
// Render it
ReactDOM.render(
<Example title="Example using Hooks:" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Typescript + React useReducer hook error: Data object not assignable to parameter of type 'never'

I am facing an error at this line
const [current, dispatch] = React.useReducer(Reducer, initialState)
I'm not certain what is the exact error, but based on the message, i interpret it to mean that 'initialState' variable is not strongly typed enough?
No overload matches this call. Overload 1 of 5, '(reducer: ReducerWithoutAction<any>, initializerArg: any, initializer?: undefined): [any, DispatchWithoutAction]', gave the following error.
Argument of type '(state: State, action: Actions) => State | { data: DataType; searchParams: SearchType; }[]' is not assignable to parameter of type 'ReducerWithoutAction<any>'. Overload 2 of 5, '(reducer: (state: State, action: { type: Types; data: DataType; searchParams: SearchType; }) => State | { data: DataType; searchParams: SearchType; }[], initialState: never, initializer?: undefined): [...]', gave the following error.
Argument of type '{ data: { bleData: IbleData[]; bleSessions: IbleSessions[]; bleSummary: IbleSummary[]; message: string; }; searchParams: { PHONE_NUMBER: string; FROM: Date; TO: Date; errorMessage: string; }; }' is not assignable to parameter of type 'never'.
(alias) const Reducer: (state: State, action: Actions) => State | {
data: DataType;
searchParams: SearchType; }[] import Reducer
This is the EventContext.jsx
import * as React from 'react';
import { Reducer, Actions, State, IbData, IbSessions, IbSummary } from "../reducers/EventsReducer"
const initialState: {
data: {
bData: IbData[];
bSessions: IbSessions[];
bSummary: IbSummary[];
message: string;
};
searchParams: {
PHONE_NUMBER: string;
FROM: Date;
TO: Date;
errorMessage: string;
};
} = {
data: {
bData: [],
bSessions: [],
bSummary: [],
message: ""
},
searchParams : {
PHONE_NUMBER: "",
FROM: new Date(),
TO: new Date(Date.now() - 604800000),
errorMessage: ""
}
}
const EventContext = React.createContext<{
current: State;
dispatch: React.Dispatch<Actions>;
}>({
current: initialState,
dispatch: () => null
});
const EventProvider: React.FC = ({ children }) => {
const [current, dispatch] = React.useReducer(Reducer, initialState)
return (
<EventContext.Provider value={{ current, dispatch }}>
{children}
</EventContext.Provider>
)
}
export { EventProvider, EventContext };
This is EventsReducer.tsx
enum Types {
SET_DATA = 'SET_DATA'
}
export interface IbData {
id: string;
datetime: Date,
...
}
export interface IbSessions {
id: string,
day_month: string,
...
}
export interface IbSummary {
total_duration: number,
phone_number: string,
other_phone_number: string
}
export type DataType = {
bData: IbData[];
bSessions: IbSessions[];
bSummary: IbSummary[];
message: string;
}
type SearchType = {
PHONE_NUMBER: string;
FROM: Date;
TO: Date;
errorMessage: string;
}
export type State = {
data: DataType;
searchParams: SearchType;
};
export type Actions =
| { type: Types.SET_DATA;
data: DataType;
searchParams: SearchType;
}
export const Reducer = (
state: State,
action: Actions
) => {
switch (action.type) {
case Types.SET_DATA:
return [
{
data: action.data,
searchParams: action.searchParams
}
];
default:
return state;
}
};
I think you have some [ ] that are too much here
case Types.SET_DATA:
return [
{
data: action.data,
searchParams: action.searchParams
}
];
Or is this intended ?

React testing with Typescript: Passing array of objects as props

I am trying to pass an array of objects to mock component data for testing like so:
const mockPackage = {
id: '1232-1234-12321-12321',
name: 'Mock Package',
price: 8.32,
description: 'Mock description',
globalProduct: {
imageUrl: 'http://imageurl.com',
},
};
const mockPackageData = {
name: 'Package',
packages: [mockPackage],
};
beforeEach(() => {
component = render(
<SuiteContextProvider>
<PackageCard
showDetail={{ display: true, selectedIndex: 1, hideOthers: false }}
handleShowDetail={handleShowDetail}
packageData={mockPackageData}
/>
</SuiteContextProvider>,
);
});
However, I receive the following error:
The component that receives the data destructures the packageData like so:
export interface Package {
id: string;
name: string;
price: number;
description: string;
globalProduct: {
imageUrl: string;
};
}
export interface PackageData {
name: string;
packages: [];
}
type Props = {
packageData: PackageData;
handleShowDetail: (data: DefaultPackageProps) => void;
showDetail: {
display: boolean;
selectedIndex: number | null;
hideOthers: boolean;
};
};
const PackageCard: React.FC<Props> = ({ packageData, handleShowDetail, showDetail }: Props) => {
return (
<>
{packageData.packages.map((packageInfo: Package, index: number) => {
const {
id,
name,
price,
description,
globalProduct: { imageUrl },
} = packageInfo;
Your PackageData defintion should be
export interface PackageData {
name: string;
packages: Package[];
}
You current code packages: []; declares packages to must be an empty array that is why you get the type '0' error.

Resources