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;
Related
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 = [];
I have an async function getContextUrl(id: string): Promise<string> that will return an url: string for a given id: string. Because the function is async, the url is wrapped in a Promise.
I now want to map a resolved url to the original id that was used. So far I have the following:
const urlMap: {[key: string]: string} = {}; // map to store url->id as key->value
const ids: string = ['foo', 'bar']; // some IDs
const contextUrlPromises: Array<Promise<string>> = ids.map((id: string) => getContextUrl(id));
Promise.all(contextUrlPromises).then((urls: string[]) => {
// what do I do here? How can I map back to the `id` that belongs to this `url`?
})
// my getUrl function
async function getContextUrl(id: string): Promise<string> {
// driver.getUrl is actually the async function from WebdriverIO, but that shouldn't be relevant for my problem
return driver.getUrl();
}
My problem is: once the urls are resolved, because of their asynchronous nature, I don't know which url belongs to which id. As you can see, my approach is to populate a key/value object urlMap with the values. But maybe there's a better way...
Bonus: I actually only need the first resolved url which contains a certain string, so there is room for further optimization because the calls to getContextUrl() can stop, as soon as such an url has been resolved. Something like this:
const contextPromises: Array<Promise<{ id: string, url: string}>> = ids.map((id: string) => getContextUrl(id).then((context: { id: string, url: string}) => {
if (context.url.includes("foo")){
return context.id;
}
});
Promise.race(contextPromises).then((firstId: string) => {
// do something with the first ID whose URL contains "foo"
})
The data returned by Promise.all are in the same order as the data used to called it.
In the following example, we are going to use the index of the url to find the related id.
Playground
// map to store url->id as key->value
const urlMap: {
[key: string]: string;
} = {};
// some IDs
const ids: string[] = ['foo', 'bar'];
// my getUrl function
function getContextUrl(id: string): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 0);
});
}
const contextUrlPromises: Promise<string>[] = ids.map((id: string) => getContextUrl(id));
(async() => {
const urls: string[] = await Promise.all(contextUrlPromises);
urls.forEach((x, xi) => {
console.log(`The urls ${x} belongs to the id ${ids[xi]}`);
});
})();
In JavaScript :
// map to store url->id as key->value
const urlMap = {};
// some IDs
const ids = ['foo', 'bar'];
// my getUrl function
function getContextUrl(id) {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 0);
});
}
const contextUrlPromises = ids.map(id => getContextUrl(id));
(async() => {
const urls = await Promise.all(contextUrlPromises);
urls.forEach((x, xi) => {
console.log(`The urls ${x} belongs to the id ${ids[xi]}`);
});
})();
In the return statement of getContextUrl() you can return an object containing both the id and the url. In the example below I have formatted it similar to urlMap. This way you can add it to the object without any modification.
let urlMap: {[key: string]: string} = {}; // map to store url->id as key->value
const ids: string = ['foo', 'bar']; // some IDs
const contextUrlPromises: Array<Promise<string>> = ids.map((id: string) => getContextUrl(id));
Promise.all(contextUrlPromises).then((urls: string[]) => {
urlMap = {...urlMap, ...urls};
})
// my getUrl function
async function getContextUrl(id: string): Promise<string> {
// driver.getUrl is actually the async function from WebdriverIO, but that shouldn't be relevant for my problem
return {[id]: driver.getUrl()};
}
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
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);
};
I'm creating an interface and i need to run a check function before most methods, repeating the code over and over doesn't seem great. Is there a way I can run a function before a function?
Example of my current code
const uploadObject = async (object) => {
if (!ipfs) noProvider()
const buffer = objectToIpfsBuffer(object)
return ipfs.add(buffer)
}
const uploadString = async (string) => {
if (!ipfs) noProvider()
const buffer = stringToIpfsBuffer(string)
return ipfs.add(buffer)
}
const uploadBuffer = async (buffer) => {
if (!ipfs) noProvider()
return ipfs.add(buffer)
}
...
module.exports = {
uploadObject,
uploadString,
uploadBuffer,
...
}
The function I wish to run before is if (!ipfs) noProvider()
I see no issue with handling this the way you are; however, another approach to "hook" a property accessor is to use a Javascript Proxy.
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
When initializing a Proxy, you'll need to provide two function inputs:
target: A target object (can be any sort of object, including a native array, a function or even another proxy) to wrap with Proxy.
handler: An object which is a placeholder object which contains traps for Proxy. All traps are optional. If a trap has not been defined, the default behavior is to forward the operation to the target.
Here's an example:
const handler = {
get: function(target, prop, receiver) {
console.log('A value has been accessed');
return Reflect.get(...arguments);
}
}
const state = {
id: 1,
name: 'Foo Bar'
}
const proxiedState = new Proxy(state, handler);
console.log(proxiedState.name);
I would probably just do it inline like you are, but to add another tool to your toolbelt: you could create a higher order function which takes in a function and the produces a new function that will do the check, and then do the work.
const checkIpfs = fxn => {
return (...args) => {
if (!ipfs) noProvider();
return fxn(...args);
}
}
const uploadObject = checkIpfs(async (object) => {
const buffer = objectToIpfsBuffer(object)
return ipfs.add(buffer);
});
const uploadString = checkIpfs(async (string) => {
const buffer = stringToIpfsBuffer(string)
return ipfs.add(buffer)
})
const uploadBuffer = checkIpfs(async (buffer) => {
return ipfs.add(buffer)
})
You could use the Proxy Object that intercepts internal operation of other object.
var newObject = new Proxy(yourObject, {
get(target, prop, receiver){
if(['uploadObject', 'uploadString','uploadBuffer'].includes(prop) && type(target[prop]) == typeof(Function)) {
if (!ipfs) noProvider()
return Reflect.get(target, prop, receiver);
}
},
});
newObject.uploadObject();