"Expected 1 arguments, but got 0." for onClick event in React with Typescript - reactjs

I have a handleClick function that I'm trying to pass as a prop for my onClick event within a component. This event just takes a setState function. I set an interface to type this as "handleClick: React.Dispatch<React.SetStateAction>;" as nothing else worked and would always give errors, so I assumed all was well, until I went ahead with writing the onClick event into the component declaration, when the error in the title appeared.
Here's the relevant code:
interface IProps {
handleClick: React.Dispatch<React.SetStateAction<boolean>>;
icon?: JSX.Element;
}
const NavLinks: React.FC<IProps> = ({ handleClick }) => (
<div className="sidebar_navlinks">
{sidebar_links.map((link) => (
<NavLink key={link.name} to={link.to} onClick={() => handleClick && handleClick()}>
<div className="link">
<link.icon className="icon" />
{link.name}
</div>
</NavLink>
))}
</div>
)
And then with that component I just do something like
<NavLinks handleClick={() => setMenuState(false)} />
How can I best type this so it stops giving the error in the title? I'm not clear why it would expect there's a value when I'm typed it to be something that sets state?
I see stuff online that, more often than not, is assuming the onClick is going to apply to an HTML button element, but I'm just using this to click on react-icons, so I'm even more lost.

handleClick should be of type () => void since setMenuState is wrapped in a function.
interface IProps {
handleClick: () => void;
icon?: JSX.Element;
}
If you passed setMenuState directly like:
<NavLinks handleClick={setMenuState} />
then it can be typed as a setState function

Related

TypeScript / React - Trying to Create My Own Button Issue - Function Signature Problem

I'm trying to create a simple button in React / TypeScript.
I'm not sure where I am getting the syntax for this incorrect.
Editors don't seem to like the ' => void ' part of my signature.
My goal is to be able to pass down a "clear state" type handler from any parent component.
import React from 'react';
import ClearIcon from '#material-ui/icons/Clear';
// problem is on this line
export function ClearButton(props: {onClick: (React.MouseEvent<HTMLElement>) => void}) {
return (<span
onClick={(e) => { props.onClick(e) }}>
<ClearIcon /></span>);
}
I believe you forgot the name of the function's parameter.
Example:
onClick: (e: React.MouseEvent<HTMLElement>) => void
Also, the actual onClick on the span should probably be assigned like so:
onClick={(e) => props.onClick(e)}>
Alternatively, you could use a shorter syntax in this case, if you prefer.
onClick={props.onClick}>
So your code becomes:
export function ClearButton(props: {onClick: (e: React.MouseEvent<HTMLElement>) => void}) {
return (
<span onClick={(e) => props.onClick(e)}>
<ClearIcon />
</span>
)
}

Best way to design a modal form in react / ionic

I'm learning react so I'm building a weight tracker.
I have different pages where i ask some datas. So i want to develop a modal form.
I have already have a Modal component from Ionic.
So i builded a ModalForm with an header with close, a cancel and a ok button.
Inside the content i render props.childrens.
Something like that
<App>
<ModalForm>
<Input>
</ModalForm>
</App>
On pressing "Ok" the component will give the input value to the parent via callback.
That value will be validated ( so i cant give the value onChange).
But it will need to know the values of childrens input.
Moreover the parent will have control of inputs ( and validation ), that is not a thing that i like.
I can let the modal choose what inputs render with an internal switch, but it cant be reused for other porpuse.
Should abandon childrens and found another way ?
Please give me some advice on how composite my components to achieve this results.
Thank you
I've found a way.
I have a parent component, that is like a wrapper or a decorator, but is lower than my final component.
interface ModalProps {
title: string,
show: boolean,
setShow: Function,
value: number | string,
onSave: Function
}
const ModalInput: React.FC<ModalProps> = (props) => {
var { show, setShow, title, value, onSave } = props;
return (
<IonPopover isOpen={show} onDidDismiss={() => { setShow(false); }}>
<IonContent class="ion-text-center modal-content">
<IonCard>
<IonCardHeader>
<IonCardTitle>{title}</IonCardTitle>
</IonCardHeader>
<IonCardContent className="text-center">
{props.children}
</IonCardContent>
</IonCard>
<IonFooter>
<IonButton color="light" onClick={() => { setShow(false); }}>Cancel</IonButton>
<IonButton color="primary" onClick={() => { onSave(value); setShow(false); }}><IonIcon slot="start" icon={save} /> Save</IonButton>
</IonFooter>
</IonContent>
</IonPopover>
);
};
export default ModalInput;
It tooks the props to open/close the modal, a title, one props to get the child value and onSave that is a callback from app.
Then i wrote a more higher component with the implementation of the children.
All the props goes to the ModalInput wrapper.
interface InputProps {
onSave: Function,
show: boolean,
setShow: Function,
defaultValue: number
}
const WeightInput: React.FC<InputProps> = ({show, setShow, defaultValue, onSave}) => {
const [value, setValue] = useState<number>(defaultValue);
return (
<ModalInput show={show} setShow={setShow} title="Starting weight" value={value} onSave={onSave}>
<IonItem>
<IonInput value={value} onIonChange={e => setValue(parseFloat(e.detail.value!))}></IonInput>
</IonItem>
</ModalInput>
);
}
export default WeightInput;
And finally how to use it :
<WeightInput show={showWeight} setShow={setShowWeight} defaultValue={weight} onSave={(w:number) => setWeight(w)}/>
So it works like that :
on input change, the input call setValue for changing the state
on state change will change also the props for the modal component
on OK click the modal component will trigger the onSave props, that come directly from the app
So the app should care only about value and the state of the modal.
The modal component should care only about his value and open state and callback on ok
The higher component contain the input logic and pass pther props to modal component.
Maybe this is not the best way, but is the best i could develop with my limited knowledge

Using a forwardRef component with children in TypeScript

Using #types/react 16.8.2 and TypeScript 3.3.1.
I lifted this forward refs example straight from the React documentation and added a couple type parameters:
const FancyButton = React.forwardRef<HTMLButtonElement>((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// You can now get a ref directly to the DOM button:
const ref = React.createRef<HTMLButtonElement>();
<FancyButton ref={ref}>Click me!</FancyButton>;
I get the following error in the last line under FancyButton:
Type '{ children: string; ref: RefObject<HTMLButtonElement>; }' is not
assignable to type 'IntrinsicAttributes & RefAttributes<HTMLButtonElement>'. Property 'children' does not
exist on type 'IntrinsicAttributes & RefAttributes<HTMLButtonElement>'.ts(2322)
It would seem that the type definition for React.forwardRef's return value is wrong, not merging in the children prop properly. If I make <FancyButton> self-closing, the error goes away. The lack of search results for this error leads me to believe I'm missing something obvious.
trevorsg, you need to pass the button properties:
import * as React from 'react'
type ButtonProps = React.HTMLProps<HTMLButtonElement>
const FancyButton = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => (
<button type="button" ref={ref} className="FancyButton">
{props.children}
</button>
))
// You can now get a ref directly to the DOM button:
const ref = React.createRef<HTMLButtonElement>()
<FancyButton ref={ref}>Click me!</FancyButton>
ADDED:
In recent versions of TS and #types/react, you can also use React.ComponentPropsWithoutRef<'button'> instead of React.HTMLProps<HTMLButtonElement>
The answers given by aMarCruz and euvs both work, but they lie to consumers a little bit. They say they accept all HTMLButtonElement props, but they ignore them instead of forwarding them to the button. If you're just trying to merge in the children prop correctly, then you might want to use React.PropsWithChildren instead:
import React from 'react';
interface FancyButtonProps {
fooBar?: string; // my custom prop
}
const FancyButton = React.forwardRef<HTMLButtonElement, React.PropsWithChildren<FancyButtonProps>>((props, ref) => (
<button type="button" ref={ref} className="fancy-button">
{props.children}
{props.fooBar}
</button>
));
FancyButton.displayName = 'FancyButton';
Or explicitly add a children prop:
interface FancyButtonProps {
children?: React.ReactNode;
fooBar?: string; // my custom prop
}
const FancyButton = React.forwardRef<HTMLButtonElement, FancyButtonProps>((props, ref) => (
<button type="button" ref={ref} className="fancy-button">
{props.children}
{props.fooBar}
</button>
));
FancyButton.displayName = 'FancyButton';
Or if you actually want to accept all the button props and forward them (let consumers choose button type="submit", for example), then you might want to use rest/spread:
import React from 'react';
interface FancyButtonProps extends React.ComponentPropsWithoutRef<'button'> {
fooBar?: string; // my custom prop
}
const FancyButton = React.forwardRef<HTMLButtonElement, FancyButtonProps>(
({ children, className = '', fooBar, ...buttonProps }, ref) => (
<button {...buttonProps} className={`fancy-button ${className}`} ref={ref}>
{children}
{fooBar}
</button>
),
);
FancyButton.displayName = 'FancyButton';
The answer given by aMarCruz works well. However, if you also need to pass custom props to the FancyButton, here is how it can be done.
interface FancyButtonProps extends React.ComponentPropsWithoutRef<'button'> {
fooBar?: string; // my custom prop
}
const FancyButton = React.forwardRef<HTMLButtonElement, FancyButtonProps>((props, ref) => (
<button type="button" ref={ref} className="FancyButton">
{props.children}
{props.fooBar}
</button>
));
/// Use later
// You can now get a ref directly to the DOM button:
const ref = React.createRef<HTMLButtonElement>()
<FancyButton ref={ref} fooBar="someValue">Click me!</FancyButton>
Just adding here for completion.
You can use ForwardRefRenderFunction<YourRefType, YourProps> on your component.
Like:
const Component: ForwardRefRenderFunction<YourRef, YourProps> = (yourProps, yourRef) => return <></>
export default fowardRef(Component)

Strange behavior of parametrised function passed as a prop in React

I am probably not seeing some obvious thing right here, but i feel pretty stuck with this one.
I have a function
public handleTest = (testNum: number) => {
console.log(testNum);
};
And the following case: I have a component to which I want to pass this function to further use it there onCLick event.
<Controls handleTest={() => this.handleTest}>
<Button label="Test1" clicked={() => this.handleTest(42)} />
</Controls>
The child component is the following:
interface IProps {
handleTest: (type: number) => void;
}
class Controls extends React.Component<IProps, {}> {
public render() {
const {
handleTest
} = this.props;
return (
<React.Fragment>
{this.props.children}
<button onClick={handleTest(42)} label="Test2" />
</React.Fragment>
);
}
}
The interesting thing about this case is that in case of Test2 button, it seems not to recognize the argument passed to it while it logs an object -
Object { dispatchConfig: {…}, _targetInst: {…}, nativeEvent: click, type: "click", target: button.sc-bwzfXH.gonlMM, currentTarget: button.sc-bwzfXH.gonlMM, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 689, … }
In case of Test1 everything works correctly. I am wondering what am I doing wrong and whether it is typescript that messes things up or some mistake of mine
This is because when you are initilizing the <Controls /> component, the function which you are sending is something like this:
() => this.handleTest
So in the button Test2 you are executing nothing because last function just return a function
So if you want to solve this:
<Controls handleTest={(number) => this.handleTest(number)}>
<Button label="Test1" clicked={() => this.handleTest(42)} />
</Controls>
Also if you do it in this way when <Controls /> is being rendered, your function is gonna be executed and not when the user click on it. To solve this you need to change it in this way:
<React.Fragment>
{this.props.children}
<button onClick={()=>handleTest(42)} label="Test2" />
</React.Fragment>
To optimize just send the function. This is function injection
<Controls handleTest={this.handleTest}>
<Button label="Test1" clicked={() => this.handleTest(42)} />
</Controls>
You're passing a function call to the handler, this would only work if you had curried the original function instead.
<button onClick={handleTest(42)} label="Test2" /> instead of
<button onClick={() => handleTest(42)} label="Test2" />
<button onClick={handleTest(42)} label="Test2" /> would work if handleTest looked like this:
public handleTest = (testNum: number) => () => {
console.log(testNum);
};
EDIT: I would actually recommend utilizing handlers the second way, when you need to infuse an outside parameter into the handler function. By declaring a handler through
{() => doSomething()}, every time you render the component that handler function will be initialized again.
Regardless, that's just a small optimization in most cases.

ReactJS typescript error Parameter props implicitly has 'any' type

Following, and adapting the tutorial here, I've hit a snag when trying to define a function to render some HTML.
function Toolbar(props) {
return (
<div>
<button onClick={() => props.onClick()}>Refresh</button>
</div>
);
}
This errors in Typescript, because props is not defined. I understand why I'm getting the error, but what I'm missing is what type should props be?
I believe I should then be able to use this inside another component like this:
function App() {
return (
<div>
<Toolbar onClick={() => alert('hello world')}>Test</Toolbar>
</div>
);
}
And the props is just whatever I specify. This would seem to be borne out here: although that link doesn't mention typescript. So, my question is: how does this feature (that of passing properties into a function or class) work with typescript when, by definition, you don't know what you're passing through?
You should define an interface for the props.
interface Props {
onClick: () => void;
}
function Toolbar(props: Props) {
return (
<div>
<button onClick={() => props.onClick()}>Refresh</button>
</div>
);
}
I also like to define my functional components as an arrow function. And you can use the FC (Functional Component) type definition with the Props generic type. This way you can deconstruct your properties right away.
const Toolbar: React.FC<Props> = ({onClick}) => {
return (
<div>
<button onClick={() => onClick()}>Refresh</button>
</div>
);
}

Resources