Why does passing the plot type ('scatter', 'bar', etc.) as string variable does not work? (using <Plot ... /> from react-plotly.js) - reactjs

I am using TypeScript, React and Plotly in my project and would like to know if it is possible to pass the plot type specification using a variable. Something like this (which is not a working code example and only used to indicate what I mean)
import Plot from 'react-plotly.js';
var plotType: string = 'bar';
return (
<Plot
data={[{
x: [1,2,3,4],
y: [1,2,3,4],
type: 'bar' // this works
type: plotType // this doesn't
}]}
/>
);
It's not really an issue since I go about the whole 'data' thing using a state property, but I still don't get why it works with the literal string but not with a variable.
The error is something like 'string' cannot be assigned since '"bar"|"scatter"|"line" ...' is expected.
For a working example I can only refer to the react-plotly github repos, where one can use the given quickstart example and try to substitute the string in type: 'scatter' with a variable.
PS.: I am quiet new to TS or JS in general so I might be using wrong/misleading terms unknowningly.

It isnt entirely evident in their docs but, this is actually a TS feature that the plotly devs are using.
I can say
type MyType = string;
then if I say
const myVar = '123';
TS will be perfectly happy. On the other hand, I could restrict other devs on what actual values they assign to MyType like
type MyType = 'bar' | 'pie' | 'scatter';
which is exactly what plotly has done. and then if a dev says
<Plot type={'123'} />
TS will throw an error because '123' isnt one of the options.
Now, you say, but my plotType variable IS one of those options. While thats true, you typed it as :string. So, when TS type checks is compares the PlotType type inside Plotly which is a limited set of strings to the string type. Thus, you have a mismatched type. In your case you could have said
var plotType: Plotly.PlotType = 'bar';
since the types now match, this would be acceptable. In fact, in this case you wouldnt even be able to accidently change the value of plotType to something not a part of Plotly.PlotType because TS would complain now.

Related

TypeScript Discriminated Union with Optional Discriminant

I've created a discriminated union that's later used to type props coming into a React component. A pared down sample case of what I've created looks like this:
type Client = {
kind?: 'client',
fn: (updatedIds: string[]) => void
};
type Server = {
kind: 'server',
fn: (selectionListId: string) => void
};
type Thing = Client | Server;
Note that the discriminant, kind, is optional in one code path, but is defaulted when it is destructured in the component definition:
function MyComponent(props: Thing) {
const {
kind = 'client',
fn
} = props;
if (kind === 'client') {
props.fn(['hey']);
// also an error:
// fn(['hey'])
} else {
props.fn('hi')
// also an error:
// fn('hey')
}
}
What I'm trying to understand is what's going on with this conditional. I understand that the type checker is having trouble properly narrowing the type of Thing, since the default value is separate from the type definition. The oddest part is that in both branches of the conditional it insists that fn is of type (arg0: string[] & string) => void and I don't understand why the type checker is trying to intersect the parameters here.
I would have expected it to be unhappy about non-exhaustiveness of the branches (i.e. not checking the undefined branch) or just an error on the else branch where the 'server' and undefined branches don't line up. But even trying to rearrange the code to be more explicit about each branch doesn't seem to make any impact.
Perhaps because the compiler simply can't narrow the types so tries an intersection so it doesn't matter which path--if the signatures are compatible then it's fine to call the function, otherwise you basically end up with a never (since string[] & string is an impossible type)?
I understand that there are a variety of ways I can resolve this via user-defined type predicates or type assertions, but I'm trying to get a better grasp on what's going on here internally and to find something a bit more elegant.
TS Playground link
It's an implementation detail in TS. Types are not narrowed when you are storing the value in a different variable. The same issue exists for the square bracket notation. You can refer to this question, it deals with a similar issue. Apparently, this is done for compiler performance.
You can fix your issue by using both props.fn and props.kind.
playground
Or write a type guard function.

Typescript - Access to the data provided by a type structure

I am using React and Typescript and I have the following type:
export type TestEnum = "TEST1" | "TEST2";
I want to check if a variable is equal to one of these values. How can I access the TEST1 value, for example?
I think you might be confusing types with variables, TypeScript doesn't exist in runtime and as such, you can't do comparisons between those types.
But you can still do some work around, and create those types from actual variables, like so:
// the "as const" will create a tuple with literal types , instead of [string, string]
const testEnums = ["TEST1", "TEST2"] as const;
type TestEnums = typeof testEnums[number] // "TEST1" | "TEST2"
This way, you can have your type and still use testEnums const to check for equality.

Why does typescript complain when I import a component, but not when the component is defined in the same file?

I have an issue where I get two different results from Typescript's type check when I import a component from another file vs define the component in the same file where it's used.
I made a sandbox to describe question in more detail: https://codesandbox.io/s/typescript-error-1l44t?file=/src/App.tsx
If you look at Example function, I'm passing in an additional parameter z, which shouldn't be valid, therefore I'm getting an error (as expected).
However if you enable L15-L22 where ExampleComponent is defined in the same file, then disable or remove the ExampleComponent import from './component' on L2, suddenly Typescript stops complaining.
Any help would be appreciated. Thank you!
If there's any extra information I can give, please let me know.
This is because your are refining a type in one scope, but if you export that value you don't also get those refinements.
In other words, when you create the value in the same file, Typescript can infer a more specific subtype. But when it's in another file it just imports whatever the most broad type that it could possibly be.
Here's a simpler example:
// test.ts
export const test: string | number = 'a string';
test.toUpperCase(); // works
This works because typescript observed that test is a string because of the assignment of a string literal. There is no way that, after executing the code of that file, test could be a number.
However, test is still typed as string | number. It's just that in this scope typescript can apply a refinement to a more narrow type.
Now let's import test into another file:
// other.ts
import { test } from './test'
test.toUpperCase() // Property 'toFixed' does not exist on type 'string | number'.
Refinements only get applied in the scope where they were refined. That means that you get the more broad type when you export that value.
Another example:
// test.ts
export const test = Math.random() > 0.5 ? 'abc' : 123 // string | number
if (typeof test === 'string') throw Error('string not allowed!')
const addition = test + 10 // works fine
// other.ts
import { test } from './test'
const addition = test + 10 // Operator '+' cannot be applied to types 'string | number' and 'number'.(2365)
In this case the program should throw an exception if a string is assigned. In the test.ts file, typescript knows that and therefore knows that test must be a number if that third line is executing.
However, the exported type is still string | number because that's what you said it was.
In your code, React.ComponentType<P> is actually an alias for:
React.ComponentClass<P, any> | React.FunctionComponent<P>
Typescript notices that you are assigning a function, and not a class, and refines that type to React.FunctionComponent<P>. But when you import from another file it could be either, so typescript is more paranoid, and you get the type error.
And, lastly, for a reason I haven't yet figured out, your code works with a function component, but not with a class component. But this should at least make it clear why there's a difference at all.

Flow complains about type incompatibility even though I check type first

I have written a React dropdown component which I can supply either of the following to :
An array of strings
An array of simple JSON object each of which contains two properties of text and icon.
My simple flow types look as follows:
type DropdownMenuItemType = DropdownMenuIconAndTextType | string;
type DropdownMenuIconAndTextType ={
text: string,
icon?: React$Element<React$ElementType>;
}
Previous versions of the component only supports strings. The addition of an element to support text and an icon is a new feature request which I am in the process of implementing. I don't want any breaking changes for my existing users.
Therefore within my component I try to attempt to convert any string supplied and wrap it in a DropdownMenuIconAndTextType so everything ends up as this type. Items that are already DropdownMenuIconAndTextType just remain so.
let Array<DropdownMenuItemType> originalItems =
let Array<DropdownMenuIconAndTextType> convertedItems = [];
{'English', 'French', 'German', {text: 'Dutch', icon : <SomeIcon />}};
items.forEach( (currItem: DropdownMenuItemType) => {
if(typeof currItem === DropdownMenuIconAndTextType){
convertedItems.push(currItem);
}
else {
convertedItems.push({text: currItem.toString()});
}
});
However flow has one error with :
if(typeof currItem === DropdownMenuIconAndTextType){
convertedItems.push(currItem);
}
and it says that currItem could still be a string and is incompatible with convertedItems despite it being type checked as DropdownMenuIconAndTextType.
What do I need to do to satisfy flow in this scenario ? Thanks in advance.
I believe you're mixing up the distinction between Flow's type code and JS code.
Inside of type signatures, typeof returns the type of a literal value, as described here. In the JS code that exists at runtime, such as in your if statement, typeof will just tell you whether something is a string, object, etc., as described here. So the left side of your conditional operator will evaluate to either "string", or "object", not to the actual Flow type of the variable.
On the right side of your conditional, you have the Flow type DropdownMenuIconAndTextType, which only exists at type-checking time, not at runtime. I'm kind of surprised that Flow doesn't give you an error because of that.
Try something like this:
if(typeof currItem !== 'string'){
convertedItems.push(currItem);
}
This will check whether the value that exists at runtime is a string or an object, which should work with Flow's type refinements.

I can't access the _groups property in angular/d3js

I have a problem accessing my "_groups" property with the following code:
function mouseDate(scale){
var g = d3.select("#group")._groups[0][0]
var x0 = scale.invert(d3.mouse(g)[0]);
console.log(x0);
}
Result of my console.log:
Selection {_groups: Array(1), _parents: Array(1)}
_groups: Array(1)
0: Array(1)
0: g#group
When I compile the code I have the following error :
D:/Documents/starter-propre-angular4/src/app/pages/graphe-page/graphe.page.ts (782,32): Property '_groups' does not exist on type 'Selection<BaseType, {}, HTMLElement, any>'.
So my question is: Is there a solution to get the information in "_groups" all year round knowing that I am evolving into TypeScript using d3js
The _groups property is a private member of a Selection object and should as such not be accessed directly. (Side note: it is common convention in JavaScript that any member starting with an underscore denotes a private member. See, e.g., "Underscore prefix for property and method names in JavaScript").
Since the property is considered private it is not part of the public interface and is therefore not included in the TypeScript type declaration for the d3-selection module. Hence, you get the compiler error you witnessed. Although this actually will work in pure JavaScript the TypeScript compiler did exactly what it is supposed to do—namely, prevent you from doing some unsafe stuff.
Looking at the code you posted, though, it becomes apparent that you are not interested in the _groups property itself but rather in _groups[0][0] which internally refers to the first node of the selection. Fortunately, the selection.node() method will return exactly that first element. You can do it like this:
function mouseDate(scale){
var g = d3.select("#group").node();
var x0 = scale.invert(d3.mouse(g)[0]);
console.log(x0);
}

Resources