How to properly use callback refs to get selected values in Typescript? - node.js

I am trying to use the up-to-date method to get a value from a Select element via React but I seem to be getting all kinds of errors. I originally used string refs, but it seems like that is now considered legacy.
class Base extends React.Component<any, any> {
constructor(props){
super(props);
this.candInput = React.createRef();
this.state = {name: '', ns:'', svc: '', base:'', cand:[]};
}
handleAddCand = (e) => {
if(!this.state.cand.includes(this.candInput.current.value) &&
this.state.base !== (this.candInput.current.value)){
this.setState((prevState) => ({
cand: [...prevState.cand, this.candInput.current.value],
}));
}
}
And the returned components that use the ref is placed like:
<Select
id="ns-select"
labelText="Namespace"
helperText="..."
ref={this.candInput}
/>
<Button
onClick={this.handleAddCand}
>
Add
</Button>
For some reason I keep getting errors that claim that candInput doesn't exist? The compiler err msg is:
Property 'candInput' does not exist on type 'Base'.
And the error that ref is not a property available for this particular element. The element is a imported Carbon Component, but I can't tell if that's the core issue. The error message is:
Property 'ref' does not exist on type 'IntrinsicAttributes & SelectProps & { children?: ReactNode; }'.
My react version is 16.12 and the carbon component version is 7.10, so I definitely have updated modules. Any pointers or help would be greatly appreciated!

Related

React Typescript take part of props

I'm relatively new to React & TypeScript. I'm trying to extend an existing component by making a wrapper around it, but I am having issues trying to add my own values to the properties.
I want it so that the default properties (in a predefined type "TextFieldProps" from the MUI library) carry over, and I can add my own values to it. I'm doing this by making my own type as such:
type PinnableTextFieldProps = TextFieldProps & {
pinned: boolean;
onPin: (newValue: boolean) => void;
};
I then use it as follows:
export function PinnableTextField(props: PinnableTextFieldProps) {
return (
<TextField
{...props}
InputProps={{}}
/>
);
}
This works fine, except that the "pinned" and "onPin" values are copied over to the TextField while they shouldn't be (TextField doesn't know what they are, and an error is printed to the console because of it)
I tried to cast it using ...(props as TextFieldProps) but it still included the properties in the spread.
How would I properly split up the props spread to only include all values inside of the TextFieldProps type, so excluding the 2 values I added?
I hope someone can point me in the right direction!
Many thanks!
You could do something like this:
export function PinnableTextField(props: PinnableTextFieldProps) {
const { pinned, onPin, ...rest } = props; // <= Splitting props into pinned, onPin and all other properties into rest
// Use pinned, onPin here
// Pass the rest of the props down to the TextField
return (
<TextField
{...rest}
InputProps={{}}
/>
);
}

Unable to pull through API to react frontend with

I am struggling to pull through the api to the front end. I completed it successfully with
https://jsonplaceholder.typicode.com/ Just mapped out the arrays. i am struggling however to pull through this seperate api I wanted to use
https://gateway.marvel.com/v1/public/comics?apikey=xxxxxxxxxxxxxxxx&ts=redant&hash=140e85a50884cef76d614f6dacada288
the erro is..
"Uncaught TypeError: Cannot read properties of undefined (reading 'results')"
so clearly it isnt actually able to get hold of results
What am I doing wrong?
import React, {Component} from 'react';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
list: []
};
}
componentDidMount() {
fetch('https://gateway.marvel.com/v1/public/comics?apikey=3cb62d086d5debdeea139095cbb07fe4&ts=redant&hash=140e85a50884cef76d614f6dacada288')
.then (response => response.json())
.then(users => this.setState({list:users}))
}
render() {
return (
<div className='App'>
{
this.state.list.data.results.map(result =>
<h1 key={result.id}>{result.urls}</h1>
)
}
</div>
)
}
}
export default App
The error Uncaught TypeError: Cannot read properties of undefined (reading 'results') in your code means that this.state.list.data returns as undefined. This means that you'll need to focus on your state property list to ensure that it has a data property. As we see in your constructor, data is initialized to an empty array which does not contain the data property.
Something we can do to prevent the error is to surround your code with an undefined check:
if (this.state.list.data != undefined) {
this.state.list.data.results.map(result =>
<h1 key={result.id}>{result.urls}</h1>
)
}
At this point, though, we don't know if your API call is returning good data or not since your program throws the error before that (since the fetch and setState are asynchronous), so the code above mainly addresses the error that you're getting rather than focusing on the "pull through the api to the front end" portion of your question.
Here's what you can do inside your then part after fetching the results
this.setState({list:users.data.results}
And inside the map function, do the following:
this.state.list.map(result =>
<h1 key={result.id}>{result.urls}</h1>
You are getting an error because List is initially empty it doesn't have any key called as results, once you fetch the result only then can you loop through your result.
Another solution would be to simply add a loader, you can control that via state as well.

Wrap a problematic npm package, while maintaining type information

The Problem
We're authoring an npm package containing React components that will be used in various (internal) web sites. There is a problematic npm package dependency that we are forced to use in our react .tsx files, that has these problems:
It doesn't expose any useful types despite having .d.ts files in it... they're empty.
It tries to run when required or imported server-side, instead of waiting until called, so we have to avoid a top-level import and instead do if (window) { const module = require('package-name') } and then use it inside that block only.
It is a frequent source of errors so everything in that library needs to be run inside of a try ... catch block.
Well, At Least We Have Types
We have already created our own types file which addressed problem #1:
// problematic-package-types.d.ts
declare module 'problematic-package' {
function doErrorProneButNecessaryThing(
foo: Record<string, unknown>,
bar: string
): void
}
The Needed Solution
The long term solution is to fix this problematic library and we're looking into how to get that done (but it's not in our direct control).
In the short term, though, we need a solution now.
Note that we are configuring dynamic requires in our npm package bundler to import them only at use-time, not treating them like other imports/requires. As our package is consumed inside other applications, we don't have full control over how that application bundling works or when the components are required, so our components may end up being required server-side when they shouldn't, and we have to tolerate that. We're still learning about some aspects of this.
My Wild (But Failed) Stab
My goal is to do something more DRY like this, where we solve all three problems of strong typing, detecting server-side execution & doing nothing, and adding error handling:
// hoping to leverage our module declaration above without importing anything
import type * as ProblematicPackage from 'problematic-package'
import wrapProblematicRequire from '../utils/my-sanity-preserving-module'
const wrappedProblematicPackage = wrapProblematicRequire<ProblematicPackage>()
// then later...
const foo: Record<string, unknown> = { property1: 'yes', property2: false }
const bar = 'yodeling'
wrappedProblematicPackage.invokeIfWindowReady(
'doErrorProneButNecessaryThing',
foo,
bar
)
However, TypeScript doesn't like the import type which unfortunately makes sense:
Cannot use namespace 'ProblematicPackage' as a type.
The Plea
How do I get the type information we've placed into problematic-package-types.d.ts to use as desired?
Or ANYTHING else. Honestly, I'm open to whatever, no matter how crude or hacky, so long as we get some clarity and reliability at call sites, with full type information as described. Suggestions/advice?
Full Details
Here is the full implementation of the wrapProblematicRequire function. I haven't tested it. It's probably awful. I'm sure it could be far better but I don't have time to get this helper module super clean right now. (My attempt to handle function type information isn't quite right.)
type Func = (...args: any[]) => any
type FunctionNames<T, TName extends keyof T> = T[TName] extends Func ? TName : never
type FunctionNamesOf<T> = FunctionNames<T, keyof T>
const wrapProblematicRequire = <T>(packageName: string) => ({
invokeIfWindowReady<TName extends FunctionNamesOf<T>>(
name: T[TName] extends Func ? TName : never,
...args: T[TName] extends Func ? Parameters<T[TName]> : never
): T[TName] extends Func ? ReturnType<T[TName]> : never {
if (!window) {
// #ts-ignore
return undefined
}
try {
// #ts-ignore
return require(packageName)[name] as T[TName](...args)
} catch (error: unknown) {
// ToDo: Log errors
// #ts-ignore
return undefined
}
}
})
export default wrapProblematicRequire
P.S. await import('problematic-package') didn't seem to work. Yes, problems abound.
Cannot use namespace 'ProblematicPackage' as a type.
Well, you can get the typeof that namespace, which seems to be what you want.
To test this, I setup the following:
// problem.js
export function doErrorProneButNecessaryThing(n) {
return n;
}
export function doErrorProneButNecessaryThing2(s) {
return s;
}
console.log('did side effect');
// problem.d.ts
export function doErrorProneButNecessaryThing(n: number): number;
export function doErrorProneButNecessaryThing2(s: string): string;
And now you can do:
import type * as ProblemNs from './problem';
type Problem = typeof ProblemNs;
// works
type A = Problem['doErrorProneButNecessaryThing'] // type A = (n: number) => number
Then the wrapProblematicRequire function just takes the name of the function as a generic, pulls the args for it, and pulls the return type.
const wrapProblematicRequire = <TName extends FunctionNamesOf<Problem>>(
name: TName,
...args: Parameters<Problem[TName]>
): ReturnType<Problem[TName]> | undefined => {
if (!window) return;
const problem = require('./problem'); // type is any, but types are enforced above
try {
return problem[name](...args);
} catch (err) {
console.log('error!');
}
};
Here require('./problem') returns the any type, but the generics keep everything key safe as long as typeof ProblemNs can be trusted.
Now to test that:
console.log('start');
const result: number = wrapProblematicRequire(
'doErrorProneButNecessaryThing',
123
);
console.log('end');
Which logs:
start
did side effect
end
Which seems to work!
Codesandbox

Angular2 object property typed as "number" changes to string

I have a very simple Angular2 app running locally. I'm using a service to send an instance of an object to a webservice API. The API validates JSON against a schema, and ID's must be numbers (i.e. NOT quoted in the JSON).
My problem is that when I try to send the object to the webservice, my ID field has quotes around it, even though it's typed to be a number in Typescript.
The behaviour is only observed when the nameproperty of the object contains "special characters".
I've tested and found that it doesn't seem to be the JSON.stringify I use — please see code below.
The app is written in Typescript.
Unit class:
export class Unit {
id: number; // This is the problem child
name: string; // This is the string that can contain special characters
short: string;
triggers_plural: number;
is_headline: boolean;
}
Method to save:
My code for saving an instance of Unit to the webservice:
updateUnit(unit: Unit): Promise<Unit>
{
var objSend = {unit_data: [unit]}; // Webservice expects an array of units
console.log(objSend.unit_data[0].id === 2); // Yields false when ID is 2
console.log(objToReturn); // Logs ID to verify it is 2 when testing
// Code for actual request
return this.http.put(`${this.unitUrl}/${unit.id}`, JSON.stringify(objSend),{headers:this.headers})
.toPromise()
.then(()=> unit)
.catch(this.handleError);
}
When running the code and calling the method, the console will log that the ID is NOT equal when the Unit object's name property contains special characters.
Example without special characters (no problem, id is a number):
Example WITH special characters (eek! Id is a string!):
The updateUnit method is called from my unit-detail component where you can edit a unit:
export class UnitDetailComponent implements OnInit {
unit: Unit; // this.unit later on
constructor(
private unitService: UnitService,
private route: ActivatedRoute
){}
ngOnInit(): void
{
this.route.params.forEach((params: Params) => {
let id = +params['id']; // The routing will give id to look for
this.unitService.getUnit(id)
.then(unit => this.unit = unit); // Here the unit is instanciated in the first place
});
}
save(): void
{
this.unitService.updateUnit(this.unit).then(this.goBack); // Here is the call to updateUnit method
}
}
It's bound to an input in the template:
<div *ngIf="unit">
<div>
<label>Edit unit</label>
<div>
<input type="text" [(ngModel)]="unit.name" />
</div>
</div>
<button class="btn btn-default" type="button" (click)="save()">Save</button>
</div>
Maybe the problem arises already when the two-way data binding is filling in the name property when you write something in the <input> but I don't understand how the type of the id can change?
Link to github repos of the whole project: https://github.com/djoike/ng2-cookbook/tree/master-so
A small solution is to cast the field to any , and then convert it to a number using parseInt(), I faced a similar problem just today
for more info about casting, check the Type assertions section here

Using WinJS with Angular2

Trying to use a property to configure a WinJS control from within Angular2, so far I couldn't find a solution, e.g. this code below is throwing 'Can't bind to 'dataWinOptions' since it isn't a known property of the '' element'.
#View({
template: `<div id="rating" data-win-control='WinJS.UI.Rating' [data-win-options]='jsonRating'></div>`
})
class MyRating {
rating: number;
get jsonRating() {
return '{averageRating: ' + this.rating + '}';
}
constructor() {
this.rating = 1.5;
}
}
Any hint?
#ericdes about your last comment I think this would be the best option. Assuming you have Nth WinJS controls
Consider the following code. I'm specifying differents values for the averageRating property in options.
<winjs-control [options]="{averageRating: '1.5', someMoreOptions : 'x'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.4', differentOptionsForThisOne :'Z'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.3'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.2'}"></winjs-control>
<winjs-control [options]="{averageRating: '1.1'}"></winjs-control>
// more and more...
The component will read this options property and will pass it to the view. Forget about the directive, it isn't necessary after all.
We pass options through attr.data-win-options since it isn't a property of div but an attribute.
#Component({
selector : 'winjs-control',
properties : ['options']
})
#View({
template : `<div data-win-control="WinJS.UI.Rating" [attr.data-win-options]="jsonRating"></div>`,
})
class WinJSComponent implements OnInit, AfterViewInit {
constructor() {}
// We specify onInit so we make sure 'options' exist, at constructor time it would be undefined
// And we stringify it or otherwise it will pass an object, we need to convert it to a string
onInit() {
this.jsonRating = JSON.stringify(this.options);
}
// We process WinJS after view has been initialized
// this is necessary or 'data-win-options' won't be fully processed
// and it will fail silently...
afterViewInit() {
WinJS.UI.processAll();
}
}
Here's a plnkr for this case.
That's one option and IMHO I think this is the easiest one. Another one, having the same HTML content, would be to communicate the parent with its children and I haven't tested your case with that approach.

Resources