Cannot pass props to component through connect() - reactjs

I am using React / Redux with TypeScript transpilation. I want my navigation menu to read from state and to maintain its own mini-state.
NavMenu.tsx:
import * as React from 'react';
import { NavLink, Link, RouteComponentProps } from 'react-router-dom';
import { connect } from 'react-redux';
import { ApplicationState } from '../store';
import * as NavigationStore from '../store/Navigation';
type NavigationProps =
NavigationStore.NavigationState
& typeof NavigationStore.actionCreators;
class NavMenu extends React.Component<NavigationProps, {}> {
public render() {
return (
<nav className='main-nav'>
<ul className={`nav-standard`}>
<li>
<NavLink exact to={'/'} activeClassName='active'>
Home
</NavLink>
</li>
<li>
<NavLink to={'/learn'} activeClassName='active'>
Learn
</NavLink>
</li>
<li>
<NavLink to={'/blog'} activeClassName='active'>
Blog
</NavLink>
</li>
</ul>
<div className='nav-small'>
<button type='button' className='navbar-toggle' onClick={() => { this.props.toggle() } }>
<span className='screen-reader-content'>Toggle Navigation</span>
<i className='fa fa-bars'></i>
</button>
</div>
</nav>
);
}
}
export default connect(
(state: ApplicationState) => state.navigation,
NavigationStore.actionCreators
)(NavMenu) as typeof NavMenu;
Here is how I am trying to render the navbar, Layout.tsx:
import * as React from 'react';
import NavMenu from './NavMenu';
export class Layout extends React.Component<{}, {}> {
public render() {
return <div>
<NavMenu />
{ this.props.children }
</div>;
}
}
I'm getting a typescript transpilation error:
TS2322: Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<NavMenu> & Readonly<{ children?: ReactNode; }> & R...'.
Type '{}' is not assignable to type 'Readonly<NavigationProps>'.
Property 'expanded' is missing in type '{}'.
Isn't this the purpose of the connect() function? To automatically map state and actions to a component? This is normally the error I get when not passing props down to a component (without using connect()).
EDIT
It does work when I exhaustively pass down all the properties expected for a NavigationProps type:
import * as React from 'react';
import NavMenu from './NavMenu';
export class Layout extends React.Component<{}, {}> {
public render() {
return <div>
<NavMenu expanded={false} expand={Navigation.actionCreators.expand} constrict={Navigation.actionCreators.constrict} toggle={Navigation.actionCreators.toggle} />
{ this.props.children }
</div>;
}
}
Navigation.ts for posterity (new import):
import { Action, Reducer } from 'redux';
export interface NavigationState {
expanded: boolean;
};
interface ExpandNavigationAction { type: 'EXPAND_NAVIGATION' }
interface ConstrictNavigationAction { type: 'CONSTRICT_NAVIGATION' }
interface ToggleNavigationAction { type: 'TOGGLE_NAVIGATION' }
type KnownAction = ExpandNavigationAction
| ConstrictNavigationAction
| ToggleNavigationAction;
export const actionCreators = {
expand: () => <ExpandNavigationAction>{ type: 'EXPAND_NAVIGATION' },
constrict: () => <ConstrictNavigationAction>{ type: 'CONSTRICT_NAVIGATION'
},
toggle: () => <ToggleNavigationAction>{ type: 'TOGGLE_NAVIGATION' }
};
export const reducer: Reducer<NavigationState> = (state: NavigationState,
action: KnownAction) => {
switch (action.type) {
case 'EXPAND_NAVIGATION':
return { expanded: true };
case 'CONSTRICT_NAVIGATION':
return { expanded: false };
case 'TOGGLE_NAVIGATION':
return { expanded: !state.expanded };
default:
const exhaustiveCheck: never = action;
}
return state || { expanded: false };
}
Even though this transpiles successfully, my goal when doing this was to avoid writing out such verbose markup. I was under the impression the whole point of the connect() method was to simply and easily pass the correct properties from state down to the child component via mapper methods.

Don't cast the result of connect() to typeof NavMenu. When you do that you're telling TS that the component expects a NavigationProps object to be passed in as a property. So it makes sense that you get an error when you don't pass in any properties.
The wrapped component created by connect() has no required properties.

Related

react typescript with function passed as prop, but can't access

I am making a simple toggle button with react + typescript.
I want to practice some complex feature by passing function as prop to child component.
I remember 'this.props' allows me to access all props passed to child in typescript. So if I define a function in parent, I should be able to call the function in child, right?
But I got the below error. Can someone please give me a helping hand? Thank you.
Error:
(property) React.DOMAttributes.onClick?: React.MouseEventHandler | undefined
Type '(checked: boolean) => void' is not assignable to type 'MouseEventHandler'.
Types of parameters 'checked' and 'event' are incompatible.
Type 'MouseEvent' is not assignable to type 'boolean'.ts(2322)
Code:
src/
import React from 'react';
import ReactDOM from 'react-dom';
import Switch from './Switch';
interface State {
buttonOn: boolean;
}
class App extends React.PureComponent<{}, State> {
public state: State = {
buttonOn: false,
};
onChange = (checked: boolean) => this.setState({ buttonOn: checked });
render() {
return (
<div>
<Switch
checked={this.state.buttonOn}
onChange={(checked) => this.onChange(!checked)}
/>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app')!);
src/Switch/
import React from 'react';
import './index.css';
interface Props {
checked: boolean;
onChange: (checked: boolean) => void;
disabled?: boolean;
}
export default class Switch extends React.PureComponent<Props> {
render() {
return (
<div>
<div onClick={this.props.onChange} /> {/* error here */}
</div>
);
}
}
so you have 2 options... Either
Do it like this where this.props.onChange is being returned by the onClick lambda function. You can also wrap this in curly braces if you want
export default class Switch extends React.PureComponent<Props> {
render() {
return (
<div>
<div onClick={(e) => this.props.onChange} /> {/* error here */}
</div>
);
}
}
or;
Change your types... It's always worth hovering over a property such as onClick to understand the function signature as well as the type of arguments that are being passed in.
# src/App.tsx
import React from "react";
import Switch from "./Switch";
interface State {
buttonOn: boolean;
}
class App extends React.PureComponent<{}, State> {
public state: State = {
buttonOn: false
};
onChange = (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
console.log(event);
};
render() {
return (
<div>
<Switch checked={this.state.buttonOn} onChange={this.onChange} />
</div>
);
}
}
export default App;
Then in the Switch:
# src/Switch.tsx
import React from "react";
interface Props {
checked: boolean;
onChange: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
disabled?: boolean;
}
export default class Switch extends React.PureComponent<Props> {
render() {
return (
<div>
<button onClick={this.props.onChange}> Hello world </button>
</div>
);
}
}
I wrote a Codesandbox playground here so you can test it out yourself.

ReactJs passing methods as props from one class to the next (with typescript)

I worked with react before, but using type script I am having issues pass methods as props. I having trouble following some of the examples on the internet.
Property 'onDrawerToggle' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes & Readonly<{ companyId: string; }> & Readonly<{ children?: ReactNode; }>'. TS2322
import React from 'react';
import HeaderBar from './Header';
import SideDrawer from './SideDrawer';
import BackDrop from './BackDrop';
interface PassedProps extends React.Props<any> {
onDrawerToggle:any
}
interface MyState {
value: string
}
export default class FrontScreen extends React.Component <{},{sideDrawer:any},PassedProps>{
constructor(props: any) {
super(props);
this.state = {
sideDrawer: false
};
}
drawerToggleClickHandler() {
this.setState((prevState)=>{
this.state.sideDrawer != prevState.sideDrawer
});
}
render() {
return (
<div className="fluid-container">
<HeaderBar onDrawerToggle={this.drawerToggleClickHandler()} companyId="10"></HeaderBar>
</div>
);
}
}
I am trying to pass a method to next class, but i keep getting an error. I trying to figure this out cause i never used typescript if you could give me an example I would be able to replicate and understand it.
import React from 'react';
import { Navbar, Nav, Form, Button, FormControl } from 'react-bootstrap';
import axios from 'axios';
import ProfileButton from './ProfileButton';
interface Props {
drawerClick: () => any;
}
export default class HeaderBar extends React.Component <{companyId:string},{companyName:any},{drawerClick:any}>{
constructor(props: any) {
super(props);
this.state = {
companyName: 'load',
};
}
limitTextFilter(text:string){
if(text.length > 14) {
return text.substring(0,14);
}
return text;
}
componentDidMount() {
axios.request(
{method:'get',
url:'http://localhost:3000/company'
})
.then(res => {
this.setState({ companyName: res.data.companyName });
console.log(res)
})
}
render() {
return (
<Navbar className="cs-header-bar p-0">
<Nav className="align-middle w-100">
<Nav.Link className="text-white">
<ProfileButton onClick={this.props.drawerClick}/>
</Nav.Link>
<Nav.Link className="text-white align-self-center">
<p className="cs-link cs-company-name">{this.limitTextFilter(this.state.companyName)}
</p>
</Nav.Link>
</Nav>
</Navbar>
);
}
}
You pass the onDrawerToggle as an prop of the component HeaderBar, so you must declare the type of props of the component HeaderBar.
type Props = {
onDrawerToggle: Function
}
export default class HeaderBar extends React.Component<Props>{
...
}
By the way, you can declare the state type of your component by passing the second type param:
export default class HeaderBar extends React.Component<Props, State>{
...
}

React dynamic menu from database

so I have been trying to create dynamic menu in .net core + react environment
the problem I have been facing is basically this error message
Type '{}' is not assignable to type
'Readonly<RouteComponentProps<{}>>'.
Property 'match' is missing in type '{}'
Here is the code for
Layout.tsx
import * as React from 'react';
import { NavMenu } from './NavMenu';
export interface LayoutProps {
children?: React.ReactNode;
}
export class Layout extends React.Component<LayoutProps, NavMenu> {
public render() {
return <div className='container-fluid'>
<div className='row'>
<div className='col-sm-3'>
<NavMenu />
</div>
<div className='col-sm-9'>
{ this.props.children }
</div>
</div>
</div>;
}
}
the other file is NavMenu.tsx
import * as React from 'react';
import { Link, NavLink } from 'react-router-dom';
import { RouteComponentProps } from 'react-router';
import * as ReactDOM from 'react-dom';
interface navMenuItems {
menuItemsList: NavMenuPages[];
loading: boolean;
}
export class NavMenu extends React.Component<RouteComponentProps<{}>, navMenuItems> {
constructor() {
super();
this.state = { menuItemsList: [], loading: true };
fetch('api/Menu')
.then(response => response.json() as Promise<NavMenuPages[]>)
.then(data => {
this.setState({ menuItemsList: data, loading: false });
});
}
public render() {
this.renderMenu(this.state.menuItemsList);
return <div></div>;
}
public renderMenu(menuItemsList: NavMenuPages[]) {
return <div className='main-nav'>
<div className='navbar navbar-inverse'>
<div className='navbar-header'>
<button type='button' className='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
<span className='sr-only'>Toggle navigation</span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
<span className='icon-bar'></span>
</button>
<Link className='navbar-brand' to={'/'}>ReactCrudDemo</Link>
</div>
<div className='clearfix'></div>
<div className='navbar-collapse collapse'>
<ul className='nav navbar-nav'>
{menuItemsList.map(mil =>
<li>
<NavLink to={`${mil.toLink}`} exact activeClassName='active'>
<span className='glyphicon glyphicon-home'></span> {mil.name}
</NavLink>
</li>
)}
</ul>
</div>
</div>
</div>;
}
}
export class NavMenuPages{
name: string = "";
toLink: string = "";
isShown: boolean = true;
}
remaining files are the same as this tutorial:
https://www.c-sharpcorner.com/article/asp-net-core-crud-with-reactjs-and-entity-framework-core/
You are declaring NavMenu as requiring the RouteComponentProps<{}>, which it would normally receive automatically if you call it via <Route>, but you are calling it directly without passing the required props. Since NavMenu does not use any of the RouteComponentProps, you can just change the props type. Replace:
export class NavMenu extends React.Component<RouteComponentProps<{}>, navMenuItems>
with:
export class NavMenu extends React.Component<{}, navMenuItems>

Can't render another component from another

I am having trouble rendering another component from my current component in vs code. The error message I get is..
Type '{}' is not assignable to type 'IntrinsicAttributes &
IntrinsicClassAttributes & Readonly<{ children?: ReactNode;
}> & ...'. Type '{}' is not assignable to type
'Readonly>'.
Property 'match' is missing in type '{}'.
Here is my code..
import * as React from 'react';
import { BillList } from './BillList';
import * as ReactDOM from "react-dom";
export interface BillComponentState {
children?: React.ReactNode,
bills: BillList
}
export class BillComponent extends React.Component<BillList>{
public render() {
return <div className='container-fluid'>
<div className='row'>
<div className='col-sm-3'>
<BillList />
</div>
<div className='col-sm-9'>
{this.props.children}
</div>
</div>
</div>;
}
}
Why can't I just render my BillList which is literally rendering a list of strings from a web api on my web server?
Disclaimer: I'm very new to React
EDIT: Here is the code in BillList.tsx
import * as React from 'react';
import { RouteComponentProps } from 'react-router';
interface BillState {
bills: Bill[],
loading: boolean
}
export class BillList extends React.Component<RouteComponentProps<{}>, BillState>
{
constructor() {
super();
this.state = { bills: [], loading: true };
fetch("api/SampleData/GetBills")
.then(response => response.json() as Promise<Bill[]>)
.then(data => {
this.setState({
bills: data,
loading: false
});
});
}
public render() {
let contents = this.state.loading
? <p><em>Loading...</em></p>
: BillList.renderBillsToList(this.state.bills);
return <div className="rendered-bills">
<h1>Bills to pay</h1>
{contents}
</div>
}
public static renderBillsToList(bills: Bill[]) {
return <ul>
{bills.map((bill, i) =>
<li key={i}> {bill.name} </li>
)}
</ul>;
}
}
interface Bill {
name: string;
}
Here is the code in a public repo. Let me know if you need more debugging information from me.
https://github.com/ddeamaral/billTracker
This code sample doesn't provide enough information to tell where the error originated from. However, the React.Component first parameter accepts the components properties and you seem to be passing on a component (BillList). Also there is a BillComponentState interface defined but never used and I don't see any actual component state.
Based on this code sample I can only advise you to rename BillComponentState to BillComponentProps and pass this to the component instead of BillList.
import * as React from 'react';
import { BillList } from './BillList';
import * as ReactDOM from "react-dom";
export interface BillComponentProps {
children?: React.ReactNode,
bills: BillList
}
export class BillComponent extends React.Component<BillComponentProps>{
public render() {
return <div className='container-fluid'>
<div className='row'>
<div className='col-sm-3'>
<BillList />
</div>
<div className='col-sm-9'>
{this.props.children}
</div>
</div>
</div>;
}
}
But this is just guessing since we don't know what BillList looks like.

Extending HTML elements in React and TypeScript while preserving props

I just can't wrap my head around this I guess, I've tried probably half a dozen times and always resort to any... Is there a legitimate way to start with an HTML element, wrap that in a component, and wrap that in another component such that the HTML props pass through everything? Essentially customizing the HTML element? For example, something like:
interface MyButtonProps extends React.HTMLProps<HTMLButtonElement> {}
class MyButton extends React.Component<MyButtonProps, {}> {
render() {
return <button/>;
}
}
interface MyAwesomeButtonProps extends MyButtonProps {}
class MyAwesomeButton extends React.Component<MyAwesomeButtonProps, {}> {
render() {
return <MyButton/>;
}
}
Usage:
<MyAwesomeButton onClick={...}/>
Whenever I attempt this sort of composition, I get an error similar to:
Property 'ref' of foo is not assignable to target property.
You can change the definition of your component to allow the react html button props
class MyButton extends React.Component<MyButtonProps & React.HTMLProps<HTMLButtonElement>, {}> {
render() {
return <button {...this.props}/>;
}
}
That will tell the typescript compiler that you want to enter the button props along with 'MyButtonProps'
I always like to do it this way:
import React from 'react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
title: string;
showIcon: boolean;
}
const Button: React.FC<ButtonProps> = ({ title, showIcon, ...props }) => {
return (
<button {...props}>
{title}
{showIcon && <Icon/>}
</button>
);
};
Then you can do:
<Button
title="Click me"
onClick={() => {}} {/* You have access to the <button/> props */}
/>
Seems Like the above answer is outdated.
In my case I'm wrapping a styled component with a functional component, but still want to expose regular HTML button properties.
export const Button: React.FC<ButtonProps &
React.HTMLProps<HTMLButtonElement>> = ({
children,
icon,
...props,
}) => (
<StyledButton {...props}>
{icon && <i className="material-icons">{icon}</i>}
{children}
</StyledButton>
);
This is what I do when extending native elements:
import React, { ButtonHTMLAttributes, forwardRef } from "react";
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
myExtraProp1: string;
myExtraProp2: string;
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ myExtraProp1, myExtraProp2, ...props }, ref) => (
<button
{...props}
ref={ref}
// Do something with the extra props
/>
),
);
Button.displayName = "Button";
forwardRef ensures that you can get a reference to the underlying HTML element with ref when using the component.
This worked for my by using a type (instead of an interface):
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
children: React.ReactNode;
icon?: React.ReactNode;
};
function Button({ children, icon, ...props }: ButtonProps) {
return (
<button {...props}>
{icon && <i className="icon">{icon}</i>}
{children}
</button>
);
}
if you're using styled components from '#emotion/styled', none of the answers work.
I had to go a little deeper.
import styled from "#emotion/styled";
import React, { ButtonHTMLAttributes } from 'react';
export type ButtonVariant = 'text' | 'filled' | 'outlined';
export const ButtonElement = styled.button`
display: flex;
align-items: center;
justify-content: center;
padding: 12px 16px;
`;
export interface ButtonProps {
variant: ButtonVariant;
}
export const Button: React.FC<ButtonProps & React.DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>> = ({
children,
variant,
...props
}) => (
<ButtonElement
{...props}
>
{children}
</ButtonElement>
);
this style allows you to pass all props that button has, and more than that, padding {...props} to ButtonElement allows you to easily reuse Button with styled-components, to do css changes you want in a good way
import { Button } from '#components/Button';
export const MySpecificButton = styled(Button)`
color: white;
background-color: green;
`;
import * as React from "react";
interface Props extends React.HTMLProps<HTMLInputElement> {
label?: string;
}
export default function FormFileComponent({ label, ...props }: Props) {
return (
<div>
<label htmlFor={props?.id}></label>
<input type="file" {...props} />
</div>
);
}
I solve this code for me, you just have to import ButtonHTMLAttributes from react and that's it
import { ButtonHTMLAttributes } from "react";
interface MyButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
children: any;
}
export const MyButton = (props: ButtonI) => {
const { children } = props;
return <button {...props}>{children}</button>;
};
You need extend your interface.
import {ButtonHTMLAttributes, ReactNode} from "react";
export interface ButtonProps extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>{
appearance: 'primary' | 'ghost';
children: ReactNode;
}
Extend HTML Element with Ref & Key
TL;DR
If you need to be able to accept `ref` and key then your type definition will need to use this long ugly thing:
import React, { DetailedHTMLProps, HTMLAttributes} from 'react';
DetailedHTMLProps<HTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
Type Definition
Looking at the type definition file, this is the type. I'm not sure why it isn't shorter, it seems you always pass the same HTMLElement twice?
type DetailedHTMLProps<E extends HTMLAttributes<T>, T> = ClassAttributes<T> & E;
Shortened DetailedHTMLProps
You could create your own type to shorten this for our case (which seems to be the common case).
import React, { ClassAttributes, HTMLAttributes} from 'react';
type HTMLProps<T> = ClassAttributes<T> & HTMLAttributes<T>;
export interface ButtonProps extends HTMLProps<HTMLButtonElement> {
variant: 'contained' | 'outlined';
}
Sample Component
import React, {ClassAttributes, HTMLAttributes, ForwardedRef, forwardRef} from 'react';
type HTMLProps<T> = ClassAttributes<T> & HTMLAttributes<T>;
export interface ButtonProps extends HTMLProps<HTMLButtonElement> {
variant: 'contained' | 'outlined';
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
return (
<button key="key is accepted" ref={ref} {...props}>
{props.children}
</button>
);
},
);
private yourMethod(event: React.MouseEvent<HTMLButtonElement>): void {
event.currentTarget.disabled = true;
}
<Button
onClick={(event) => this.yourMethod(event)}
/>
I encountered the same issue today and here is how I fixed it:
ReactButtonProps.ts
import {
ButtonHTMLAttributes,
DetailedHTMLProps,
} from 'react';
/**
* React HTML "Button" element properties.
* Meant to be a helper when using custom buttons that should inherit native "<button>" properties.
*
* #example type MyButtonProps = {
* transparent?: boolean;
* } & ReactButtonProps;
*/
export type ReactButtonProps = DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>;
Usage in Button-ish component:
import classnames from 'classnames';
import React, { ReactNode } from 'react';
import { ReactButtonProps } from '../../types/react/ReactButtonProps';
type Props = {
children: ReactNode;
className?: string;
mode?: BtnMode;
transparent?: boolean;
} & ReactButtonProps;
const BtnCTA: React.FunctionComponent<Props> = (props: Props): JSX.Element => {
const { children, className, mode = 'primary' as BtnMode, transparent, ...rest } = props;
// Custom stuff with props
return (
<button
{...rest} // This forward all given props (e.g: onClick)
className={classnames('btn-cta', className)}
>
{children}
</button>
);
};
export default BtnCTA;
Usage:
<BtnCTA className={'test'} onClick={() => console.log('click')}>
<FontAwesomeIcon icon="arrow-right" />
{modChatbot?.homeButtonLabel}
</BtnCTA>
I can now use onClick because it's allowed due to extending from ReactButtonProps, and it's automatically forwarded to the DOM through the ...rest.
import { FC, HTMLProps } from 'react';
const Input: FC<HTMLProps<HTMLInputElement>> = (props) => {
return <input {...props} />;
};
You can do this to extend the button properties
import { ButtonHTMLAttributes, ReactNode } from "react";
interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
}
const Button = ({ children, ...props }: Props): JSX.Element => {
return <button {...props}>{children}</button>;
};

Resources