Unable to spread an array inside a template literal - reactjs

I have the following component where I am trying to pass in values
meant to be used for linear gradient values in my styling.
colors is an array thus expecting it to be spreadable as follows.
...gradientProps.colors
But this doesn't work with following error:
TS2349: This expression is not callable.   Type 'Element' has no call
signatures.
New to typescript and googling around shows spread operator works over an array.
What am I doing wrong here?
interface GradientProps {
angle?: string;
colors?: string[];
}
interface Props {
// 10 other props
gradientProps?: GradientProps;
}
const MyComponent = ({
// 10 other props
gradientProps,
}: Props): React.ReactElement => {
return (
<div
style={{
// Error is with ${...gradientProps.colors}
backgroundImage: `url('www.aaa.com/some.png') linear-gradient(${gradientProps.angle}, ${...gradientProps.colors})`,
}}
>
{(<div>{''}</div>)}
</div>
);
};
With the props filled up after spreading, expecting the backgroundImage's value to look as follows:
background-image: linear-gradient(45deg, red, yellow, blue);

The spread syntax is not a standalone construct. It can only be used in certain places. I'm rather partial to MDN's documentation on it:
Spread syntax (...) allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.
For this case, getting a comma-delimited list out of an array, is taken care of by good old join, which by default, uses comma as a delimiter.
backgroundImage: `url('www.aaa.com/some.png') linear-gradient(${gradientProps.angle}, ${gradientProps.colors.join()})`,

Related

Typescript: Member of union type with incompatible signature when using find on array of objects

I want to check if a value exists in an array of objects.
My array looks something like this:
[
0: {
id: 'unique_obj_id',
item: {
id: 'unique_item_id',
...
},
...
},
1: {...}
]
The objects in the array can be one of many interface types (depending on my api call, here: resource strings represent these interfaces, which are defined somewhere else). But one array will always consist of the same interface types for a given data request.
I'm trying to write a reusable function to check whether given_id exists for any object in the array for obj.item.id.
So far I've managed to write the correct logic but typescript throws some exceptions that I can't seem to figure out. shopApprovalData is a collection of interfaces each following the above object structure accessible by the indices of resource.
export type ApprovalResource = 'str1' | 'str2' | 'str3' | 'str4' | 'str5';
export const checkApprovalItem = (given_id: string, resource: ApprovalResource) => {
const shopApprovalData = useShopApprovals();
if (shopApprovalData && shopApprovalData[resource]) {
const resourceApprovalData = shopApprovalData[resource];
if (resourceApprovalData.find(e => e.item.id === id)) {
return true;
}
}
return false;
}
Typescript shows me that shopApprovalData and itemApprovalData is possibly undefined and that the expression find() is not callable since signatures of members of the union type are not compatible with each other. (Apparently, some removes the error, why?)
What approach should I choose instead to check whether the given_id exists in any object of the array?
Based on your usecase, i create this code sandbox: https://codesandbox.io/s/stackoverflow-answer-ke9drk?file=/src/index.ts
explanation:
Type wont compile, so we need something to mark what the interface of the object.
Also i may confused by your code shopApprovalData[resource] what do you want to achieve here? for example, if resource is str1 shopApprovalData[resource] will always return undefined because no index str1 in Array
I hope it help,
Best Regards

Typescript: get properties of the type itself

I have the next structure
type child = {
id:string;
// other props
};
type parent = {
children: ReactElement<child>[];
onSelect:(val: /* literals of children ids ("id1" | "id2"...) */) =>void;
}
So as I imagine it, it would be good to have "this" type to allow to refer to the field of itself. So if I pass children with id1 and id2 as props to parent component, it could get its literal types and type of "onSelect" argument would be not just string, but literal types of ids of children
I found ThisParameterType and ThisType in typescript utilities, but those "this" are not type's this. Right?
Is there a way to do it in ts?

TypeScript array square bracket syntax with type values

Is it possible to write "isHoritzontal" using square bracket array syntax AND WITHOUT a variable assignment? I see it's possible with a variable assignment (ie. const a: Placement[] = ["top", "bottom"]) but I'd like to know if it can be done in a single line of code like with Array<Placement> as shown below.
type Placement = "top" | "bottom" | "left" | "right";
const isHorizontal = (placement: Placement): boolean =>
Array<Placement>("top", "bottom").includes(placement);
("top", "bottom") is using the comma operator - that expression is equivalent to "bottom", which is not what you want (and on which includes happens to work because it's a string method as well as an array method, but it's not the logic you're looking for).
Put square brackets around the array you're searching, then assert that it's Array<Placement> with as, then call .includes on the whole result.
const isHorizontal = (placement: Placement): boolean => (["top", "bottom"] as Array<Placement>).includes(placement);
You might consider avoiding using angle bracket syntax and using as instead to avoid ambiguity with JSX - eg, use expression as string instead of <string>expression.
You would need to do this:
type Placement = "top" | "bottom" | "left" | "right";
const isHorizontal = (placement: Placement): boolean =>
(["top", "bottom"] as Placement[]).includes(placement);
However, I recommend using an intermediate variable with an explicit type to avoid bugs since a type assertion would allow your code to compile even if the array contains invalid strings. For example, if you later update the Placement type, TypeScript will not complain about this code, even though it's no longer correct logically:
type Placement = "up" | "down" | "left" | "right";
const isHorizontal = (placement: Placement): boolean =>
(["top", "bottom"] as Placement[]).includes(placement);

Unexpected token while adding a type to "value"

const count= data.reduce((s:any, {(value:any)}) => s + value, 0);
i am trying to add a new type to "value" , it shows unexxected token, i am new to type script please help
There are a couple of problems there:
There shouldn't be any () around value: any part.
You can't provide the type of a destructured parameter inline like that, because it conflicts with the renaming feature of destructuring syntax. {value: any} means "take the value of value and put it in the variable/parameter called any.
To give the type to a destructuring pattern, you type the pattern as a whole:
// The destructured parameter −−−−−−vvvvv
const count = data.reduce((s: any, {value}: {value: any}) => s + value, 0);
// The type for the object −−−−−−−−−−−−−−−^^^^^^^^^^^^^^
Playground link
In general, the any type annotation isn't best practice. You haven't shown what data is, but in most cases, you shouldn't have to put type annotations on the callback's parameters at all. For example, let's assume data is {value: number;}[] — an array of objects that have a value property that's a number. Given that type for data, TypeScript doesn't need any type annotations on that reduce call at all:
const count = data.reduce((s, {value}) => s + value, 0);
TypeScript knows from the 0 at the end and the definition of data that both s and value will be numbers, and that the result will be a number.
In a comment you've asked how you'd do this with forEach where you also want the index. Again, you probably don't need to specify the types, and you certainly don't need to specify the type of index since TypeScript knows what type it will be in a forEach callback. But if you did, since index isn't destructured, you just specify its type inline:
// If TypeScript knwos the type of `this.data`, you DON'T NEED THESE TYPES
this.data.forEach(({value, text, data}: {value: typeOfValue; text: typeOfText; data: typeOfData}, index: number) => {
// Types −−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−−−^^^^^^^^
// ....
}

Indexable type interface containing values with various types

Is there any way of achieving type variety in indexable type interfaces?
I have a state which contains fields with different types.
public state:State = {
item: {...},
item1: {...},
...
itemCollection: [],
itemCollection1: []
...
}
In the code above item is some specific name which I want to access to the state with it like this.state['item']. But, as far as I realized, indexable types in typescript only accept values with the exact same type.
I have an interface like:
interface State{
[field:string]: ITEM | ITEM[] | null
}
, which I want to be able to turn into something like this:
interface State{
[field:string]: ITEM | null
[field:string]: ITEM[] | null
}
I know it looks stupid. As I am new to TypeScript, I wonder if I could do it like I used to do in JavaScript.
Explanation
indexable types in typescript only accept values with the exact same
type
That's very close, but not exactly correct — the index signature must contain all types that appear on the right-hand side. It means the type used for indexing can be exactly the same as your values, but can also be wider.
What does it mean for a type to be wider? You can think of types are sets. For example, a specific string like "foo" belongs to the set of all strings, so string is wider than "foo". The types at the top (any and unknown) contain almost everything, meaning everything can be assigned to them.
Solution
You must use an index signature that contains all possible types of the values.
interface State {
[field: string]: ITEM | ITEM[] | null
}
or
interface State {
[field: string]: any;
}
However, this will destroy any code completion. If it suits your use case, you could also try this approach:
type Foo = {
foo: number[]
bar: string[]
[index: string]: unknown;
}
const foo: Foo = {
foo: [1, 2, 3],
bar: ["a", "b", "c"],
baz: Window
}
In this example, we get correct code hints for foo and bar, and any excess properties are of type unknown. This means they need to be verified before they can be used.
let unrecognized = foo["baz"]
if (unrecognized instanceof Window) {
console.log(unrecognized.document);
}

Resources