how to use prevstate to localstorage in typescript(react) - reactjs

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"));

Related

How can I pass data from custom hook to component?

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/

Typing issue with custom hook that accept any function as parameter, whatever the return type is or arguments are

I wrote a custom hook that accept an async function as parameter.
Everything is working as expected with functions that have no parameter :
type UseAsyncOperationResult<TResult> = {
status: "idle" | "pending" | "success" | "error";
fire: () => Promise<void>;
error?: Error;
data?: TResult;
};
type UseAsyncOperationOptions<TResult> = {
onSuccess?: (result: TResult) => void;
onError?: (error: Error) => void;
autofire?: boolean;
};
export const useAsyncOperation = <TResult>(
operation: () => Promise<TResult>,
options: UseAsyncOperationOptions<TResult> = {}
): UseAsyncOperationResult<TResult> => {
... actual code ...
}
// within a component:
const computeAsync = (x: number, y: number): Promise<number> => {
const result = x * y;
console.log(`Computing ${x}*${y}=${result}`);
return Promise.resolve(result);
};
const [x, setX] = useState(10);
const [y, setY] = useState(10);
const { data, fire } = useAsyncOperation(
() => {
return computeAsync(x, y);
},
{ autofire: true }
);
The idea is to return an object that has a fire property, which has exactly the same signature than the operation parameter, and a data property that is in this example number (data is ready) or undefined (data is not ready)
this works well when sticking on function that has no parameters.
Working sample here: code sandbox.
I'd like to extend the hook to accept any function with any number or arguments.
I tried to type my function like this:
type UseAsyncOperationResult<TResult, TArgs extends never[]> = {
status: 'idle' | 'pending' | 'success' | 'error';
fire: (...args: TArgs) => Promise<void>;
error: Error | undefined;
data: TResult | undefined;
};
type UseAsyncOperationOptions<TResult, TArgs extends never[]> = {
onSuccess?: (result: TResult) => void;
onError?: (error: Error) => void;
autofire?: TArgs;
};
export const useAsyncOperation = <
TResult,
TArgs extends never[],
TOperation extends (...args: TArgs) => Promise<TResult>
>(
operation: TOperation,
options: UseAsyncOperationOptions<TResult, TArgs> = {}
): UseAsyncOperationResult<TResult, TArgs> => {
... actual code ...
}
However, the typings are not properly handled.
For example, if I use:
const computeAsync = useCallback((x: number, y: number): Promise<number> => {
const result = x * y;
console.log(`Computing ${x}*${y}=${result}`);
return Promise.resolve(result);
}, []);
const {
status,
data,
error
} = useAsyncOperation(computeAsync, { autofire: [2, 3] });
The data variable is understood as unknown by typescript, the expected type (should be number)
Another sandbox is available with the extended hook, which is not compiling.
The relevant code in the sandbox is
const computeAsync = (x: number, y: number): Promise<number> => {
const result = x * y;
console.log(`Computing ${x}*${y}=${result}`);
return Promise.resolve(result);
};
// within a component :
const [x, setX] = useState(10);
const { data, fire } = useAsyncOperation(
(y: number) => {
return computeAsync(x, y);
},
{ autofire: [10] }
);
How can I fix my code ?
Here it seems to work as expected :
type UseAsyncOperationResult<TResult, TArgs extends [...any[]]> = {
status: "idle" | "pending" | "success" | "error";
fire: (...args: TArgs) => Promise<void>;
error: Error | undefined;
data: TResult | undefined;
};
type UseAsyncOperationOptions<TResult, TArgs extends [...any[]]> = {
onSuccess?: (result: TResult) => void;
onError?: (error: Error) => void;
autofire?: TArgs;
};
export const useAsyncOperation = <
TResult,
TArgs extends [...any[]]
>(
operation: (...args: TArgs) => Promise<TResult>,
options: UseAsyncOperationOptions<TResult, TArgs> = {},
): UseAsyncOperationResult<TResult, TArgs> => {
throw new Error("NotImplemented");
};
The main difference is to use [...any[]] instead of never[].

How to pass value to Context Provider in TypeScript?

I am trying to convert my react scripts to typescript and having issues defining the types:
export const inputListContext = createContext<null>(null)
import {MyEnum} from "../ts-api-script";
type checkBoxParams = {
myEnum: any
name: any
}
export const Options = () => {
const [greenVals, setGreenVals] = useState(Set<MyEnum>);
const [redVals, setRedVals] = useState(Set<MyEnum>);
const greenValue = {greenVals, setGreenVals};
const redValue = {redVals, setRedVals};
return (
<inputListContext.Provider value={{greenValue, redValue}}>
<Checkbox myEnum={FirmStatusEnum.Op} name={"Option 1"}</Checkbox>
</inputListContext.Provider>
)
}
All I am trying to do is pass the red and green objects as values to my context provider. The issue is coming from typescript.
ERROR:
Type '{ greenValue: any; redValue: { redVals: { new (values?: readonly FirmStatusEnum[] | null | undefined): Set<MyEnum>; new (iterable?: Iterable<MyEnum> | null | undefined): Set<...>; readonly prototype: Set<...>; readonly [Symbol.species]: SetConstructor; }; setRedVals: React.Dispatch<...>; }; }' is not assignable to type 'null'
What are the steps I need to take to declare my types properly and rectify this?
First, I think using Set<MyEnum> for useState initialization does not seem to be ok.
So you can write the Options component like this:
function Options() {
const [greenVals, setGreenVals] = useState(new Set<MyEnum>());
const [redVals, setRedVals] = useState(new Set<MyEnum>());
const greenValue = { greenVals, setGreenVals };
const redValue = { redVals, setRedVals };
return (
<inputListContext.Provider
value={{
redValue,
greenValue,
}}
>
{/* whatever you want to pass */}
</inputListContext.Provider>
);
}
Then let's get to inputListContext. You should declare an interface outside of your Options component representing the inputListContext type, which based on what you mentioned in the question is something like this:
interface inputListContextType {
greenValue: { greenVals: Set<MyEnum>, setGreenVals: Dispatch<SetStateAction<Set<MyEnum>>> };
redValue: { redVals: Set<MyEnum>, setRedVals: Dispatch<SetStateAction<Set<MyEnum>>> };
}
And Next you create inputListContext not with null type and null initial value.
const inputListContext = createContext<inputListContextType>({
greenValue: { greenVals: new Set<MyEnum>(), setGreenVals: () => { } },
redValue: { redVals: new Set<MyEnum>(), setRedVals: () => { } },
});

What type to specify for event handlers tied to multiple fields?

I have a React form with dynamic fields based on this tutorial. It needs to be modified to be TypeScript compatible. I'm getting this error:
"Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{ makeModel: string; year: string; }'."
const handleCarChange = (e: any, index: number) => {
const { name, value } = e.target;
const list = [...carList];
list[index][name] = value; // TODO fix this typescript error
setCarList(list);
};
For using types with click handlers, you can try using MouseEvent from React. Not sure if this solves the typescript error though. It looks different.
import { MouseEvent } from 'react';
const handleCarChange = (e: MouseEvent, index: number) => {
const { name, value } = e.target;
const list = [...carList];
list[index][name] = value; // TODO fix this typescript error
setCarList(list);
};
Further reading.
https://fettblog.eu/typescript-react/events/
This tutorial is bad. And this is only my opinion.
Why ?
First of all, this line const list = [...carList]; does not make any sense.
Here, author of tutorial still mutates the array
list[index][name] = value;
Consider next example:
const list = [{ name: 1 }]
const newList = [...list]
newList[0]["name"] = 2
list // [{name: 2}]
You should never mutate the state. This line [...list] only creates shallow copy. It just new reference to whole array, but each element in the array still have the same reference.
Let's go back to your problem.
type Fn = (args: any) => any
type ChangeHandler = ChangeEventHandler<HTMLInputElement>
type WithIndex<T extends Fn> = (e: Parameters<T>[0], index: number) => void
const Component = () => {
const [inputList, setInputList] = useState<State>([{ firstName: "", lastName: "" }]);
const handleCarChange: WithIndex<ChangeHandler> = (e, index) => {
const { name, value } = e.target;
const newList = inputList.map((elem: Entity, i: number) =>
i === index ? {
...elem,
[name]: value
} : elem)
setInputList(newList);
};
return (
<input
onChange={e => handleCarChange(e, 0)}
/>
)
}
Pls keep in mind, for any mouse handler you alway can use built in types like ChangeEventHandler
Playground
If you want to update your state please refer to official redux patterns

how i can use Generic type in function body

i'm trying to encapsulate some logic inside component OrderGuard ( which can work with two type of orders: CheckinOrder | Checkout order, but, when i'm try to pass order to orderLoad callback, typescript start complain like so
CheckinOrder is assignable to the constraint of type T but T could be instantiated with a different subtype of constaint 'Order'
type Order = CheckoutOrder | CheckinOrder;
interface Props<T extends Order> {
orderId: string;
orderLoaded: boolean;
onOrderLoad: (order: T) => void;
loadOrder: UseCheckinOrder | UseCheckoutOrder;
children?: React.ReactElement;
}
const isCheckinOrder = (order: Order): order is CheckinOrder => {
return !('servicesFallbackURL' in order);
};
const OrderGuard: <T extends Order>(props: Props<T>) => React.ReactElement<Props<T>> = ({
orderId,
orderLoaded,
onOrderLoad,
loadOrder,
children
}) => {
const [userHasAccess, setUserHasAccess] = useState(true);
const { refetch, loading } = loadOrder(orderId, { skip: true });
const handleOrderLoad = (order: Order) => {
if (isCheckinOrder(order)) {
onOrderLoad(order as CheckinOrder); // <-- error here
} else {
onOrderLoad(order as CheckoutOrder); // <-- and here
}
};
i think i miss something but can't figure out what
I'm pretty new in typescript, how others handle this situations?
component call looks like
<OrderGuard<CheckoutOrder>
orderId={orderId}
orderLoaded={!!order}
onOrderLoad={fillOrder}
loadOrder={useOrder}
>
<Checkout startNewSearch={startNewSearch} />
</OrderGuard>
Not making the Prop interface generic will fix your problem in this instance.
type Order = CheckoutOrder | CheckinOrder;
interface Props {
orderId: string;
orderLoaded: boolean;
onOrderLoad: (order: Order) => void;
loadOrder: UseCheckinOrder | UseCheckoutOrder;
children?: React.ReactElement;
}
const isCheckinOrder = (order: Order): order is CheckinOrder => {
return !('servicesFallbackURL' in order);
};
const OrderGuard: (props: Props) => React.ReactElement<Props> = ({
orderId,
orderLoaded,
onOrderLoad,
loadOrder,
children
}) => {
const [userHasAccess, setUserHasAccess] = useState(true);
const { refetch, loading } = loadOrder(orderId, { skip: true });
const handleOrderLoad = (order: Order) => {
if (isCheckinOrder(order)) {
onOrderLoad(order); // <-- no need to cast
} else {
onOrderLoad(order as CheckoutOrder); // <-- no more errors
}
};

Resources