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.
Related
Background
I wrote an exact, short yet complete example of a Parent component with a nested Child component which simply attempts:
Alter a string in the Parent's state
See the Child component updated when the Parent's state value is altered (this.state.name)
Here's What It Looks Like
When the app loads a default value is passed from Parent state to child props.
Change The Name
All I want to do is allow the change of the name after the user adds a new name in the Parent's <input> and clicks the Parent's <button>
However, as you can see, when the user clicks the button only the Parent is rendered again.
Questions
Is it possible to get the Child to render the new value?
What am i doing wrong in this example -- why isn't it updating or
rendering the new value?
All Source Code
Here is all of the source code and you can view it and try it in my StackBlitz project.
I've kept it as simple as possible.
Parent component (DataLoader)
import * as React from 'react';
import { useState } from 'react';
import { Grid } from './Grid.tsx';
interface LoaderProps {
name: string;
}
export class DataLoader extends React.Component<LoaderProps, {}> {
state: any = {};
constructor(props: LoaderProps) {
super(props);
this.state.name = this.props.name;
this.changeName = this.changeName.bind(this);
}
render() {
const { name } = this.state;
let parentOutput = <span>{name}</span>;
return (
<div>
<button onClick={this.changeName}>Change Name</button>
<input id="mapvalue" type="text" placeholder="name" />
<hr id="parent" />
<div>### Parent ###</div>
<strong>Name</strong>: {parentOutput}
<hr id="child" />
<Grid childName={name} />
</div>
);
}
changeName() {
let newValue = document.querySelector('#mapvalue').value.toString();
console.log(newValue);
this.setState({
name: newValue,
});
}
}
Child component (Grid)
import * as React from 'react';
interface PropsParams {
childName: string;
}
export class Grid extends React.Component<PropsParams, {}> {
state: any = {};
constructor(props: PropsParams) {
super(props);
let counter = 0;
this.state = { childName: this.props.childName };
console.log(`CHILD -> this.state.name : ${this.state.childName}`);
}
render() {
const { childName } = this.state;
let mainChildOutput = <span>{childName}</span>;
return (
<div>
<div>### Child ####</div>
<strong>Name</strong>: {mainChildOutput}
</div>
);
}
}
App.tsx is set up like the following -- this is where default value comes in on props
import * as React from 'react';
import { DataLoader } from './DataLoader.tsx';
import './style.css';
export default function App() {
return (
<div>
<DataLoader name={'default value'} />
</div>
);
}
You're seeing two different values because you're tracking two different states. One in the parent component and one in the child component.
Don't duplicate data.
If the child component should always display the prop that's passed to it then don't track state in the child component, just display the prop that's passed to it. For example:
export class Grid extends React.Component<PropsParams, {}> {
render() {
const { childName } = this.props; // <--- read the value from props, not local state
let mainChildOutput = <span>{childName}</span>;
return (
<div>
<div>### Child ####</div>
<strong>Name</strong>: {mainChildOutput}
</div>
);
}
}
In the Child component, you set the prop childName value to state in the contructor ONLY. The constructor is executed ONLY WHEN THE COMPONENT IS MOUNTED. So, it doesn't know if the childName prop is changed later.
There are 2 solutions for this.
(1) Directly use this.props.childName without setting it to a state.
(2) Add a useEffect that updates the state value on prop change.
React.useEffect(() => {
this.state = {
childName: this.props.childName;
};
}, [this.props.childName]);
However, I recommend 1st solution since it's not a good practice to duplicate data.
I have customize a react-select drop-down component for my react-project.When i try to extend the interface using React.HTMLAttributes<HTMLSelectElement | HTMLInputElement>. It shows "No overload matches this call".
I have tried to extend different attributes to get the default values like id, label, name, placeholder, etc.
How can I get the default properties inside the props?
Did anyone experience any similar issue?
sample code:
import * as React from "react";
import ReactSelect from 'react-select';
export interface SelectProps extends React.HTMLAttributes<HTMLSelectElement | HTMLInputElement> {
options: Array<any>;
isMulti: boolean;
isDisabled: boolean;
};
const Select: React.FC<SelectProps> = (props: SelectProps) => {
return (<div>
<label htmlFor={props.id}>{props.label}</label>
<div>
<ReactSelect
{...props}
/>
</div>
</div>)
}
export default Select;
After using the above code, I am not able to get props.id or props.name, etc.
Edit:
React-Select v5 now natively supports TypeScript, therefore some type changes have been made (details).
Here's an updated example for v5 (react-select/async), similar to the original one with v4:
import ReactSelectAsync, { AsyncProps } from "react-select/async";
import { GroupBase } from "react-select";
interface CustomSelectAsyncProps {
additionalCustomProp: number;
}
function SelectAsync<
OptionType,
IsMulti extends boolean = false,
GroupType extends GroupBase<OptionType> = GroupBase<OptionType>
>({
additionalCustomProp,
...props
}: AsyncProps<OptionType, IsMulti, GroupType> & CustomSelectAsyncProps) {
return <ReactSelectAsync {...props} />;
}
export default SelectAsync;
Codesandbox
Original Answer:
This is documented on the official react-select documentation.
Quote:
Wrapping the Select component
Oftentimes the Select component is wrapped in another component that is used throughout an app and the wrapper should be just as flexible as the original Select component (i.e., allow for different Option types, groups, single-select, or multi-select). In order to provide this flexibility, the wrapping component should re-declare the generics and forward them to the underlying Select. Here is an example of how to do that:
function CustomSelect<
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>(props: Props<Option, IsMulti, Group>) {
return (
<Select {...props} theme={(theme) => ({ ...theme, borderRadius: 0 })} />
);
}
It also explains how to extend the props with additional custom props:
You can use module augmentation to add custom props to the Select prop types:
declare module 'react-select/dist/declarations/src/Select' {
export interface Props<
Option,
IsMulti extends boolean,
Group extends GroupBase<Option>
> {
myCustomProp: string;
}
}
But I personally prefer to just add a custom interface using the & character with a custom interface to add custom props (example with ReactSelectAsync, see ... & CustomSelectAsyncProps):
interface CustomSelectAsyncProps {
additionalCustomProp: number;
}
function SelectAsync<
OptionType extends OptionTypeBase,
IsMulti extends boolean = false,
GroupType extends GroupTypeBase<OptionType> = GroupTypeBase<OptionType>
>({
additionalCustomProp,
...props
}: Props<OptionType, IsMulti, GroupType> & CustomSelectAsyncProps) {
return (
<ReactSelectAsync {...props} />
);
}
You can use like this:
import React from 'react'
import { omit } from 'lodash';
import Select, { GroupBase, Props } from 'react-select';
type SelectProps<Option, IsMulti extends boolean = false, Group extends GroupBase<Option> = GroupBase<Option>> = Props<Option, IsMulti, Group> & {
label?: string;
id?: string;
}
const SearchableSelect = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>(props: SelectProps<Option, IsMulti, Group>) => {
const id = props.id || Math.random().toString()
const reactSelectProps = omit(props, ['label', 'className'])
return (
<div>
{props.label && (
<label htmlFor={id} className="block text-sm font-medium text-gray-700">
{props.label}
</label>
)}
<Select
{...reactSelectProps}
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
primary: "var(--primary-500)",
},
})}
/>
</div>
);
}
export default SearchableSelect
You can import the Props type directly from the react-select package. You probably want to extend it in order to require that label and id are both defined.
import React from "react";
import ReactSelect, { Props } from "react-select";
type SelectProps = Props & {
id: string;
label: string;
};
const Select: React.FC<SelectProps> = (props) => {
return (
<div>
<label htmlFor={props.id}>{props.label}</label>
<div>
<ReactSelect {...props} />
</div>
</div>
);
};
I'm using React and TypeScript and trying to pass some data like prop to child component and use it in child component. But i'm getting error and i can't understand why is happened and how fix it. I'm beginner on TypeScript also.
Here is my parent component
import * as React from "react";
import ChildComponent from "./ChildComponent";
const data = [
{
title: "A",
id: 1,
},
{
title: "B",
id: 1,
},
];
const ParentComponent = () => {
return (
<ChildComponent items={data} />
)
}
export default ParentComponent;
Here is error in parent component at items
(JSX attribute) items: {
title: string;
id: number;
}[]
Type '{ items: { title: string; id: number; }[]; }' is not assignable to type 'IntrinsicAttributes'.
Property 'items' does not exist on type 'IntrinsicAttributes'.ts(2322)
And when in regular react and es6 i can use this props in child component like this:
const ChildComponent = (props) => {
return (
<div>
{props.items.map((item) => (
<p to={item.title}></p>
))}
</div>
)
}
but have use this props in child component if it will be TypeScript?
You need to specify what type of props the child component wants. For example:
interface Item {
title: string;
id: number;
}
interface ChildComponentProps {
items: Item[]
}
const ChildComponent: React.FC<ChildComponentProps> = (props) => {
return (
<div>
{props.items.map((item) => (
<p to={item.title}></p>
))}
</div>
)
}
Added to the reply, if the props can be null, you put a question mark.
interface ChildComponentProps {
items?: Item[]
}
This question already has an answer here:
How do I type this 'as' JSX attribute in TypeScript?
(1 answer)
Closed 3 years ago.
I'm creating a component that looks like this:
<Button component='a' href='/'>
Link
</Button>
I want to get the type definition of the component prop, so I can automatically include it in the props for the Button component. Any ideas on what the interface/type for the Button component should look like
I've used this pattern before. Declare the component prop of type React.ComponentType<T> | string. Your Button component should look a bit like this:
export interface ButtonProps {
component?: React.ComponentType<any>; // or React.ReactElement<any>["type"]
href?: string;
children?: React.ReactNode;
}
export function Button(props: ButtonProps) {
const {
component: Component = "button", // note: uppercase
children,
...other
} = props;
return (
<Component {...other}>
{children}
</Component>
);
}
Your Button component could be typed like that:
import * as React from 'react';
type ButtonProps = {
component: string,
href: string,
}
type ButtonState = {}
export default class Button extends React.Component<ButtonProps , ButtonState > {
constructor(props) {
super(props);
this.state = {}
}
render() { return <div/> }
}
I wrote a whole article on building a TypeScript + React app from A to Z, check it out for complete details and explanations!
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})