React Hooks useState initial empty array value in typescript - reactjs

I'm trying to make typescript happy by giving the correct type. But I can't figure out the exact syntax.
export type CategoriesType = {
id: string;
title: string;
url: string;
courses: CoursesType[];
};
const [singleCategory, setSingleCategory] = useState<CategoriesType>([] as any);
useEffect(() => {
const categories = Categories;
for (const category of categories) {
setSingleCategory(category);
}
}, [singleCategory]);
return (
singleCategory.courses !== [] ? ...
)
This code works. But typescript complains about usage of any. I would prefer to avoid using any.
Other tries:
I get error Property 'courses' does not exist on type 'CategoriesType | []'
const [singleCategory, setSingleCategory] = useState<CategoriesType | []>([]);
Object is possibly 'undefined'.
const [singleCategory, setSingleCategory] = useState<CategoriesType>();

Set state like below.
const [singleCategory, setSingleCategory] = useState<CategoriesType[]>([])
Also in your return singleCategory.courses doesn't make any sense because singleCategory is array type not the object type. You should match your types for state definition and usage.

Related

How to use typescript generics constraints with useState

I am learning Typescript and have a custom React hook similar to this:
import { useEffect, useState } from 'react';
type TypeOne = {
one: string;
};
type TypeTwo = {
two: number;
};
type TypeThree = {
three: {
some: string;
};
}
type AnyPropertyWithString = {
[index: string]: string | AnyPropertyWithString;
};
export function getContent(condition: string): Promise<AnyPropertyWithString> {
return Promise.resolve({ one: 'content' });
}
export default function useContent<T extends AnyPropertyWithString>(
initial: T,
condition: string
): T {
const [content, setContent] = useState<T>(initial);
useEffect(() => {
async function fetchData() {
const data = await getContent(condition);
setContent(data);
}
fetchData();
}, [condition]);
return content;
}
My intention is to provide different types of data to this hook initially and save it in a state. Then fetch new data on some condition and replace the state. I want to restrict the type provided to the hook to AnyPropertyWithString. I want to use generic because the types I will provide could have different properties and multiple levels. The return type should be the same as generic.
I expect to use it like this:
const one: TypeOne = { one: 'content' };
const two: TypeTwo = { two: 2 };
const three: TypeThree = { three: { some: '3' } }
const firstResult = useContent(one, 'condition'); // ok
const secondResult = useContent(two, 'condition'); // expected TS error
const thirdResult = useContent(three, 'condition'); // ok
However, when I try to setContent(data), I have an error:
Argument of type 'AnyPropertyWithString' is not assignable to parameter of type 'SetStateAction<T>'.
Type 'AnyPropertyWithString' is not assignable to type 'T'.
'AnyPropertyWithString' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'AnyPropertyWithString'.
I don't want to cast it to T as I've read that this is a bad practice.
I suspect the problem is that getContent returns the data of the type that can't be effectively matched against, but I thought that generic constraint would narrow down T to AnyPropertyWithString (which is returned by getContent).
I tried useState<T | AnyPropertyWithString>, but then I have a problem with the return type. I also tried different combinations of extends, but none worked.
Could you, please, explain why I have this error and how I can work around it if that's possible?
I really appreciate any help you can provide.
How is getContent supposed to know what T is, and then return data in the format of T?
When you do this:
const hookReturnValue = useContent({ foo: string }, 'condition')
Then the T type in useContent is { foo: string }, which is a subtype of AnyPropertyWithString. getContent returns an entirely different subtype of T.
So if this code ran, then the initial value would set T to { foo: string }, then the effect would run and you would save { one: string } to state, which is not a compatible type.
Then your code would do:
const hookReturnValue = useContent({ foo: string }, 'condition')
hookReturnValue.foo.toUpperCase() // crash when effect completes
This is what is meant by this error:
'AnyPropertyWithString' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'AnyPropertyWithString'.
It's unclear how getContent would work, but it would need to know what kind of data format to return at runtime, and you aren't passing it anything that would let it figure that out.
So if you figure out how getContent will return the same type as the initial argument type of the hook, then the answer will start reveal itself.

Why does TypeScript not throw an error when a variable with wrong type is passed to useState?

Why does the TypeScript not throw an error when a variable is passed to useState? Is there a way I can still type check when passing a variable?
type BuildingNames = [
'farm',
'sawmill',
];
type BuildingType = { [n in BuildingNames[number]]?: number };
interface ITownContext {
buildings: BuildingType;
setBuildings: (buildings: BuildingType) => void;
}
interface TownProviderProps {
children: ReactNode;
}
const defaultValues = {
resourceBuildings: {
farm: 1,
asdfasdf: 5,
},
};
const TownContext = createContext<ITownContext>({} as ITownContext);
const TownProvider = ({ children }: TownProviderProps) => {
// no error
const [buildings, setBuildings] =
useState<BuildingType>(defaultValues.buildings);
// error occurs
const [buildingsDirect, setBuildingsDirect] =
useState<BuildingType>({ asdf: 1 });
return (
<TownContext.Provider
value={{
buildings,
setBuildings,
}}
>
{children}
</TownContext.Provider>
);
};
export { TownContext, TownProvider };
The difference you see between the 2 usages of useState is the effect of TypeScript excess property check, which triggers only when doing an assignment of an object literal (i.e. as opposed to assigning the reference of another variable, like defaultValues.buildings).
The fact that excess property check is not enforced when assigning the reference of another variable, enables a classic OOP usage of passing a subtype (here an object with more properties than strictly necessary).
Still, even when excess property check does not kick in, TS still checks for other errors: should one of the properties not match its type, it will be reported.
const defaultValues = {
buildings: {
farm: "string instead of number",
asdfasdf: 5,
},
};
useState<BuildingType>(defaultValues.buildings) // Types of property 'farm' are incompatible. Type 'string' is not assignable to type 'number'.
// ~~~~~~~~~~~~~~~~~~~~~~~
Playground Link

Type any is not assignable to type never

I'm just learning Typescript and also React, so I am new to this and I tried many things, I'm getting this error at -...attributes-, but I don't know how to set its type when it is in useState.
const [tableAttributes, setTableAttributes] = useState({
attributes: [],
})
const handleChange = (e) => {
// Destructuring
const { value, checked } = e.target
const { attributes } = tableAttributes
console.log(`${value} is ${checked}`)
// Case 1 : The user checks the box
if (checked) {
setTableAttributes({
attributes: [...attributes, value],
})
}
You need to type the state, or else ts does not know what the attributes array is:
const [tableAttributes, setTableAttributes] = useState<{attributes: string[]}>({
attributes: [],
})
Here we tell ts, that tableAttributes is an object with attributes as a string array.
Since I don't know, what the array actually is, you might need to change it to be boolean, number or what you what it to be in this case.

React with Typescript

I'm using typescript with React. Whenever I pass props to function or destructure the props to {text} e.g. I get an error that says "Binding element 'text' implicitly has an 'any' type". Although I can bypass this with using const Example = ({text}:any) => { return () } or const Example = ({text}:{text:string}) => { return () }
it is not efficient for me. I wanted to ask if you can teach me any good way to overcome this without props argument itself but the destructured version of it.
Note: I see that I couldn't explain my problem well. I'm using Typescript because I want to use its type checking functionality. Giving every prop "any" type is ok but when I try to give every prop its own type e.g const Example = ({Text, Number, RandomCrap,}: {Text: string; Number: number; RandomCrap: any;}) => {return ();} It looks like this. Is this the only way?
Answer: Instead of cramming my code like above I can define types elsewhere like
type PropsType = {Text: string; Number: number; RandomCrap: any;};
and use it in my function like const Header = ({ Text, Number, RandomCrap }: PropsType) => {return ()}
Thanks everyone :)
You can define a type for your whole props object:
type PropsType = {
text: string
}
const Example = ({text}: PropsType) => { return () }
// or
const Example1: React.FunctionComponent<PropsType> = ({text}) => { return () }

Typescript error type not assign able to state - usestate

I am new to type script and I am trying to set the state in react using use state hook and type script.
const intialState: State ={
items: [],
value: "",
error: null
}
const [data, setData]= useState([intialState]);
return (
<>
{
data.items.map(x) => (
//my html component to be displayed.
)
}
</>
)
but I am getting the below error.
Can any one tell me.
Is it valid to set an object inside a usestate like I have done?
Why I am getting the typescript error?
First, we should check if there is an interface mismatch between State, and the initialState object. It will help if you can post the type definition of State.
In addition, you should supply the useState hook with the generic type parameter of State[]
const [data, setData]= useState<State[]>([intialState]);
I guess this was what you tried to achieve. Make sure you define your State type correctly, not importing from some other packages
interface State {
items: any[];
value: string;
error: string | null;
}
const MyComponent: (props) => {
const intialState: State ={
items: [],
value: "",
error: null
}
const [data, setData] = useState(intialState)
return (
<>
{
data.items.map(x) => (
//my html component to be displayed.
)
}
</>
)
}
You need to specify type for useState. Since its an array of your state object you can write it like
const [data, setData]= useState<State[]>([intialState]);
Also make sure you define a type for your state's object
interface ErrorType {
status: Number // more keys and their types here
}
interface MyDataType {
items: ItemType[],
error: ErrorType | null,
value: string,
}
post that you can use it with useState like
const [data, setData]= useState<MyDataType[]>([intialState]);
Your data is an array of objects and when you update the state you need to merge the values. For that you can use functional setState
var updatedDataObj = { items: ["a","b","c"], error: "404", value: "xyx", };
setData(prevData => [...prevData, updatedDataObj]);

Resources