Argument of type 'never[]' is not assignable to parameter of type 'Comment | (() => Comment)' - reactjs

I've been for some hours tryin to solve this, I've made the code below using as base another component pretty equal from the same project. What am I doing wrong?
The error is exactly what I've put in title: Argument of type 'never[]' is not assignable to parameter of type 'Comment | (() => Comment)'. I think it's something about the interface or the state above, idk.
import React, { useState, useEffect } from 'react';
import { collection, getDocs } from 'firebase/firestore';
import { db } from '../../services/firebase';
import { CommentListDiv } from './styles';
interface Comment {
email: string;
message: string;
name: string;
}
const CommentList = ({ pokemon }: { pokemon: any }) => {
const [comments, setAllComments] = useState<Comment>([]);
const collectionRef = collection(db, `comments-${pokemon.id}`);
const loadAllComments = async (): Promise<any> => {
await getDocs(collectionRef).then((snapshot) => {
const allComments: any = snapshot.docs.map((doc) => doc.data());
console.log(allComments);
console.log(comments);
setAllComments(allComments);
});
};
useEffect(() => {
loadAllComments();
});
return (
<div>
<CommentListDiv>
<h1>Comments about {pokemon.name}</h1>
<h2>Name: {comments.name}</h2>
<h2>E-Mail: {comments.email}</h2>
<p>
Message:
<br />
{comments.message}
</p>
</CommentListDiv>
</div>
);
};
export default CommentList;
This is the return of both console.logs (the return is correct, the exact entry that I've made and that's showing at Firebase too:

useState<Comment>([]);
This says that the state will contain exactly one Comment, but then you try to pass an array in as the initial value. If the state is supposed to store an array of comments, then do:
useState<Comment[]>([]);

Related

react hook with generic interface

i'm trying to write a hook to be used by two of my components-datepicker and dateRangePicker.
Two components extend antd datepicker and rangerpicker, and have the same way of handling states. DatePicker is for selecting a single date and Rangepicker for range of date.
I am trying to extract common logic out of these components and make a custom hook.
Here's my DateRangePicker. Date Picker Looks almost the same.
import { useRef } from "react";
import { DateRangePickerProps } from "./types";
import * as S from "./styles";
import { ReactComponent as CalenderIcon } from "#assets/images/calender.svg";
import Footer from "./Footer";
import { useDatePicker } from "./hooks";
function DateRangePicker({
isError = false,
withFooter = false,
showNextPage = false,
format = "DD/MM/YYYY",
onChange,
defaultValue,
placeholder = ["From", "To"],
...props
}: DateRangePickerProps) {
const wrapperRef = useRef(null);
const {
confirmedValue,
isOpen,
handleOpen,
handleKeyDown,
handleChange,
handleConfirm,
handleDismiss
} = useDatePicker<DateRangePickerProps>(
defaultValue,
onChange,
withFooter,
wrapperRef
); // calling custom hook
return (
<S.Wrapper ref={wrapperRef}>
<S.RangePicker
{...props}
value={props.value || confirmedValue?.date}
open={isOpen}
onOpenChange={handleOpen}
onKeyDown={handleKeyDown}
onChange={handleChange}
isError={isError}
separator={<S.Separator>-</S.Separator>}
suffixIcon={<CalenderIcon />}
format={format}
getPopupContainer={triggerNode => triggerNode}
withFooter={withFooter}
renderExtraFooter={() =>
withFooter && (
<Footer onConfirm={handleConfirm} onDismiss={handleDismiss} />
)
}
showNextPage={showNextPage}
placeholder={placeholder}
/>
</S.Wrapper>
);
}
export default DateRangePicker;
And here's my Custom hook.
I've used Generic parameter which could either be 'DateRangePickerProps' or 'DatePickerProps'
import { useState, useEffect, useRef, RefObject } from "react";
import { DateRangePickerProps, DatePickerProps } from "./types";
type Event = MouseEvent | TouchEvent;
interface SelectedDate<T extends DateRangePickerProps | DatePickerProps> {
date: T['value'] | null
dateString: T["dateString"] | null;
}
export function useDatePicker<T extends DateRangePickerProps | DatePickerProps>(
defaultValue: T['defaultValue'],
onChange: T['onChange'],
withFooter: boolean,
wrapperRef: RefObject<HTMLElement>
) {
const [isOpen, setIsOpen] = useState<boolean>(false);
// confirmedRange, pendingRange 타입 일치 시키기
const [confirmedValue, setConfirmedValue] = useState<SelectedDate<T>>({
date: defaultValue ?? null,
dateString: null
});
const [pendingValue, setPendingValue] = useState<SelectedDate<T>>({
date: null,
dateString: null
});
const sync = useRef(true);
useEffect(() => {
// invoke user-passed onChange
if (confirmedValue.dateString !== null && confirmedValue && !sync.current) {
onChange?.(confirmedValue.date, confirmedValue.dateString);
sync.current = true;
}
}, [confirmedValue, onChange]);
useEffect(() => {
// prevent previous pendingValue from taking over
setPendingValue({ date: null, dateString: null });
}, [isOpen]);
useOnClickOutside(wrapperRef, () => {
if (withFooter) setIsOpen(false);
});
const handleChange: T['onChange'] = (date, dateString) => {
// I get error here; Parameter 'date' implicitly has an 'any' type.ts(7006)
if (withFooter) setPendingValue({ date, dateString });
else {
sync.current = false;
setConfirmedValue({ date, dateString });
}
};
const handleOpen = (internalOpenState: boolean) => {
if (withFooter && !internalOpenState) return;
setIsOpen(internalOpenState);
};
const handleConfirm = () => {
// prevent null(incomplete date range) from taking over
if (pendingValue) {
setConfirmedValue(pendingValue);
sync.current = false;
}
setIsOpen(false);
};
const handleDismiss = () => {
setIsOpen(false);
};
const handleKeyDown: T["onKeyDown"] = key => {
if (key.code === "Escape") {
setIsOpen(false);
}
};
return {
confirmedValue,
isOpen,
handleOpen,
handleKeyDown,
handleChange,
handleConfirm,
handleDismiss
};
}
DateRangePickerProps and DatePickerProps share common properties but with slight difference. For example, 'onChange' is gets parameters of (moment, string) in datePicker, whereas ([moment, moment], [string, string]) is valid in rangePicker.
I'm using following types
import type { DatePickerProps as AntDatePickerProps } from "antd";
import { RangePickerProps as AntRangePickerProps } from "antd/lib/date-picker";
export interface CustomProps {
isError?: boolean;
withFooter?: boolean;
showNextPage?: boolean;
}
export type DatePickerProps = AntDatePickerProps &
CustomProps & { dateString: string };
export type DateRangePickerProps = AntRangePickerProps &
CustomProps & { dateString: [string, string] };
export interface ButtonProps {
size?: "small" | "medium" | "large";
$type?: "primary" | "secondary";
children?: React.ReactNode;
}
export interface FooterProps {
onDismiss: () => void;
onConfirm: () => void;
}
It seems like my approach of generic interface isn't well understood by typescript.
My understanding is that typescript can't figure out which of interface will be passed.
I've left another question regarding this and got an answer, but couldn't implement it.
Generic Function and index access
Can anyone come up with better and error-free approach of dealing with this?
Thanks in advance.

Function and data not appearing on type ContextProps. React Context with TypeScript

I'm getting some vague errors when using React Context with TypeScript to pass things down.
The app works well and displays everything it needs to and the functions work yet TypeScript is yelling at me.
Heres my Context being created.
import React, { createContext, useContext, useEffect, useState } from 'react';
type PokemonProps = {
number: string;
name: string;
image: string;
};
type ContextProps = {
capturedPokemons: PokemonProps[];
catchPokemon: (newPokemon: PokemonProps[]) => void;
releasePokemon: (id: string) => void;
};
const CaughtContext = createContext<ContextProps | null>(null);
export const useCaught = () => useContext(CaughtContext);
export const CaughtProvider: React.FC<ContextProps> = ({ children }) => {
const [capturedPokemons, setCapturedPokemons] = useState<any>([]);
const catchPokemon = (newPokemon: PokemonProps[]) => {
if (capturedPokemons.length >= 6) {
alert('You cannot carry any more Pokemon.');
return;
}
const alreadyCaptured = capturedPokemons.some(
(p: PokemonProps) => p.name === newPokemon[0].name
);
if (alreadyCaptured) {
alert('you already have that pokemon');
return;
}
if (window.confirm('Capture Pokemon')) {
setCapturedPokemons([...capturedPokemons, ...newPokemon]);
}
};
return (
<CaughtContext.Provider
value={{ catchPokemon, capturedPokemons, releasePokemon }}>
{children}
</CaughtContext.Provider>
);
};
When using the above code to use the functions or state in my app for example const { catchPokemon } = useCaught(); I get and error "Property 'catchPokemon' does not exist on type 'ContextProps | null'." How does catchPokemon not exist on type ContextProps when I have created the type here:
type ContextProps = {
capturedPokemons: PokemonProps[];
catchPokemon: (newPokemon: PokemonProps[]) => void;
releasePokemon: (id: string) => void;
};
Once again everything works as expected but Typescript is telling me something is wrong with vague solutions.

Using Context API in TypeScript

I'm creating a context using Typescript for the first time, and I'm having a hard time making it work. Everytime I try to create interfaces and put them in my value prop I get errors, and I really need some help. Here's my context(I'll point the errors in comments)
In the IContext, I don't know how can I put the type of the transactions(it is an array of objects)
I'd like to pass everything in the value prop, the two functions, the transactions array, and the inputs.
anyway, that's my first time applying typescript in a bigger project, so if you guys have any advices on how to practice it better, just let me know.
import React, { createContext, useState, ChangeEvent } from 'react';
interface IContext {
handleInputChange(): void;
handleSubmit(): void;
inputElements: {
name: string;
amount: string;
};
transactions: <-- I don't know what to put here
}
export const TransactionsContext = createContext<IContext | null>(null)
interface ITransaction {
name: string;
amount: string;
}
interface ITransactions {
transactionList: ITransaction
}
export const TransactionsContextProvider: React.FC = ({ children }) => {
const [transactions, setTransactions] = useState<ITransactions[]>([])
const [inputs, setInputs] = useState<ITransaction>({
name: '',
amount: ''
})
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setInputs({...inputs, [name]: value })
}
const handleSubmit = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
setTransactions([...transactions, inputs]) <-- Error here
}
return (
<TransactionsContext.Provider
value={{ transactions, handleInputChange, handleSubmit, inputs }}> <-- Error here
{children}
</TransactionsContext.Provider>
)
}
Error
In the setTransactions:
Argument of type '(ITransaction | ITransactions)[]' is not assignable to parameter of type 'SetStateAction<ITransactions[]>'.
Type '(ITransaction | ITransactions)[]' is not assignable to type 'ITransactions[]'.
Type 'ITransaction | ITransactions' is not assignable to type 'ITransactions'.
Property 'transactionList' is missing in type 'ITransaction' but required in type 'ITransactions'.
You don't need interface ITransactions.
use ITransaction[] as type for transactions
const [transactions, setTransactions] = useState<ITransaction[]>([])
import React, { createContext, useState, ChangeEvent } from 'react';
interface ITransaction {
name: string;
amount: string;
}
interface IContext {
handleInputChange(e: ChangeEvent<HTMLInputElement>): void;
handleSubmit(e: ChangeEvent<HTMLInputElement>): void;
inputElements: {
name: string;
amount: string;
};
transactions: ITransaction[];
}
export const TransactionsContext = createContext<IContext | null>(null)
export const TransactionsContextProvider: React.FC = ({ children }) => {
const [transactions, setTransactions] = useState<ITransaction[]>([])
const [inputs, setInputs] = useState<ITransaction>({
name: '',
amount: ''
})
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setInputs({...inputs, [name]: value })
}
const handleSubmit = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
setTransactions([...transactions, inputs]);
}
return (
<TransactionsContext.Provider
value={{ transactions, handleInputChange, handleSubmit, inputs }}>
{children}
</TransactionsContext.Provider>
)
}

React and TypeScript—which types for an Axios response?

I am trying to present a simple user list from an API which returns this:
[{"UserID":2,"FirstName":"User2"},{"UserID":1,"FirstName":"User1"}]
I do not understand fully how to handle Axios responses with types. The TypeScript error is
Type '{} | { id: number; firstName: string; }' is not assignable to type 'IntrinsicAttributes & UserListProps & { children?: ReactNode; }'.
Property 'items' is missing in type '{}' but required in type 'UserListProps'.
from the <UserList /> element in the Users.tsx file below. Is my User interface wrong?
import React, {useEffect, useState, Fragment } from 'react';
import UserList from './UserList';
import axios, {AxiosResponse} from 'axios';
interface User {
id: number;
firstName: string;
}
const Users: React.FC = (props) => {
const [users, setUserList] = useState<User>();
useEffect(() => {
// Use [] as second argument in useEffect for not rendering each time
axios.get('http://localhost:8080/admin/users')
.then((response: AxiosResponse) => {
console.log(response.data);
setUserList( response.data );
});
}, []);
return (
<Fragment>
<UserList {...users} />
</Fragment>
);
};
export default Users;
Below is my UserList.tsx.
import React, {Fragment } from 'react';
interface UserListProps {
items: {id: number, firstName: string}[];
};
const UserList: React.FC<UserListProps> = (props) => {
return (
<Fragment>
<ul>
{props.items.map(user => (
<li key={user.id}>
<span>{user.firstName}</span>
{/* not call delete function, just point to it
// set this to null in bind() */}
</li>
))}
</ul>
</Fragment>
);
};
export default UserList;
There is generic get method defined in axios/index.d.ts
get<T = never, R = AxiosResponse<T>>(url: string, config?: AxiosRequestConfig<T>): Promise<R>;
Example
interface User {
id: number;
firstName: string;
}
axios.get<User[]>('http://localhost:8080/admin/users')
.then(response => {
console.log(response.data);
setUserList( response.data );
});
I think you are passing list the wrong way to child component.
const [users, setUserList] = useState<User[]>([]);
<UserList items={users} />
interface UserListProps {
items: User[];
};
const UserList: React.FC<UserListProps> = ({items}) => {
return (
<Fragment>
<ul>
{items.map(user => (
<li key={user.id}>
<span>{user.firstName}</span>
</li>
))}
</ul>
</Fragment>
);
};
You need to provide a type argument when calling axios.get if you do not want Axios to infer the type for the value response as any.
And you are passing an incorrect type argument when you useState to create the array of users.
The correct way
interface User {
id: number;
firstName: string;
}
// Initialized as an empty array
const [users, setUserList] = useState<User[]>([]); // 'users' will be an array of users
For example,
import React, {useEffect, useState, Fragment } from 'react';
import UserList from './UserList';
import axios from 'axios';
interface User {
id: number;
firstName: string;
}
// You can export the type TUserList to use as -
// props type in your `UserList` component
export type TUserList = User[]
const Users: React.FC = (props) => {
// You can also use User[] as a type argument
const [users, setUserList] = useState<TUserList>();
useEffect(() => {
// Use [] as a second argument in useEffect for not rendering each time
axios.get<TUserList>('http://localhost:8080/admin/users')
.then((response) => {
console.log(response.data);
setUserList(response.data);
});
}, []);
return (
<Fragment>
<UserList {...users} />
</Fragment>
);
};
export default Users;
If you choose to export the type type TUserList = User[], you can use it in your UserList component as the type for props. For example,
import React, {Fragment } from 'react';
import { TUserList } from './Users';
interface UserListProps {
items: TUserList // Don't have to redeclare the object again
};
const UserList: React.FC<UserListProps> = (props) => {
return (
<Fragment>
<ul>
{props.items.map(user => (
<li key={user.id}>
<span>{user.firstName}</span>
{ /* Do not call the delete function. Just point
to it. Set this to null in bind(). */}
</li>
))}
</ul>
</Fragment>
);
};
export default UserList;

Passing useState as props in typescript

Say I have a parent component with two child components:
const Parent = () => {
const [myVar, setmyVar] = useState(false)
return (
<>
<MyChildComponent1 myVar={myVar} setMyVar={setMyVar} \>
<MyChildComponent2 myVar={myVar} \>
</>
)
}
Now how would I go about setting the type correctly in MyChildComponent2?
This is what I've come up with so far:
const MyChildComponent1 = (
{myVar, setMyVar}:
{myVar: boolean, setMyVar: (value: boolean) => void}) = (...)
Is the type for setMyvar correct? Or should it be something else?
The type that would match the function returned from invoking useState would be:
setMyVar: (value: boolean | ((prevVar: boolean) => boolean)) => void;
If we look at the type definition file from DefinitelyTyped [1], we can see that the second type in the return type is a dispatch:
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
Thus the generic type provided is passed through to SetStateAction<S>, which is defined as:
type SetStateAction<S> = S | ((prevState: S) => S);
So essentially, an interface for your component would be the following:
interface IProps {
myVar: boolean;
setMyVar?: (value: boolean | (prevVar: boolean) => boolean) => void;
}
As #Retsam said, it's best to use React's exported types:
import { Dispatch, SetStateAction } from "react";
interface IProps {
myVar: boolean;
setMyVar?: Dispatch<SetStateAction<boolean>>;
}
References:
[1] https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L845
Dispatch & SetStateAction types
As #Retsam mentioned, you can also import and use the types Dispatch and SetStateAction from React:
import React, { Dispatch, SetStateAction } from 'react';
const MyChildComponent1 = (
myVar: boolean,
setMyVar: Dispatch<SetStateAction<boolean>>
) => {...};
Bonus
When I find myself frequently using this, I create a type alias to help with readability
import React, { Dispatch, SetStateAction } from 'react';
type Dispatcher<S> = Dispatch<SetStateAction<S>>;
const MyChildComponent1 = (
myVar: boolean,
setMyVar: Dispatcher<boolean>,
) => {...};
hope this helps.
Adding onto #fiz's comment, his code of block was slightly not working for me:
import React, { Dispatch, SetStateAction } from 'react';
const MyChildComponent1 = (
myVar: boolean,
setMyVar: Dispatch<SetStateAction<<boolean>>
) => {...};
I had to set setMyVar: Dispatch<SetStateAction<boolean>> (there was one too many brackets)
It can also be done like this using Interface and React components.
MainComponent.tsx
Value assignment of the useState component defined in Main is done in
the child component. When this field is triggered, the code in useEffect will run.
import React, { useEffect } from 'react';
import Login from './views/Login/index';
const App: React.FunctionComponent = () => {
const [isAuthenticated, setAuthenticatedStatus] = React.useState(false);
useEffect(() => {
if (isAuthenticated)
window.location.href = "<redirect url>";
}, [isAuthenticated]);
return (<Login setLoginStatus={setAuthenticatedStatus} />)
};
export default App;
ChildComponent.tsx
import { Dispatch, SetStateAction, FunctionComponent } from 'react';
import authService from '../../apiServices/authService';
interface IProps {
setLoginStatus: Dispatch<SetStateAction<boolean>>;
}
const Login: FunctionComponent<IProps> = (props: IProps) => {
const login = async (username: string, password: string) => {
const response = await authService.authenticateUser(username, password);
if (response && response.statusCode == 200 && response.result.accessToken) {
props.setLoginStatus(true);
}
else {
// ...
}
};
return (
<>
...
</>
);
};
export default Login;
I found the other answers slightly confusing, so here's what worked for me:
Instantiate useState() in parent.ts:
const [someState, setSomeState] = useState<stateType>(initialState)
Pass in setSomeState() to child.ts:
<Child
setSomeState={setSomeState}
/>
Make sure to only pass the name without the ()
In child.ts set Props like so:
import Dispatch from 'react'
interface Props {setSomeState: Dispatch<stateType>}
MyChildComponent1.tsx
set type Props like this:
import React from 'react'
type Props = {
myVar:boolean;
setmyVar: React.Dispatch<React.SetStateAction<boolean>>;
}
const MyChildComponent = ({myVar, setmyVar}: Props) => {
... YOUR CODE ...
}

Resources