ReactJS typescript error Parameter props implicitly has 'any' type - reactjs

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>
);
}

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

Render an element based on a given prop taking on appropriate element attributes

I would like to create a component that renders a specific HTML element based on properties given to it. In this case, I'd like to render a div if the component's isDiv property is true, and a button if it's false.
I also want to be able to provide my component with any of the element's attributes, which will be passed on down to the element itself.
Without TypeScript, I might write the component like so:
const Button = ({ isDiv, elementProps, children }) => {
return isDiv ? (
<div {...elementProps} className="button">{children}</div>
) : (
<button {...elementProps} className="button">{children}</button>
);
};
To be used, for example, like:
<Button type="submit" />
{/* <button class="button" type="button">...</button> */}
<Button isDiv />
{/* <div class="button">...</div> */}
My attempt now, using TypeScript (and a technique I've read refered to as a "Discriminated Union") is as follows:
type DivProps = {
isDiv: true;
elementProps: React.HTMLAttributes<HTMLDivElement>
};
type ButtonProps = {
isDiv: false;
elementProps: React.ButtonHTMLAttributes<HTMLButtonElement>;
};
const Button: React.FC<DivProps | ButtonProps> = ({
isDiv,
elementProps,
children,
}) => {
return isDiv ? (
<div {...elementProps} className="button">{children}</div>
) : (
<button {...elementProps} className="button">{children}</button>
);
};
Where I get errors due to HTMLButtonElement and HTMLDivElement not being compatible, ultimately:
Property 'align' is missing in type 'HTMLButtonElement' but required in type 'HTMLDivElement'
How can I correctly implement this component using TypeScript?
you need to help TS know about the relationship between isDiv and elementProps so it could narrow down the discriminated union.
this works:
const Button: React.FC<DivProps | ButtonProps> = ({
children,
...props,
}) => {
return props.isDiv ? (
<div {...props.elementProps} className="button">{children}</div>
) : (
<button {...props.elementProps} className="button">{children}</button>
);
};

Rendering different html in reusable components

So I'm trying to make a reuseable component, which takes in an array and a variable, then i want to map through that array and return html.
But when i reuse this component, the html wont necesarly be the same each time i use it. For example:
Home Component
<div className='home'>
<Mappedarray array={list} pattern={pattern}/>
</div>
Account Component
<div className='account'>
<Mappedarray array={users} pattern={pattern}/>
</div>
function Mappedarray(props) {
const {array, pattern} = props
const arrayrow = array?.map(el=>{
return VARIABLE_HTML
})
}
So this is the basic set up, now for the VARIABLE_HTML, I want to return different html elements, for example, in the Home Component I want to return
<Link to={el.link}>
<p>{el.title}</p>
<i className={el.src}'></i>
</Link>
But for the User Component, I want to return
<div className='usercont'>
<img src={el.src}/>
<p>{el.title}</p>
</div>
I've been using a solution like passing a boolean variable to the component to determine what html should be used, but the component will get very messy and doesn't seem like a good solution.
For example, in the Home Component I would pass down:
<Mappedarray array={list} pattern={pattern} home={true} />
Then in the Mappedarray Component I would do
function Mappedarray(props) {
const {array, pattern, home, user} = props
const arrayrow = array?.map(el=>{
return <>
{
home?
<Link to={el.link}>
<p>{el.title}</p>
<i className={el.src}'></i>
</Link>
:user?
<div className='usercont'>
<img src={el.src}/>
<p>{el.title}</p>
</div>
:ANOTHER_VAR?
...
}
</>
})
}
Therefore, by doing it like this it would get very messy and disorganized, looking to a more dynamic way of doing this
Well since you are mapping trough same array and want different result maybe reusable component is not for this the best case. But if you want to have this united into one component like this you can just add a flag isLink to your reusable component and you are done:
function Mappedarray(props) {
const { array, pattern, isLink } = props;
const arrayrow = array?.map((el) => {
return isLink ? (
<Link to={el.link}>
<p>{el.title}</p>
<i className={el.src}></i>
</Link>
) : (
<div className="usercont">
<img src={el.src} />
<p>{el.title}</p>
</div>
);
});
}
Than this would be usage of that component in two cases:
Home Component
<div className='home'>
<Mappedarray array={list} pattern={pattern} isLink={true}/>
</div>
Account Component
<div className='account'>
<Mappedarray array={users} pattern={pattern} isLink={false}/>
</div>
NOTE
If you just put isLink with no ={true} it will be implicitly true. But for this example i added it explicitly
You can accept a render function as a prop. But if you are doing that then your Mappedarray isn't doing much of anything.
function Mappedarray({ array = [], render }) {
return (
<div>{array.map(render)}</div>
);
}
You can define render components for various types. Make sure that you are setting a key property since we will use this as a callback for .map.
const MyLink = ({ link, title, src }) => (
<Link to={link} key={link}>
<p>{title}</p>
<i className={src}></i>
</Link>
)
You would call Mappedarray by passing the function component as the render prop. Your array prop would be an array of props for that component.
const Test = () => {
return (
<Mappedarray
array={[{ link: "/", title: "home", src: "/images/home.jpg" }]}
render={MyLink}
/>
)
}
With Typescript Types
You could also tweak this slightly to pass the array index as a prop to the render component instead of passing it as a second argument. This version allows for both class components and function components to be passed to render.
function Mappedarray<T>({ array = [], render: Render }: Props<T>) {
return (
<div>{array.map((props, index) => (
<Render {...props} index={index} />
))}</div>
);
}
With Typescript Types

Using Unstated with TypeScript and React, how to get a typed container instance inside <Subscribe> children?

In my React app, I'm using Unstated to manage shared state, but I'm running into a problem using this with TypeScript: the <Subscribe> component passes me an instance of my state that's typed as Container<any>. This means it will need to be cast to my own type, e.g. Container<MyState>, before I can safely use it.
If instead I wanted Unstated to pass me an already-typed instance of my container, how should I wrap and/or fork the Unstated source and/or its typings file so that when I get the Container instance, it's typed as Container<MyState>?
BTW, the particular reason why I want to get a passed-in typed container is so that I can use destructuring of complex state without having to switch to using the block form of fat-arrow functions which is much more verbose.
Here's a simplified example of the code that I'd like to be able to write:
function Counter() {
return (
<Subscribe to={[CounterContainer]}>
{({increment, decrement, state}) => (
<div>
<button onClick={() => decrement()}>-</button>
<span>{state.count}</span>
<button onClick={() => increment()}>+</button>
</div>
)}
</Subscribe>
);
}
And here's the current way I'm writing it, inside a simplified Unstated example using TypeScript:
import React from 'react';
import { render } from 'react-dom';
import { Provider, Subscribe, Container } from 'unstated';
interface CounterState {
count: number
};
class CounterContainer extends Container<CounterState> {
public state = {
count: 0
};
public increment() {
this.setState({ count: this.state.count + 1 });
}
public decrement() {
this.setState({ count: this.state.count - 1 });
}
}
function Counter() {
return (
<Subscribe to={[CounterContainer]}>
{(counter: CounterContainer) => {
const {increment, decrement, state} = counter;
return (
<div>
<button onClick={() => increment()}>-</button>
<span>{state.count}</span>
<button onClick={() => decrement()}>+</button>
</div>
)
}}
</Subscribe>
);
}
render(
<Provider>
<Counter />
</Provider>,
document.getElementById('root')
);
So looking at the PR that originally added the typescript definition, it is noted that they couldn't find a way to provide the typing automatically that you are looking for, since something like $TupleMap doesn't exist in TS like it does in Flow, and you instead have to manually provide the typing.
If your primary motivation is avoiding the extra {} with the arrow function, you can manually provide the typing and still do the same destructuring. So from your example:
function Counter() {
return (
<Subscribe to={[CounterContainer]}>
{({increment, decrement, state}: CounterContainer) => (
<div>
<button onClick={() => increment()}>-</button>
<span>{state.count}</span>
<button onClick={() => decrement()}>+</button>
</div>
)
}
</Subscribe>
);
}
works as well. Maybe there is a way to type this automatically in Typescript, but it's beyond me.

When I log a prop to the console, I don't get the props but .0.0.2.0

I have a CoreLayout smart component, and a Banner dumb component as a child, written as follow (I only kept the relevant slices of the code):
class CoreLayout extends React.Component {
constructor(props) {
super(props)
}
closeBanner(e, type) {
e.stopPropagation()
console.log(type)
}
render() {
return (
<div>
<Banner type='bannerA'/>
<Banner type='bannerB'/>
</div>
)
}
}
And a simple Banner component:
const Banner = ({type, closeBanner}) => {
return (
<div>
<span onClick={ (e, type) => closeBanner(e, type) }>close</span>
<span>A message</span>
</div>
)
}
My problem is that instead of logging 'bannerA' or 'bannerB' to the console when I click on the closing , I see things like 0.0.2.0.0 or 0.0.3.0.0 depending on the banner I click.
However, if I log the type in the Banner component, it is able to log the right type to the console.
Has anyone an idea about this behavior ? Let me know if you need more details on this.
Issue is here:
onClick = {(e, type) => close(e,type)}
onClick = {(e, type) => ... } here e and type, these two parameters will get passed by a onClick function, here type will not be the same as the type you are getting from parent component, type will be just the parameter name in onClick function.
In the first line you are destructuring the props object:
const Banner = ({type, closeBanner}) => {
This type will have the value, you passed from parent component.
Write it like this:
const Banner = ({type, closeBanner}) => {
return (
<div>
<span onClick={ (e) => closeBanner(e, type) }>close</span>
<span>A message</span>
</div>
)
}
Also you need to pass closeBanner function from parent:
<Banner type='bannerA' closeBanner={this.closeBanner}/>

Resources