How do I make a route change in React using Typescript? - reactjs

I am coding along the ZTM course, making a face recognition app, but instead of Javascript, I am using Typescript to build my app, avoiding using any type. I am new to the language and don't fully grasp its nuances, so please advise me on how to resolve the issue below.
The problem is simple. I want the Sign Out page to appear when we click the corresponding button. However, I can't make this task possible like it's made in Javascript.
Let me show you what my code looks like to make it simpler to understand the problem. Firstly, there is my main App component, including its interface and parts that are important to the issue:
interface IAppState {
input: string,
imageUrl: string,
box: Object,
route: string
}
class App extends Component<{title: string}, IAppState> {
constructor(props: {title: string}) {
super(props);
this.state = {
input: '',
imageUrl: '',
box: {},
route: 'signin'
}
}
Here is the part of the main App component that makes the state change and routes us to the main page, including the render() method, where you can see my property onRouteChange:
onRouteChange = ({route}) => {
this.setState({route: route});
}
render() {
return (
<div className="App">
<ParticlesBg type="cobweb" bg={true} />
<Navigation onRouteChange={this.onRouteChange} />
{ this.state.route === 'signin'
? <SignIn onRouteChange={this.onRouteChange}/>
: <>
<Logo />
<Rank />
<ImageLinkForm title='image link form'
onInputChange={this.onInputChange}
onButtonSubmit={this.onButtonSubmit}
/>
<FaceRecognition imageUrl={this.state.imageUrl}/>
</>
}
</div>
);
}
In my Navigation component, there is the Sign Out button, which should route us back to the Sign In page:
interface INavigationProps {
onRouteChange: MouseEventHandler<HTMLInputElement>;
}
const Navigation: FC<INavigationProps> = ({ onRouteChange }) => {
return (
<nav style={{ display: 'flex', justifyContent: 'flex-end' }}>
<p onClick={() => onRouteChange('signin')} className='f3 link dim black underline pa3 pointer'>Sign Out</p>
</nav>
);
}
With a given code the Sign Out button doesn't work. Errors appear.
The first one that's obvious to me is regarding the route element: Binding element 'route' implicitly has an 'any' type.
And the second one that I don't understand how to fix:
Type '({ route }: { route: any; }) => void' is not assignable to type 'MouseEventHandler<HTMLInputElement>'.
Types of parameters '__0' and 'event' are incompatible.
Property 'route' is missing in type 'MouseEvent<HTMLInputElement, MouseEvent>' but required in type '{ route: any; }'.
The last error is in my Navigation component: Argument of type 'string' is not assignable to parameter of type 'MouseEvent<HTMLInputElement, MouseEvent>'.
I was playing with interfaces and types to resolve this, but I couldn't find the solution. How do I make the Sign Out button route us back to the Sign In page?

I have done more research and here's the final error-free code below. Firstly, fixed the line in interface IAppState to:
route: MouseEventHandler<HTMLInputElement> | undefined | string
Then redacted onRouteChange to look like this, stating its types accordingly:
onRouteChange = (route: MouseEventHandler<HTMLInputElement> | undefined | string) => {
return this.setState({route: route});
}
And then final changes were made to the Navigation component:
interface INavigationProps {
onRouteChange: (route: MouseEventHandler<HTMLInputElement> | undefined | string) => void;
}
const Navigation: FC<INavigationProps> = ({ onRouteChange }) => {
return (
<nav style={{ display: 'flex', justifyContent: 'flex-end' }}>
<p onClick={() => onRouteChange('signin')} className='f3 link dim black underline pa3 pointer'>Sign Out</p>
</nav>
);
}
That way I can access Sign In page from the home page, using Sing Out button.

Related

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

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

How to pass a functional component as a prop in Typescript and render it?

In the code below, how can I:
type the icon properly so React.createElement can create it?
Render the icon if not using React.createElement()?
function DefaultIcon () {
return <svg></svg>;
}
interface ExampleProps {
icon?: React.ReactElement; // JSX.IntrinsicElements also errors
}
function Example({ icon = <DefaultIcon /> }: ExampleProps) {
const Icon = icon;
return (
<div>
{React.createElement(icon)}
{/* ^Shows Error #1 */}
{React.createElement(icon as React.ReactElement<any>)}
{/* ^Shows Error #1 */}
<Icon />
{/* ^Shows Error #2 */}
</div>
)
}
Error #1
No overload matches this call.
The last overload gave the following error.
Argument of type 'ReactElement<any, string | JSXElementConstructor<any>>'
is not assignable to parameter of type 'string | FunctionComponent<any> |
ComponentClass<any, any>'.ts(2769)
Error #2
JSX element type 'Icon' does not have any construct or call signatures.ts(2604)
Could you please try this?
function DefaultIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.57574 12.5757C4.34142 12.8101 4.34142 13.1899 4.57574 13.4243C4.81005 13.6586 5.18995 13.6586 5.42426 13.4243L4.57574 12.5757ZM15.6 3C15.6 2.66863 15.3314 2.4 15 2.4L9.6 2.4C9.26863 2.4 9 2.66863 9 3C9 3.33137 9.26863 3.6 9.6 3.6H14.4V8.4C14.4 8.73137 14.6686 9 15 9C15.3314 9 15.6 8.73137 15.6 8.4L15.6 3ZM5.42426 13.4243L15.4243 3.42426L14.5757 2.57574L4.57574 12.5757L5.42426 13.4243Z"
fill="#404040"
/>
</svg>
);
}
interface ExampleProps {
// eslint-disable-next-line react/require-default-props
icon?: React.ReactNode;
}
function Example({ icon = <DefaultIcon /> }: ExampleProps) {
return <div>{icon}</div>;
}
export default Example;
Key Insight: React.createElement() only takes references to components (ie., Icon), not an instance of it. To clone an instance of a component (ie., <Icon />), use React.cloneElement()
With that in mind, here are the two methods to recreate a component:
1. Pass an Instance of the component as props
a) To render: {icon}
b) To recreate: React.cloneElement(icon)
You can also provide the typing in the React.ReactElement<> prop definition as seen in icon2 or cast the type as in icon3.
function DefaultIcon() {
return <svg></svg>;
}
interface ExampleProps {
icon?: React.ReactElement;
icon2?: React.ReactElement<SVGSVGElement>;
icon3?: React.ReactElement;
}
function Example({ icon = <DefaultIcon />, icon2, icon3 }: ExampleProps) {
return (
<div>
{icon}
{React.cloneElement(icon, { className: "block" })}
{React.isValidElement(icon2) && (
React.cloneElement(icon2, { className: "block" })
)}
{React.isValidElement(icon3) && (
React.cloneElement(icon3 as React.ReactElement<SVGSVGElement>, {
className: "block"
})
)}
</div>
);
}
2. Pass a Reference to the component as props
a) To render: const Icon = icon and return (<Icon />)
b) To recreate: React.createElement()
Note the updated prop definition and default passed in
function DefaultIcon () {
return <svg></svg>;
}
interface ExampleProps {
icon?: React.FC; // <-- new type definition
}
function Example({ icon = DefaultIcon}: ExampleProps) {
const Icon = icon; // ^ new default fallback
return (
<div>
{React.createElement(icon)}
<Icon />
</div>
);
}
The issue here is you cannot set jsx as a default value for props in typescript instead try to modify this code
const Icon = icon || DefaultIcon by using this expression we are evaluating that if the code icon is undefined DefaultIcon would be used. then remove the default value in props.

Cannot extend MUI-Material ListItemButtonProps with custom additional props... TypeScript error?

This should be an easy task. I want a custom component inherits all props from ListItemButtonProps, adding an additional state prop:
type SidebarListItemButtonProps = ListItemButtonProps & {
state: SidebarState,
};
const SidebarListItemButton = ({ state, children, ...props }: SidebarListItemButtonProps) => (
<ListItemButton {...props} sx={{
...props.sx,
...((!state.open && state.collapsed) && {
justifyContent: 'center',
}),
}}>
{children}
</ListItemButton>
);
This seems to work fine util you pass the component prop:
<SidebarListItemButton state={{ open, collapsed }} component={RouterLink} to='/'>
<ListItemText primary='Home' />
</SidebarListItemButton>
Property 'component' does not exist on type 'IntrinsicAttributes & ListItemButtonBaseProps & Omit<{ action?: Ref | undefined; ... 13 more ...; touchRippleRef?: Ref<...> | undefined; }, "classes"> & CommonProps & Omit<...> & { ...; }'.
Anyone does know why this is happening and how to solve this TypeScript issue?
My guess is that there's no problem with how you're passing the props.
I tried the same thing in a new project.
I do get the same error when I pass a component link like NextLink from next/link.
Like this:
import Link from "next/link";
<ListItemButton component={NextLink} />
However, If I pass in the Link component from mui there's no error.
import { Link, ListItemButton } from "#mui/material";
<ListItemButton component={Link} />
So I believe your error is with RouterLink being the wrong type and not the type of the prop.

Typescript React - use MouseEvents without passing it to child element

Can I use Mouse Events on whole React Element or I have to pass it to child element to get it work? After several functions components where I passed my handleEvent function I want to know if it's possible without getting a TypeScript error. My code is really simple
<Tile onHover={handleHover} name="Random name"/>
and Tile component
export const Tile: React.FC<{ name: string }> = ({ name }) => {
return (
<div className="tile-wrapper">
<h1 className="tile-header>
{name}
</h1>
</div>
)
}
What you want is to combine React.DOMAttributes<HTMLElement> with your custom props using an intersection. That will avail all the DOM events to your custom component, without having to manually pass it in.
export const Tile: React.FC<{ name: string } & React.DOMAttributes<HTMLElement>> = ({ name, ...events }) => {
return (
<div className="tile-wrapper" {...events}>
<h1 className="tile-header>
{name}
</h1>
</div>
)
}
As your Title component does not expect an onHover prop, it will just be ignored. The event you want to use is rather onMouseOver, which works on most HTML elements (according to documentation):
All HTML elements, EXCEPT: <base>, <bdo>, <br>, <head>, <html>, <iframe>, <meta>, <param>, <script>, <style>, and <title>
What you could do is the following, in the Title component:
export const Tile: React.FC<{ name: string, onHover: SomeType }> = ({ name, onHover }) => {
return (
<div className="tile-wrapper" onMouseOver={onHover}>
<h1 className="tile-header>
{name}
</h1>
</div>
)
}

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)

Resources