Object.assign overwritten outside of constructor - javascript

I'm having a rather strange issue. I'm using TypeScript and Preact to create a Button component. I'm using a TypeScript interface for prop validation, essentially and using a constant variable as the "default" props. The props are then applied using Object.assign and passed to super. Here is the code as follows:
import classNames from "classnames";
import { Component, h } from "preact";
export interface ButtonProps {
color?: string;
type?: "primary" | "secondary";
}
const ButtonPropsDefaults: ButtonProps = {
color: "primary",
type: "primary",
};
export default class Button extends Component<PButtonProps> {
constructor(props: ButtonProps) {
// Logs { color: "primary", type: "primary" }
console.log(ButtonPropsDefaults);
super(Object.assign(ButtonPropsDefaults, props));
// Logs { color: "primary", type: "primary", children: {...} }
console.log(this.props);
}
public render() {
// Logs { children: {...} }
console.log(this.props);
const className = classNames({
"button": true,
[`button--color-${this.props.color}`]: !!this.props.color,
[`button--type-${this.props.type}`]: !!this.props.type,
});
return <button className={className}>{this.props.children}</button>;
}
}
Notice the results of the console.log statements. It seems like the value "reverts" back to the original state as passed in to the constructor. So, for example, if I were to use the component as <Button color="primary">Test</Button>, the console.log statement in the render function will be { color: "primary", children: {...} }.
Thanks for reading and I appreciate any help you can give!

You want merge the props and your default props into a new object. So you can do
super(Object.assign({}, ButtonPropsDefaults, props)) or
super({...ButtonPropsDefaults, ...props})

Related

How to pass props in a component Reactjs

My component:
interface Props extends RouteComponentProps<any> {
sample: {
info: SampleInfo;
};
}
export class Step extends Component<Props, State>{
render() {
const { title } = this.props.sample.info;
return (
<TextContent >
<Title headingLevel="h1" size="3xl">
Uploading and validating your swagger : {name} // name is available under sample.info
</Title>
</TextContent>
)}
}
When I am trying to access props, I am getting this.props.sample is undefined.
I am trying to call this Step Component in a wizard:
const steps = [
{
id: 1,
name: "show",
component: <Form />,
},
{
id: 2,
name: "Listing",
component: <Sample2 />,
},
{ name: "Save", component: <Step/>}, // I am calling my Step component from here
];
Do I need to pass anything in Step component, please help me with this. I am new to reactjs
You <Step/> component must actually have props.
It must pass something like <Step sample={{ info: { title: 'whatever' } }}/> then it'll work.
RouteComponentProps from react-router is defined as:
export interface RouteComponentProps<
Params extends { [K in keyof Params]?: string } = {},
C extends StaticContext = StaticContext,
S = H.LocationState
> {
history: H.History<S>;
location: H.Location<S>;
match: match<Params>;
staticContext?: C;
}
(Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-router/index.d.ts#L70)
Due to extends { [K in keyof Params]?: string } and you specifying
Params generic as any (via RouteComponentProps<any>), K may be any key and because of the ? the value for all keys may be undefined.
I do not use react-router but I think you have to specify RouteComponentProps using appropriate values fot the generic parameters.

Set Child Component TextField's Value to Parent Components Dropdown Selection?

In the following two codes, There is a child and a parent component. The child component has a TextField, and the parent component has Dropdown, is there a way I can set the TextField's value to whatever I change the Parent Dropdown to? For example, if I selected "Medium", I want to change the child component TextField's value to "Medium". Is this possible?
Here is the Child Component
import * as React from "react";
import { TextField } from 'office-ui-fabric-react/lib/';
const ChildComponent = (props) => {
return(
<TextField
label="Description" required
key="descriptionKey"
styles={props.styles}
value={props.value}
multiline={props.multiline}
onChange={props.onChange}
defaultValue={props.defaultValue}
placeholder={props.placeholder}
/>
);
}
export default ChildComponent;
Here is the Parent Component
import * as React from "react";
import ChildComponent from './ListItem';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/';
class ParentComponent extends React.Component {
handleChange = (event) => {
this.setState({
operationType: event.target.value,
})
};
render(){
const riskAssess: IDropdownOption[] = [
{ key: 'high', text: 'High' },
{ key: 'medium', text: 'Medium' },
{ key: 'low', text: 'Low' },
]
return(
<div>
<Dropdown
label="Risk Assessment" required
ariaLabel="Risk"
styles={{ dropdown: { width: 125 } }}
options={riskAssess}
onChange={this._onChange}
/>
<ChildComponent value="This is what I want to change to Dropdown value" />
</div>
);
}
private _onChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
this.setState({operationType: item.text})
console.log(event);
};
}
export default ParentComponent;
Yep! Replace "This is what I want to change to Dropdown value" with {this.state.operationType}. The curly braces tell JSX that you want a variable to populate that value.
With TypeScript, your components need to define their property and state structures, by adding generic arguments to React.Component:
interface ParentProps = {};
interface ParentState = {
operationType: string;
};
class ParentComponent extends React.Component<ParentProps, ParentState> {
For ChildComponent, you'd want to extend React.FC (short for FunctionalComponent) in a similar way. Since it doesn't define any state, it can forego the second generic argument:
interface ChildProps {
styles?: React.CSSProperties;
value?: string;
multiline?: boolean;
onChange?: React.ChangeEventHandler<HTMLInputElement>
defaultValue?: string;
placeholder?: string;
};
const ChildComponent: React.FC<ChildProps> = (props) => {
Now TypeScript knows what to expect.
TypeScript's precise interface definitions ensure that each component gets only what it allows. It replaces React's PropTypes warnings with enforced compilation errors. It's more complex, having to define all this, but it makes for less error-prone development.

Typescript error from defaultProps on React FunctionComponent

I have the following React Functional Component:
import React, { memo } from 'react';
interface Props {
buttonType?: JSX.IntrinsicElements['button']['type'];
text: string;
};
const defaultProps = {
buttonType: 'button',
};
const Button: React.FunctionComponent<Props> = ({
buttonType,
text,
}) => (
<button type={buttonType}>
{text}
</button>
);
Button.defaultProps = defaultProps;
export default memo(Button);
This throws a Typescript error:
Type '{ buttonType: string; }' is not assignable to type 'Partial<Props>'.
This is how I usually write stateless components, and the error here is because I'm assigning defaultProps to the component. The error goes away if I write the defaultProps declaration as:
Button.defaultProps = {
buttonType: 'button',
};
Why do I get the error when assigning defaultProps from a const, but not if I do it all inline? Isn't it the same thing?
You have to specify the type of buttonType on your defaultProps object. This is automatically inferred to be JSX.IntrinsicElements['button']['type'] when you use Button.defaultProps, but when you create a fresh object, it sees it as a string.
const defaultProps: Partial<Props> = {
buttonType: 'button',
}
Should work

TypeScript warning for connected component with own props

I have two components (smart and dumb) as follows:
// Container component file...
interface OwnProps {
id: Id;
}
function mapStateToProps(state: State, ownProps: OwnProps) {
return { isActive: ... };
}
export const Container: any = connect(mapStateToProps, null)(DumbComponent);
// Dumb component file...
interface DumbComponentProperties {
isActive: boolean;
}
export class DumbComponent extends React.Component<DumbComponentProperties, {}> {
...
}
I get a red-squiggly warning under the DumbComponent inside connect, that:
Argument of type 'typeof DumbComponent' is not assignable to parameter of
type 'Component OwnProps & { isActive: boolean;
}'....>
If I make id optional via id?, this fixes the error. This looks like TypeScript thinks DumbComponent needs the OwnProps type as well because id is being passed as a prop to it? I don't understand why this is the case though, isn't only the property isActive being passed to DumbComponent?
I don't see any problem with your code but it's a good practice to type the function mapStateToProps function, like,
function mapStateToProps(state: State, ownProps: OwnProps): DumbComponentProperties {
return { isActive: ... };
}

TypeScript + React: defining defaultProps correctly

Say you define your component like so:
interface IProps {
req: string;
defaulted: string;
}
class Comp extends React.Component<IProps, void> {
static defaultProps = {
defaulted: 'test',
};
render() {
const { defaulted } = this.props;
return (
<span>{defaulted.toUpperCase()}</span>
);
}
}
when you want to use it, TypeScript wants the defaulted prop from you, even though it's defined in defaultProps:
<Comp req="something" /> // ERROR: TypeScript: prop 'defaulted' is required
However, if you define the props interface like so:
interface IProps {
req: string;
defaulted?: string; // note the ? here
}
then you cannot use it in:
render() {
const { defaulted } = this.props; // ERROR: prop 'defaulted' possibly undefined
return (
<span>{defaulted.toUpperCase()}</span>
);
}
How to define the IProps, defaultProps and component correctly so the types make sense?
EDIT:
I'm using the strictNullChecks flag.
I have an example with the following code (ComponentBase is just my wrapper around React.Component).
Edit: updated code to work with 'strictNullChecks' setting
interface IExampleProps {
name: string;
otherPerson?: string;
}
/**
* Class with props with default values
*
* #class Example
* #extends {ComponentBase<IComponentBaseSubProps, {}>}
*/
export class Example extends ComponentBase<IExampleProps, {}> {
public static defaultProps: IExampleProps = {
otherPerson: "Simon",
name: "Johnny"
};
constructor(props: IExampleProps) {
super(props);
}
public render(): JSX.Element {
const person: string = this.props.otherPerson === undefined ? "" : this.props.otherPerson;
return(
<div>
<h1><small>Message by ComponentBaseSub: Hello {this.props.name} and {person} </small></h1>
</div>
);
}
}
I have no issues using Visual Studio Code, TypeScript 2.0.3, TSLint 0.5.39.
Even simpler is
<span>{(defaulted as string).toUpperCase()}</span>
Works the same way with properties. If Foo requires the barProp property but Parent does not and gets it through defaultProps, Parent's render method can do
<Foo barProp={this.props.barProp as string} />
If you know for sure the prop will have a default value, you can use the null assertion type operator, like this:
render() {
const { defaulted } = this.props;
return (
<span>{defaulted!.toUpperCase()}</span>
);
}

Categories

Resources