How can I pass data from custom hook to component? - reactjs

I have custom hook which pass parameter data with fetched data to component Settings. There I have hook setData11 in useEffect and I want to set in that hook data passed from useTable but I get error
Argument of type 'Data | Column[] | undefined' is not assignable to parameter of type 'SetStateAction<DataTable[] | undefined>'.
Type 'Data' is not assignable to type 'SetStateAction<DataTable[] | undefined>'.
Type 'Data' is missing the following properties from type 'DataTable[]': length, pop, push, concat, and 29 more.ts(2345)
interface DataTable {
[id: string | number]: string | number;
}
interface Data {
[category: string]: DataTable[];
}
const urls: string[] = [
"https://jsonplaceholder.typicode.com/posts",
"https://jsonplaceholder.typicode.com/comments",
"https://jsonplaceholder.typicode.com/albums",
"https://jsonplaceholder.typicode.com/photos",
"https://jsonplaceholder.typicode.com/todos",
];
const useTable = (idurl: number, actualcategory: string) => {
const [data, setData] = useState<Data>();
const [columns, setColumns] = useState<Column[]>();
const loadDatabase = () => {
fetch(urls[idurl])
.then((response) => response.json())
.then((response) => {
setData({
...data,
new: response.filter((t: DataTable[], i: number) => {
return i > 50 && i < 100 && t;
}),
removed: response.filter((t: DataTable[], i: number) => {
return i > 100 && i < 150 && t;
}),
postponed: response.filter((t: DataTable[], i: number) => {
return i > 50 && i < 100 && t;
}),
});
const objcolumn: Column[] = Object.keys(response[0]).map(
(t: string) => {
let d: Column = { col: { title: t, disp: true } };
return d;
}
);
setColumns(objcolumn);
});
};
useEffect(() => {
loadDatabase();
alert(JSON.stringify(data));
}, []);
return [data, columns];
};
export { useTable };
const Settings = () => {
const [data, columns, checkall, changeDatabase] = useTable(1, "new");
const[ data1, setData1]=useState<DataTable[]>()
useEffect(() => {
setData1(data)
}, []);
return <></>
}

It's not clear to me what this code is supposed to do, so it's difficult to give you a specific answer.
Your example contains several mistakes.
The first one can be confusing. When you return an array from your custom hook, its type will be inferred as Array<Data | Column[]>. If you want to get a signature similar to useState, you should use as const to tell TypeScript that this array contains a specific number of elements in a specific order and cannot change.
But even after you specify as const, TypeScript will complain about two things:
The number of elements that you are trying to unpack from useTable should correspond to the two that you are returning (possibly just a typo). So it's should be const [data, columns] = useTable(...)
The type Data | undefined and DataTable[] | undefined are incompatible, which is entirely true. I'm not entirely sure what your end goal is. If you want to use a specific category as the data1 state, you should pass some attribute of data
const useTable = (idurl: number, actualcategory: string) => {
...
return [data, columns] as const; // use "as const"
};
// --------------
const Settings = () => {
// Remove extra parameters
const [data, columns] = useTable(1, "new");
const [data1, setData1] = useState<DataTable[]>()
useEffect(() => {
// data also can be undefined, so you need to use ?. syntax
setData1(data?.SOME_CATEGORY)
}, []);
return <></>
}

thanks for answer :)
here is resolution of the problem
https://fettblog.eu/typescript-react-typeing-custom-hooks/

Related

RTK Query UseLazyQuery and Typescript: how to get the correct type from hook argument

I need to create a react hook that accepts a RTK Query UseLazyQuery function (from any endpoint), apply a debounce to it and return the trigger function and results.
Everything is working as expected but I can't manage to get the correct type to the data returned by it.
As "UseLazyQuery" type requires a type for its generic type and I don't know what type it'll be, I set it to "any", but it makes the data to be typed as "any" too.
I need help to get/extract the correct type from the given "UseLazyQuery" function given to the hook and make the returned data have the correct type.
export default function useSearch<T extends UseLazyQuery<any>>(lazyQueryFn: T) {
const [ trigger, results ] = lazyQueryFn()
const getParamsAndTrigger = async (
params: ISearchParamsRequest,
filterParams?: (params: ISearchParamsRequest) => ISearchParamsRequest
) => {
await trigger(filterParams ? filterParams(params) : params, true)
}
const debouncedTrigger = useDebouncedFn(getParamsAndTrigger, 1500)
return [debouncedTrigger, { ...results }] as const
}
The "results" const returned by "lazyQueryFn" has the type "UseQueryStateDefaultResult". I need it to have the right data type.
I had a similar situation this days and I came with this approach which fixed it and should help you too:
interface ResponseData<V> {
items: V[];
cursor: string;
}
export const useGetCursorPaginatedData = <
V,
T extends UseLazyQuery<QueryDefinition<any, any, any, ResponseData<V>, any>>
>(
lazyQuery: T
) => {
const [trigger] = lazyQuery();
const { onTriggerAction, isLoading, error } = useAsyncAction();
const [items, setItems] = useState<V[]>([]);
const [newCursor, setNewCursor] = useState<string | undefined>();
const getData = async (isRefresh?: boolean) => {
const tempList = isRefresh ? [] : items;
const { data } = await trigger(isRefresh ? undefined : newCursor);
setItems([...tempList, ...data?.items]);
setNewCursor(data?.cursor);
};
const onLoadMore = useCallback(() => {
getData();
}, [newCursor]);
const onRefresh = () => {
onTriggerAction(async () => getData(true));
};
useEffect(() => {
onTriggerAction(getData);
}, []);
return {
items,
isLoading,
isError: error != null,
onLoadMore,
onRefresh
};
};
And this is how to use it:
const {
items: dailyTweets,
isLoading,
isError,
onLoadMore,
onRefresh
} = useGetCursorPaginatedData<DailyTweet, typeof useLazyGetDailyTweetsQuery>(
useLazyGetDailyTweetsQuery
);
Basically what you need extra is to define an interface for the QueryDefinition Response, in my case ResponseData and add one more generic type to function definition that is the type of the returned data in your response.
I hope this will help you.

how to use prevstate to localstorage in typescript(react)

type allergyProps = {
filterAllergy: allergyArray[];
setRemoveAllergy: Dispatch<SetStateAction<string>>;
setSearchAllergy: Dispatch<SetStateAction<string>>;
};
type allergyArray = {
id: number;
name: string;
};
const Allergy = ({ setSearchAllergy, filterAllergy }: allergyProps) => {
const [isInput, setIsInput] = useState(false);
const [removeAllergy, setRemoveAllergy] = useState<string[]>([]);
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value) {
setIsInput(true);
} else {
setIsInput(false);
}
setSearchAllergy(e.target.value);
};
const click = (e: React.MouseEvent<HTMLButtonElement>) => {
let value = e.currentTarget.name;
setRemoveAllergy([...removeAllergy, value]);
localStorage.setItem("allergy", removeAllergy); << #####error point !#######
};
error name is
Argument of type 'string[]' is not assignable to parameter of type 'string'.
29 | let value = e.currentTarget.name;
30 | setRemoveAllergy([...removeAllergy, value]);
> 31 | localStorage.setItem("allergy", removeAllergy);
| ^^^^^^^^^^^^^
32 | };
i want use to prevState for setItem,,
how to use prevState of array type?
localStorage cannot hold any data type except for strings. If you would still like to force it, you can convert the array into a string and then place it in localStorage as follows:
localStorage.setItem("allergy", JSON.stringify(removeAllergy));
When you want to read from localStorage, you would need to do this to get it back into the form of an array:
JSON.parse(localStorage.getItem("allergy"));

How to check "Type" user passed in custom hook in React typescript?

I am creating a custom hook to fetch json from api. I am using this hook for different type of data. So i need to set the type whenever i use this hook. Based on the type I use hook should parse data and return the parsed data which should contain data of our desired type.
const useSearch = <T>(url: string) => {
const [data, setData] = useState<T[]>([])
const fetchData = React.useCallback(
(searchText: string): void => {
fetch(url).then(res=>res.json()).then(({ data }) => {
//check if T is Type1, Type2, Type3
// all types parse their data using different functions
//I need to check the type of T
setData(parseData(data))
}).catch(error z=> {
console.log(error)
})
},
[],
)
return [data, fetchData] as const
}
But I am unable to find a way to check the type of T and use the corresponding parsing function.
You could try using type predicates:
type MyDataType = {
foo: 'bar'
}
type MyOtherDataType = {
baz: 'biz'
}
function isMyDataType(obj: MyDataType | MyOtherDataType): obj is MyDataType {
return (obj as MyDataType).foo !== undefined;
}
function isMyOtherDataType(obj: MyDataType | MyOtherDataType): obj is MyOtherDataType {
return (obj as MyOtherDataType).baz !== undefined;
}
// Now your hook...
const useSearch = <T>(url: string) => {
const [data, setData] = useState<T[]>([])
const fetchData = React.useCallback(
(searchText: string): void => {
fetch(url).then(res=>res.json()).then(({ data }) => {
if (isMyDataType(data)) {
// TS will treat data as MyDataType
myUniqueDataHandler(data);
} else if (isMyOtherDataType(data)) {
// TS will treat data as MyOtherDataType
myOtherUniqueDataHandler(data);
}
setData(parseData(data))
}).catch(error z=> {
console.log(error)
})
},
[],
)
return [data, fetchData] as const
}
Alternatively, you could add a "type" field to the data objects that are returned by your API and have an easy check at runtime. Type predicates have the advantage of being type safe (or, at least, as type safe as your predicates); in other words, TS will treat your data as the type you've narrowed to AND Javascript will perform the same check at runtime so you can run your unique handlers.

How to correctly set react useState using generics

Core question - where am I messing up my useState
const [data, setData] = useState<IAssetGetManyResponseDto<IAssetResponseDto>>();
so that the data object I'm sending down as props is undefined?
My (functioning) api call looks like this
export const getAllAssets = async (
): Promise<IAssetGetManyResponseDto<IAssetResponseDto>> => {
console.log("get all assets ran in api.ts!")
const response = await amsApiClient.get(ASSET_ENDPOINT(),
{
params: {
limit: 1000
}
}
);
console.log("logging response data "+JSON.stringify(response.data))
return response.data;
};
Here are the types being used:
export interface IAssetGetManyResponseDto<T> {
items: T[];
totalCount: number;
}
export interface IAssetResponseDto {
id: string | null;
name: string | null;
type: string | null;
url: string | null;
// metadata: IMetadataObj| null;
tags: Array<string> | null;
thumbnailImageURL: string | null;
createdAt: number | null;
updatedAt: number | null;
}
And here is the component that makes the call, to show context
export const AssetManagementTable = () => {
const [data, setData] = useState<IAssetGetManyResponseDto<IAssetResponseDto>>();
const getAssets = async () => {
console.log("getAssets ran!")
const assets = await getAllAssets();
console.log("logging data from asset managment table " + data)
console.log("logging assets from asset managment table " + assets)
setData(assets)
}
useEffect(() => {
getAssets()
}, []);
return (
<div>
<NewAssetTable items={data} />
</div>
);
};
Elsewhere in my app, when I pass props down and refer to props.items.map((item: any). => {... I'm getting undefined.
More specifically, TypeError: Cannot read properties of undefined (reading 'map')
Am I messing up the argument to useState by improperly specifying types?
Thanks!
Looks like type definitions for React.useState are little unsafe. They don't augment return type with undefined for variant with no arguments.
So, if you told that state will always contain IAssetGetManyResponseDto<IAssetResponseDto>, TS compiler believes it.
Hover on data to see it has IAssetGetManyResponseDto<IAssetResponseDto> while it should be IAssetGetManyResponseDto<IAssetResponseDto> | undefined.
Workaround:
useState<IAssetGetManyResponseDto<IAssetResponseDto> | undefined>()
useState<IAssetGetManyResponseDto<IAssetResponseDto>>(someFunctionalMockOfYourData)

React with TypeScript: how to type useState hook with previous state

I have a React + TS application and I'm writing a custom hook to update the header of a table. Basically, I can click on different checkboxes and hide/show that specific column of the table.
const useHeader = (headerConfiguration: TableHeader[]) => {
const [header, setHeader] = useState(headerConfiguration);
const updateHeader = (column: string) => {
setHeader(prevState => { // <- this line has the error
prevState.forEach(el => {
if (el.name === column) el.visible = !el.visible;
});
});
};
return {
header,
updateHeader,
};
};
export default useHeader;
As the code shown, I need to update the visible property based on the column name.
Currently I have an error tough:
Argument of type '(prevState: TableHeader[]) => void' is not
assignable to parameter of type 'SetStateAction<TableHeader[]>'.
Type '(prevState: TableHeader[]) => void' is not assignable to type
'(prevState: TableHeader[]) => TableHeader[]'.
Type 'void' is not assignable to type 'TableHeader[]'.
I get what the error is saying but I'm not sure how to fix it, if I write the same function using the library Immer it works without any error:
setHeader(
produce(header, draftHeader => {
draftHeader.forEach(el => {
if (el.name === column) {
el.visible = !el.visible;
}
});
}),
);
How can I fix this?
When not using immer, you're responisble to produce and return a new state. So instead of manipulating prevState you need to create a copy of it, with the changes you require applied.
For example, with something like this:
const updateHeader = (column: string) => {
setHeader(prevState => {
// return a copy prevState
return prevState.map(
el => el.name === column
? {...el, visible: !el.visible} // copy inner item, if changed
: el;
);
});
};
Please try this:
const useHeader = (headerConfiguration: TableHeader[]) => {
const [header, setHeader] = useState(headerConfiguration);
const updateHeader = (column: string) => {
header.forEach(el => {
if (el.name === column) el.visible = !el.visible;
});
setHeader(header);
};
return {
header,
updateHeader,
};
};
export default useHeader;

Resources