StencilJS #Prop() variable is undefined - javascript

I created a stencil component () that uses another stencil component () in the rendered template.
One of the props (key: string) passed from smg-compound-filter to smg-filter is undefined, whereas the other props are well defined. I made sure that {filter.key} is defined in the template of smg-compound-filter, and i even tried to pass a literal string to smg-filter, but it's undefined in this component. I think i am missing something. If someone could give me an insight, it will be bery helpful.
smg-compound-filter.ts (render function)
render() {
return (
<div class="smg-filter-container">
<div class={`filters`}>
this.filters.map(filter => {
return (
<div class='filter'>
<smg-filter
key={filter.key} // even "str" doesn't work
label={filter.label}
target={filter.target}
options={filter.options}
customClasses='secondary'
onFilterChanged={(event) => {this.toggleOption(event)}}
>
</smg-filter>
</div>
);
})
</div>
</div>
);
}
smg-filter.ts
export class SmgFilter {
#State() filter: Filter;
#State() showOptions: boolean;
/** custom classes to adapt style */
#Prop() customClasses: string;
/** smartsheet column linked to the filter */
#Prop() key: string;
/** label of the filter */
#Prop() label: string;
/** options */
#Prop() options: FilterOption[];
/** type of products to be filtered: 'master' or 'product' */
#Prop() target: FilterTarget;
#Event() filterChanged: EventEmitter<Filter>;
componentWillLoad() {
console.log(this.key); // undefined
this.showOptions = !isSecondary ? true : false;
this.filter = {
key: this.key,
target: this.target,
label: this.label,
options: this.options,
};
}

That's because key is a reserved keyword since Stencil uses it to improve loop rerender performance.
I'm not 100% sure if it can be changed or disabled but I think you'll have to use another property name.

The #Prop() name "key" is a reserved public name. Please rename the "key" prop so it does not conflict with
an existing standardized prototype member. Reusing prop names that are already defined on the element's
prototype may cause unexpected runtime errors or user-interface issues on various browsers, so it's best to
avoid them entirely.

Related

Typescript - Conditional Type / Optional

I have a component, It takes arguments such as :
interface Props {
label: string;
children?: React.ReactNode;
withoutActions?: boolean;
fieldKey?: KeyProperties;
corporate: Corporate;
}
The withoutActions and fieldKey are used by a form.
If the withoutActions prop is true, then there should not be a provided fieldKey.
However, if my withoutActions is undefined, then i should enforce my fieldLabel to be of type **KeyProperties** which is a specific list of available values for it.
If withoutActions is undefined, the other SHOULD NOT be defined at all
If withoutActions is true, the other SHOULD be defined respecting the specific keyProperties type.
How can i implement that ?
Similar to the other answer, but you can refactor it a bit to be cleaner. You did not specify what the expected behavior is if withoutActions is false instead of true or undefined. Here, I assume the behavior for false and undefined are the same. If false is not a valid type, you could just swap withoutActions?: false for withoutActions: undefined like the other answer.
type Props = {
label: string;
children?: React.ReactNode;
corporate: Corporate;
} & (
{
withoutActions: true;
} |
{
withoutActions?: false;
fieldKey: KeyProperties;
}
)
However, there is an important pitfall here that you should be aware of. Because of TypeScript's structural typing, you only get excess property checking when you are directly assigning an object literal. You do not get excess property checking when you assign an an object as an inferred type. TypeScript and React treat direct props declarations as if they are object literals, and will do excess property checking like you seem to desire. However in some cases, if you assign objects to variables and let their type be inferred, TypeScript may not warn that there is an excess property present.
Check out this demo based on your original example. Example #1 and #2 will error because of excess property checking, but example #3 will not.
const ExampleOne = () => {
// Type error - excess property checking
return <Component label={''} corporate={''} withoutActions fieldKey={''} />;
}
const ExampleTwo = () => {
const props: Props = {
label: '',
corporate: '',
withoutActions: true,
// Type error - excess property checking
fieldKey: '',
}
return <Component {...props} />;
}
const ExampleThree = () => {
const props = {
label: '',
corporate: '',
withoutActions: true,
fieldKey: '',
}
// No type error - no excess property checking
return <Component {...props} />;
}
I did not fully understand your requirement, but I would use a type alias instead of an interface. E.g. something like this:
type Props = {
label: string,
children?: React.ReactNode,
withoutActions: true,
corporate: Corporate
} | {
label: string,
children?: React.ReactNode,
withoutActions: undefined,
fieldKey: KeyProperties,
corporate: Corporate
}

How to use either this type or another based on props only? React Typescript Discriminating Unions

I have a componenet:
type Base = {
color: string
}
type Button = {
to: string
} & Base
type Link = {
link: string
linkNewTab: boolean
} & Base
type ComponentProps = Button | Link
export const Button: React.FC<ComponentProps> = (props) => {
return (<>
{props.link ? <Link newTab={props.linkNewTab}>...</Link> : props.to && <Button>...</Button>}
</>)
}
There is a base type with props which both types have in common.
The Component should either has Button or Link type based on the given props. But if it has Link, these props are preferred.
Typescript Error:
Property 'link' does not exist on type 'PropsWithChildren<ComponentProps>'.
Property 'link' does not exist on type '{ to: string; } & Base & { children?: ReactNode; }'.ts(2339)
What I don't want:
I can solve the problem by adding a type to the Base type and decide from their which props are allowed. I would like to automatically decide that based on props.
Information: 4.5.4 TypeScript Version
The problem is that you are attempting to access a value at property which might not exist. Instead, you can check to see if that property exists in the object (by using the in operator) before trying to access its value. This will also discriminate the union:
TS Playground
// instead of this:
if (props.link) {}
// ^^^^
// Property 'link' does not exist on type 'Button | Link'.
// Property 'link' does not exist on type 'Button'.(2339)
// prop in obj
if ('link' in props) {
props.color // ok
props.link // ok
props.linkNewTab // ok
}
else {
props.color // ok
props.to // ok
}
You can check whether the link property exists by using the in operator. This will narrow down the type without accessing the property, and so typescript will allow it:
<>
{"link" in props ? (
<Link newTab={props.linkNewTab}>...</Link>
) : (
"to" in props && <Button>...</Button>
)}
</>

Many boolean properties in react state - how to refactor?

I have a React component that is a report with many buttons. You can open some subreports on table row click or by button click. I keep in state boolean values for each report. Basically my State interface for types looks like this:
interface State {
isSubreportVisible: boolean;
isLogsVisible: boolean;
isSelectStatusVisible: boolean;
isMoneyVisible: boolean;
isTargetStoreWarningVisible: boolean;
isMultiVisible: boolean;
isCancelVisible: boolean;
isPrintVisible: boolean;
}
Then my initial state is like this:
public state: State = {
isSubreportVisible: false,
isLogsVisible: false,
isSelectStatusVisible: false,
isMoneyVisible: false,
isMultiVisible: false,
isTargetStoreWarningVisible: false,
isCancelVisible: false,
isPrintVisible: false,
};
And inside component I have:
{isSubreportVisible && (
<Subreport
...
...
...
/>
)}
File of the component is really long - almost 600 lines of code. Is there anything could be done here? All subreports in modals are connected to this component?
Is it a good pattern to create a file called for example "types.tsx" and move all interfaces there (State, StateToProps, Props etc.)? Any ideas? Maybe it could be better to have just one string value in state - currentModalVisible and to keep there its names from enum?
Maybe it could be better to have just one string value in state - currentModalVisible and to keep there its names from enum?
This depends -- is only one modal visible at a time? If so then I would store a single value which is either null/undefined or the name of the visible modal. If the modals toggle independently then you will want to store a boolean for each one.
Either way, I think a good goal here is to have generalized functions that take a string key for the modal as an argument. You want to be able to call things like isModalVisible('print'), toggleModalVisible('print'), setModalVisible('print', false), etc.
I would define a type for the props that are available to the modals/subreports. You want to require that the components cannot require any additional props. That way you can call a component by its variable and pass it all the available props some will be ignored).
For maximum flexibility, you could even pass the available modals as a prop.
interface ModalProps {
someKey: string;
}
interface FlexibleSettings {
// what to display in your tab/toggle
// optional because can default to a cased version of the `key`
buttonText?: React.ReactNode;
// the component to render when selected
modal: React.ComponentType<ModalProps>;
}
interface VeryFlexibleComponentProps<Keys extends string> {
// pass the modals as an object keyed by the modal key
modals: Record<Keys, FlexibleSettings>;
// can start with one visible
initialVisible?: Keys;
// need to access the ModalProps somewhere
// maybe some come from this component and can be omitted
modalProps: ModalProps;
}
const VeryFlexibleComponent = <Keys extends string>({
modals,
initialVisible,
modalProps
}: VeryFlexibleComponentProps<Keys>) => {
const [visible, setVisible] = React.useState(initialVisible); // type Keys | undefined
// need an uppercase variable to call a component with JSX
const CurrentModal = visible ? modals[visible].modal : undefined;
return (
<div>
<h3>Select Modal</h3>
{(Object.keys(modals) as Keys[]).map((key) => (
<div key={key}>
<input
type="radio"
name="visible"
value={key}
id={key}
checked={visible === key}
onChange={(e) => {
if (e.target.checked) setVisible(key);
}}
/>
<label htmlFor={key}>
{modals[key].buttonText ?? _startCase(key)}
</label>
</div>
))}
{CurrentModal !== undefined && (
<div>
<h3>Viewing Modal</h3>
<CurrentModal {...modalProps} />
</div>
)}
</div>
);
};
Code Sandbox

Typescript: How To Use Generics Properly To Infer Return Type of Function Correctly?

I've the following types
type ItemDefaultType = object | null | string
interface ItemToString<Item = ItemDefaultType> {
(item: ItemDefaultType): string;
}
interface AutosuggestState<Item = ItemDefaultType> {
highlightedIndex: number | null
inputValue: string | null
isOpen: boolean
selectedItem: Item
}
interface AutosuggestProps<Item = ItemDefaultType> extends AutosuggestState<Item> {
itemToString?: ItemToString<Item>;
initialSelectedItem?: Item;
initialInputValue?: string;
initialHighlightedIndex?: number | null;
initialIsOpen?: boolean;
defaultHighlightedIndex?: number | null;
defaultIsOpen?: boolean;
id?: string;
inputId?: string;
labelId?: string;
menuId?: string;
itemCount?: number
}
I want to make a getInitialValue function with the intended signature –
// this has to be done correctly using generics, not been able to do it, read on…
(
props: Props extends AutosuggestProps,
stateKey: StateKey extends keyof AutosuggestState
) => AutosuggestState[StateKey] // return the correct type of AutosuggestState property based on which stateKey was passed
The behaviour of getInitialValue looks like this
// javascript version
const defaultStateValues = {
highlightedIndex: -1,
isOpen: false,
selectedItem: null,
inputValue: ''
}
function getDefaultValue(props, statePropKey) {
const defaultPropKey = `default${capitalizeString(statePropKey)}`
if (defaultPropKey in props) {
return props[defaultPropKey]
}
return defaultStateValues[statePropKey]
}
function getInitialValue(props, statePropKey) {
if (statePropKey in props) {
return props[statePropKey]
}
const initialPropKey = `initial${capitalizeString(statePropKey)}`
if (initialPropKey in props) {
return props[initialPropKey]
}
return getDefaultValue(props, statePropKey)
}
I'm finding it hard to write types of both getInitialValue and getDefaultValue such that getInitialValue correctly infers the right type as below –
const selectedItem = getInitialValue(props, 'selectedItem') // selectedItem variable should correctly be inferred as **object | null | string** since that's what its type is in **AutosuggestState** interface
Can someone help me write the types?
Here's a possible typing, which might or might not fit your use case:
function getDefaultValue<P extends AutosuggestProps, K extends keyof AutosuggestState>(
props: P,
statePropKey: K) {
const defaultPropKey = `default${capitalizeString(statePropKey)}`
if (defaultPropKey in props) {
return props[defaultPropKey as K] // assert here
}
return defaultStateValues[statePropKey]
}
function getInitialValue<P extends AutosuggestProps, K extends keyof AutosuggestState>(
props: P, statePropKey: K) {
if (statePropKey in props) {
return props[statePropKey]
}
const initialPropKey = `initial${capitalizeString(statePropKey)}`
if (initialPropKey in props) {
return props[initialPropKey as K] // assert here
}
return getDefaultValue(props, statePropKey)
}
All I've really done is make the functions generic in P and K of the types you alluded to in your pseudocode.
One decision you need to make is what to do when you use defaultPropKey and initialPropKey. The compiler has no way to verify that prepending "default" or "initial" to a property name will, if it exists, pick out another property of the same type. The compiler does not understand string concatenation at the type level (the suggestion for doing so in Github seems to be microsoft/TypeScript#12754). If you could refactor your interfaces so that the default and initial values are stored in properties named default and initial which are themselves objects holding properties of the same keys, then you could make the compiler figure it out. Assuming that you're keeping it this way, I've used a type assertion to say, for example, "treat initialPropKey as if it were of type K". Then the compiler will assume that props[initialPropKey] is of the same type as props[statePropKey].
Anyway, now the return types of those functions are inferred as
P[K] | {
highlightedIndex: number;
isOpen: boolean;
selectedItem: null;
inputValue: string;
}[K]
which makes sense since it uses defaultStateValues at the end, which is a concrete type not known to be the extended P type. This might be tightened up by using a conditional return type, but your example code didn't show a need for that.
Let's see if it works:
declare const props: AutosuggestProps;
const selectedItem = getInitialValue(props, 'selectedItem');
// const selectedItem: ItemDefaultType
Looks reasonable to me. Okay, hope that helps; good luck!
Link to code

Flow: is not a polymorphic type

i just integrated flow for the first time to check my javascript sources statically.
I am struggling with a error flow finds and i am not able to solve it on my own. Its about using es6 classes and inheritance. More specific i created some react Components and they should inherit some methods.
I have a Callout Component, that represents a callout message of unspecified severity. To make things a little more simple i thought about providing a ErrorMessage Component, that inherits the Callout Component. My classes Structure looks like:
React.Component
> AbstractComponent (here i add some project-wide helpers for i18n and so on
> Callout (this represents a pretty message on the screen)
> ErrorMessage (this represents an error)
Flow tells me:
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ src/js/Components/Generic/ErrorMessage.js:14:43
statics of Callout [1] is not a polymorphic type.
11│ icon: string
12│ };
13│
[1] 14│ export default class ErrorMessage extends Callout<Props> {
15│
16│ static get defaultProps(): Props {
17│ return {
The part Callout<Props> gets highlighted
I already define the Props Type for the Callout class, so this might be the problem but i cant solve it on my own.
A similar error is thrown a few lines below, where i try to access a parent method by addressing super.content (content is a get-method of Callout).
Thanks in advance
UPDATE: Why do i want to use class inheritance?
The inheritance Callout > ErrorMessage just exists to reduce redundant code, but its not necessary, so lets ignore this and talk about a more common case:
I want to have a class AbstractComponent to make common things in my project easier.
Some examples:
Printing of translation strings: In order to make the component multilingual, i created a utility class to generate translation strings, inside a component it works like
function render() {
return (
<div>
{new Translation(
'namespace',
'key',
[some, args],
`${some} fallback message with optional ${args}`
).toString()}
</div>
)
}
In order to use this, every component in my stack ends up with the import statement on top
import Translation from "../Core/Translation"
or in the best case
import t from "../Core/Translation"
I use webpack to build a bundle and webpack seems to blow up the compiled javascript with every import statement you use. So i figured - to reduce coding effort and bundle size - i provide a intermediate component class, that adds some utility methods like:
class AbstractComponent extends React.Component {
constructor(props) {
super(props);
this.logger = props.logger || new Logger();
this.api: ApiInterface = props.api || new MockApi();
}
translate(namespace: string, key: string, args: ?[] = null, fallback: ?string): string {
return new Translation(namespace, key, args, fallback).toString();
}
svgSprite(id: string, className: string = "") {
return (
<SvgSprite id={id} className={className} />
)
}
}
I also added some other things to show you more reason for a intermediate Component class.
So, all of this works! But flow complains about missing return types and so on, thats good with me, for that purpose i want to use flow! The problem i cant solve is the inheritance itself... But for me it does make a lot of sense.
If you really want to deal with inheritance (which I don't have an issue with, I just feel like you will probably run into issues later), you can do something like the following:
class AbstractComponent<Props: {}, State: ?{} = null> extends React.Component<Props, State> {
api: ApiInterface
logger: typeof Logger
constructor(props) {
super(props);
this.logger = props.logger || new Logger();
this.api = props.api || new MockApi();
}
translate(namespace: string, key: string, args: ?string[] = null, fallback: ?string): string {
return new Translation(namespace, key, args, fallback).toString();
}
svgSprite(id: string, className: string = "") {
return (
<SvgSprite id={id} className={className} />
)
}
}
And use it like:
class Test extends AbstractComponent<{ some: string, args: string }> {
render() {
const { some, args } = this.props
return (
<div>
{this.translate(
'namespace',
'key',
[some, args],
`${some} fallback message with optional ${args}`
)}
</div>
)
}
}
Now, I will say that to some extent I understand where Facebook is coming from. Your component in this case is really already an abstract construct. And if you want this to be more flexible (let's say you have a stateless component that could benefit from having a logger and a translate function), you could do one of two things:
This is the defined type and translate function I'm using in both:
type CommonProps = {
logger?: Logger,
api?: ApiInterface,
translate?: (namespace: string, key: string, args: ?string[], fallback: ?string) => string
}
// This should look familiar
function translate(namespace: string, key: string, args: ?string[] = null, fallback: ?string): string {
return new Translation(namespace, key, args, fallback).toString();
}
Higher order component
function addCommonStuff({ logger = new Logger(), api = new MockApi(), translate = translate }: CommonProps) {
return <Props: {}>(
WrappedComponent: ComponentType<Props>
): ComponentType<
$Diff<Props, $NonMaybeType<CommonProps>>
> => (props: Props) => <WrappedComponent {...props} logger={logger} api={api} translate={translate} />
}
And used like:
class Test extends React.Component<{}> {}
const TestWithCommons = addCommonStuff({})(Test)
;<TestWithCommons />
Reusable component with a render prop
class Common extends React.Component<CommonProps & { render?: Function, children?: Function }, $NonMaybeType<CommonProps>> {
state = {
logger: this.props.logger || new Logger(),
api: this.props.api || new MockApi(),
translate: translate
}
render() {
const { children, render } = this.props
return typeof render === 'function' ? render(this.state) : (
typeof children === 'function' ? children(this.state) : null
)
}
}
And use it like this:
class TestCommon extends React.Component<{}> {
render() {
return <Common>
{({ logger, api, translate }) => translate('namespace',
'key',
null,
`Fallback message`
)}
</Common>
}
}
As an aside to this discussion, you don't need to write defaultProps as a getter for your callout. static defaultProps = {} should be enough. It shouldn't take passed in props into account or anything. If it does, you're better off using state

Categories

Resources