Having issues with typescript in react - javascript

I am having issues with typescript react in when try to define a value.
Here is the error:
Argument of type '{}' is not assignable to parameter of type
'SetStateAction<boolean[]>'. Type '{}' is not assignable to type
'(prevState: boolean[]) => boolean[]'.
Type '{}' provides no match for the signature '(prevState: boolean[]): boolean[]'.ts(2345)
Here is my following code:
const [addButtonFruits, setAddButtonFruits] = useState([true]);
useEffect(() => {
const initialAddButtonFruits = {};
fruitsArray.map((each) => (initialAddButtonFruits[each] = true));
setAddButtonFruits(initialAddButtonFruits);
}, []); // only executed for initial rendering

Error message says exactly what is wrong. Your state variable addButtonFruits is a boolean array. And you are trying to assign an object to it.
Try:
const initialAddButtonFruits = fruitsArray.map(each => true);
setAddButtonFruits(initialAddButtonFruits);
If you want to store an object:
const [addButtonFruits, setAddButtonFruits] = useState({});
useEffect(() => {
const initialAddButtonFruits = {};
fruitsArray.forEach((each) => (initialAddButtonFruits[each] = true));
setAddButtonFruits(initialAddButtonFruits);
}, []); // only executed for initial rendering

addButtonFruits is a boolean array.
You can't assign an empty object to a boolean array variable.
const initialAddButtonFruits = [];

Related

Argument of type 'void' is not assignable to parameter of type 'SetStateAction<never[]>'

I am getting the following error:
Argument of type 'void' is not assignable to parameter of type 'SetStateAction<never[]>'.
My relevant code looks like this:
const ViewEvents: React.FC<ViewEventsProps> = ({ navigation }) => {
const flatListRef = useRef(null);
const [limit] = useState(5);
const [page, setPage] = useState(1);
const [clientData, setClientData] = useState([]);
const [serverData, serverDataLoaded] = useState([]);
const [pending_process, setPending_process] = useState(true);
const [loadmore, setLoadmore] = useState(false);
const [refresh, setRefresh] = useState(false);
const getEvents = async (thePage: any) => {
try {
const value = await AsyncStorage.getItem('user_id');
if (value !== null) {
// We have data!!
const user_id = JSON.parse(value)
const response = await APIKit.get('/v1/events/user-events/' + user_id)
.then((response) => {
console.log(response.data)
return response.data.slice((thePage - 1) * limit, thePage * limit);
}, (error) => {
console.log(error);
});
}
} catch (error) {
// Error retrieving data
}
}
const requestToServer = async (thePage: any) => {
let data = await getEvents(thePage);
console.log('data', data);
serverDataLoaded(data);
};
Obviously data is not being initialized with the data returned from the getEvents function. What I cannot figure out is why? I am using TypeScript and so far no errors are being thrown from the getEvents function.
I am new to React and TypeScript so I might be missing something and so far all the similar questions I have seen online do not seem to be relevant to my error.
First of all let's look at SetStateAction<never[]>. If you hover over serverData in this line:
const [serverData, serverDataLoaded] = useState([]);
It will tell you the type of serverData is never[]. The problem is that [] is not enough information for typescript to know what should into that array, since there is no contents to infer types from. To do that you need to pass in a type parameter to useState.
I dont know the shape of your data, but something like:
const [serverData, serverDataLoaded] =
useState<{ id: number, name: string }[]>([]);
Now if you hover over serverData you should see:
const serverData: {
id: number;
name: string;
}[]
And if you hover over serverDataLoaded you should see:
const serverDataLoaded: React.Dispatch<React.SetStateAction<{
id: number;
name: string;
}[]>>
So now that function is a React set state action that takes the type you defined.
Now let's look at the other side of your problem: Why is that value you're trying to assign void?.
If you hover over getEvents, you should see the inferred return type of that function. You should see this:
const getEvents: (thePage: any) => Promise<void>
So typescript believes that the function returns a promise, that resolve to void. That means that typescript didn't see a return statement in your function.
Why is that? Well, the only return statement is in a nested function. You seem to have mixed up async/await and the promise based .then() style. You should never use both of these. In this case you only need to await the call to the api, and then use whatever it returned:
const response = await APIKit.get('/v1/events/user-events/' + user_id)
console.log(response.data)
return response.data.slice((thePage - 1) * limit, thePage * limit);
Now that return statement is hit in the normal function flow, and now in a nested function.
See: How to return a result properly from async/await function? for more information on this.
Now, after all that, getEvents returns a value instead of void, and serverData and serverDataLoaded both have the right types, and it should work as you expect.
See typescript playground
Debug your execution of getEvents function where you have mixed up both async await and .then handling of promise

Dynamically set multiple object properties using variable

I wanna set multiple object property with the same value.
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
const handler = (state, payload) => {
return {
...state,
data: payload
};
};
const object = {
[SHOW_BUSINESS_DIALOG]: handler,
[SHOW_PAYMENT_DIALOG]: handler,
};
As above example, I have to manually assign handler for 2 property SHOW_BUSINESS_DIALOG & SHOW_PAYMENT_DIALOG
Is there anyway to set it quickly on the fly by js api but no need to introduce a new function to handle that like
const object = {
[SHOW_BUSINESS_DIALOG, SHOW_PAYMENT_DIALOG]: handler,
};
You could use Object.fromEntries() with .map() if you're okay with having the same reference to handler for each value... (the snippet console output shows how the handler method is the same reference for each value, that's why it looks a little strange):
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
const handler = (state, payload) => {
return {
...state,
data: payload
};
};
const keys = [SHOW_PAYMENT_DIALOG, SHOW_BUSINESS_DIALOG];
const object = Object.fromEntries(keys.map(k => [k, handler]));
console.log(object);
Please note that .fromEntries() is currently in draft mode, however, I think a generic if statement accompanied with a Set (using .has()) would be better for this case than using an object:
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
const handler = (state, payload) => {
return {
...state,
data: payload
};
};
const get_handler = key => {
const keys = new Set([SHOW_PAYMENT_DIALOG, SHOW_BUSINESS_DIALOG]);
if(keys.has(key))
return handler;
}
console.log(get_handler(SHOW_BUSINESS_DIALOG)); // hanlder
console.log(get_handler("foo")); // undefined
You can create a generic function and pass an array of key and loop through keyArr and place value for each key
const SHOW_PAYMENT_DIALOG = 'SHOW_PAYMENT_DIALOG';
const SHOW_BUSINESS_DIALOG = 'SHOW_BUSINESS_DIALOG';
let value = "some value"
const object = { someKey: 'value'};
let dynamicSetValues = (keyArr, value) => {
keyArr.forEach(key => {
object[key] = value
})
}
dynamicSetValues(['SHOW_PAYMENT_DIALOG','SHOW_BUSINESS_DIALOG'], value)
console.log(object)
Note:- this mutates original object, if you want immutability you can make a copy of object and place value on desired keys and return a new object every time from function

How to preserve Typescript return types when calling Promise.all?

I have the following code where i have two functions which return objects of two different types (InterfaceA and InterfaceB)
Now, when i call Promise.all on them, i cant define the type of promises variable so i have to go with any.
interface InterfaceA {
value: string;
}
interface InterfaceB {
value: number;
}
const returnA = (): Promise<InterfaceA> => {
const obj: InterfaceA = {
value: 'a'
};
return Promise.resolve(obj);
};
const returnB = (): Promise<InterfaceB> => {
const obj: InterfaceB = {
value: 1
};
return Promise.resolve(obj);
};
const promises: any = [returnA(), returnB()];
const res = await Promise.all(promises);
console.log(res)
const objA: InterfaceA = res[0];
The problem is that once i get the response of Promise.all, and try to define the types of the responses, i get the following errors:
Type '{}' is not assignable to type 'InterfaceA'.
Property 'value' is missing in type '{}'. (2322)
Type '{}' is not assignable to type 'InterfaceA'. (2322)
I am not exactly sure how to deal with this and what i can do to preserve the type of the objects that i get in the response of Promise.all?
Thanks!
The problem is that promises type isn't precisely inferred as (Promise<InterfaceA> | Promise<InterfaceB>)[].
It can be properly typed with tuple type:
const promises: [Promise<InterfaceA>, Promise<InterfaceB>] = [returnA(), returnB()];
const res = await Promise.all(promises);
This problem can be avoided by eliminating promises temporary variable:
const res = await Promise.all([returnA(), returnB()]);
I didnt fully understand you intention, what i understood is that you wanted to achive something like this with the as operator.
const returnA = (): Promise<InterfaceA> => {
const obj: InterfaceA = {
value: 'a'
};
return Promise.resolve(obj as InterfaceA);
};

How to add "fallback" value to optional function argument in flow?

I have following function utalising flow types
push = (pathname: string, data?: Object) => {
const history = [...this.state.history, pathname];
this.setState({
history,
pathname,
data
});
};
In normal javascript I would be able to do something like (pathname, data = null) where null would be used for data if it wasn't provided, but I can't figure out syntax for this when using flow types.
let push = (pathname: string, data?: Object = {a: 1}) => {
// ...
};
In addition, you can also use default values with destructuring together:
type Arg = { prop: number };
const func = ({ prop = 1 }: Arg) => prop;

Testing for valid objectId

I'm using jest to test a mongoDB ObjectID.
In the function I check if the id is valid. Then I return the ObjectId.
In my test I'm expecting to get a string using valueOf() of the ObjectId, but the test is failing:
import { ObjectId } from 'mongodb'
const safeObjectId = (id) => {
return ObjectId.isValid(id) ? new ObjectId(id) : null
}
it('should return ObjectId if it is valid', () => {
const result = safeObjectId('5a1154523a6bcc1d245e143d')
expect(typeof result).toBe('object')
expect(result.valueOf()).toEqual('5a1154523a6bcc1d245e143d')
})
But I do get the error
Expected value to equal:
"5a1154523a6bcc1d245e143d"
Received:
"5a1154523a6bcc1d245e143d"
Difference:
Comparing two different types of values. Expected string but received object.
You need to access the 'str' attribute:
const safeObjectId = (id) => {
return ObjectId.isValid(id) ? new ObjectId(id) : null
}
it('should return ObjectId if it is valid', () => {
const result = safeObjectId('5a1154523a6bcc1d245e143d')
expect(typeof result).toBe('object')
expect(result.str).toEqual('5a1154523a6bcc1d245e143d')
})
From the docs... https://docs.mongodb.com/manual/reference/method/ObjectId/
Access the str attribute of an ObjectId() object, as follows:
ObjectId("507f191e810c19729de860ea").str
p.s. It's bad practice to have multiple assertions in a single test, I would move the object check and value check into two separate tests like so...
const safeObjectId = (id) => {
return ObjectId.isValid(id) ? new ObjectId(id) : null
}
it('should return an object', () => {
const result = safeObjectId('5a1154523a6bcc1d245e143d')
expect(typeof result).toBe('object')
})
it('should return the correct ObjectId', () => {
const result = safeObjectId('5a1154523a6bcc1d245e143d')
expect(result.str).toEqual('5a1154523a6bcc1d245e143d')
})

Categories