Converting to Typescript: Passing mapped props - reactjs

I am struggling with converting the following React.JS script to TypeScript. Can anyone help? I am trying to make a drop down nav bar in my website.
This is my Header.tsx file:
I am getting a red squiggly line on onClick={closeMobileMenu} - Property 'onClick' does not exist on type 'IntrinsicAttributes & { items: any; }'.
<ul className="navbar-nav">
{menuItems.map((menu, index) => {
return (
<MenuItems
items={menu}
key={index}
onClick={closeMobileMenu}
/>
);
})}
</ul>
This is my Menu.tsx file
I am getting an error on
"items": Binding element 'items' implicitly has an 'any' type
"contains":Property 'contains' does not exist on type 'never'
import React, { useState, useEffect, useRef } from "react";
import {HashLink} from "react-router-hash-link";
import Dropdown from "./Dropdown";
import "./Header.css";
interface MenuItems {
items: string
key: number
onClick: (param: any) => void
}
const MenuItems = ({ items }) => {
let ref = useRef();
const [dropdown, setDropdown] = useState(false);
const onMouseEnter = () => {
window.innerWidth > 960 && setDropdown(true);
};
const onMouseLeave = () => {
window.innerWidth > 960 && setDropdown(false);
};
useEffect(() => {
const handler = (event: { target: any; }) => {
if (dropdown && ref.current && !ref.current.contains(event.target)) {
setDropdown(false);
}
};
document.addEventListener("mousedown", handler);
document.addEventListener("touchstart", handler);
return () => {
// Cleanup the event listener
document.removeEventListener("mousedown", handler);
document.removeEventListener("touchstart", handler);
};
}, [dropdown]);
return (
<li
className="nav-item"
ref={ref}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onClick={() => setDropdown(false)}
>
{items.submenu ? (
<>
<button
type="button"
aria-haspopup="menu"
aria-expanded={dropdown ? "true" : "false"}
>
<HashLink smooth to={items.path} className="nav-link">
{items.title} <i className="fas fa-chevron-down"></i>
</HashLink>
</button>
<Dropdown submenus={items.submenu} dropdown={dropdown} />
</>
) : (
<HashLink
smooth to={items.path}
className="first-level-nav-link"
>
{items.title}
</HashLink>
)}
</li>
);
};
export default MenuItems;
This is my menuItems.tsx file:
export const menuItems = [
{
title: "Home",
path: "/",
cName: "nav-link",
submenu: [
{
title: "Story",
path: "/#story",
cName: "nav-link",
},
{
title: "Map",
path: "/#map",
cName: "nav-link",
}
],
},
{
title: "Rewards",
path: "/",
cName: "nav-link",
submenu: [
{
title: "competition",
path: "competition",
cName: "nav-link",
},
{
title: "prizes",
path: "prizes",
cName: "nav-link",
}
],
},
{
title: "Downloads",
path: "downloads",
cName: "nav-link",
}
];

For error #2
TypeScript cannot actually infer how you intend to use this ref without any extra information.
const ref = useRef() // React.MutableRefObject<undefined>
However, useRef can be used as a generic to tell TypeScript how you do intend on using the ref.
const ref = useRef<HTMLLIElement>(null) // React.MutableRefObject<HTMLLIElement>
Only then will TypeScript allow you to access ref.current.contains, because it knows that the contains property exists on a HTMLLIElement node.

Correction #1: For function reference as a parameter you need to define any data type
interface MenuItems {
items: any,
key: number,
onClick: any
}
Correction #2 : At your MenuItems Component.
const MenuItems = (props: MenuItems ) => {
//access menu item
console.log(props.items.title);
}

Related

How to get the name of the speedDial rendered element in MuiV4?

I have the following code where I want to get the name of the clicked element but for some reason it is not compatible with any type of action in typescript ?
const _setModal = () => {
const element = e.currentTarget;
const name = element.name;
// the the modal acording to the name at hand
};
const actions = [
{ icon: <HelpIcon name='tutorial' />, name: 'Tutorial', onClick: _setModal },
{ icon: <AddCircleIcon />, name: 'Add Day', onClick: _setModal },
];
return (
<SpeedDial
>
{actions.map((el) => {
return <SpeedDialAction id={el.name} key={el.name} icon={el.icon} onClick={el.onClick} />;
})}
</SpeedDial>
);

Generic Parent ignores Generic children React

First, here's my "Parent Component" or "Factory", I'm not quite certain what the terminology is.
export interface ReadOnlyProps {
ignoreReadOnly?: boolean;
disabled?: boolean;
}
export const WithReadOnly = <T,>(Component: React.ComponentType<T>): React.ComponentType<T & ReadOnlyProps> => {
const ReadOnlyComponent = (props: T) => {
const { ...rest } = props as T & ReadOnlyProps;
return <Component {...(rest as T)} disabled={true} />;
};
return ReadOnlyComponent;
};
Here are the components I did for this:
const Dropdown = <T,>(props: { items: T[]; onChange: (data: T) => void }) => {
return (
<ul>
{props.items.map((item, index) => {
<li onClick={() => props.onChange(item)}>{index}</li>;
})}
</ul>
);
};
const DropdownReadOnly = WithReadOnly(Dropdown);
Then I did this two examples.
// Works
<Dropdown items={[{ name: 'Someone' }, { name: 'Someone else' }]} onChange={(item) => alert(item.name)} />;
// Doesn't work
<DropdownReadOnly items={[{ name: 'Someone' }, { name: 'Someone else' }]} onChange={(item) => alert(item.name)} />;
The first one is working, the second one is complaining that item is unknown on the onChange prop.
Anyone know what I am doing wrong here?
Thank you beforehand!

Dynamic render react child component

How can i dynamic render react child component? Now that looks like this and its works.
<CustomFieldArea>
{(ExampleCustomFields || []).map((e: {
field: string;
CustomComponent: 'Text' | 'TextArea'
}) => {
if (e?.CustomComponent === 'Text') {
return (
<CustomFieldArea.Text
name={e?.field}
/>
)
}
if (e?.CustomComponent === 'TextArea') {
return (
<CustomFieldArea.TextArea
name={e?.field}
/>
)
}
})}
</CustomFieldArea>
Here is the output I’m looking for:
<CustomFieldArea>
{(ExampleCustomFields || []).map((e: {
field: string;
CustomComponent: 'Text' | 'TextArea'
}) => {
return (
<CustomFieldArea[e?.CustomComponent]
name={e?.field}
/>
)
})}
</CustomFieldArea>
But it doesnt work. How can i using <CustomFieldArea[e?.CustomComponent] label={e?.title}> like this.
Are you want something like render props ?
<DataProvider render={data => (
<h1>Hello, {data.target}</h1>
)}/>
<Mouse children={mouse => (
<p>Current mouse position: {mouse.x}, {mouse.y}</p>
)}/>
Read more here
if render props isn't that you want then Use HOC's
const menu = [
{ title: 'Home', icon: 'HomeIcon' },
{ title: 'Notifications', icon: 'BellIcon' },
{ title: 'Profile', icon: 'UserIcon' },
]
const Icon = (props) => {
const { name } = props
let icon = null
if (name === 'HomeIcon') icon = HomeIcon
if (name === 'BellIcon') icon = BellIcon
if (name === 'UserIcon') icon = UserIcon
return React.createElement(icon, { ...props })
}
Read more here
Helpful links
First
Second

React Hook useEffect has a missing dependency. Either include it or remove the dependency array react-hooks/exhaustive-deps

If i add the dependency array "fitems" in the dependecy array like its telling me to do, then it causes infinite loop. also if i dont use the spread operator on the array then the warning doesnt show but then the state change doesnt rerender.
Sidebar.tsx
import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import axios from "axios";
import getItems from "./../services/SidebarItems";
import { sidebarInfoUrl } from "./../services/ApiLinks";
function Sidebar() {
const fItems = getItems();
const location = useLocation();
const paths = location.pathname.split("/");
const [items, setItems] = useState(fItems);
useEffect(() => {
axios.get(sidebarInfoUrl).then((response) => {
const updatedItems = [...fItems]
updatedItems.forEach((item) => {
if (item.match === "projects") item.value = response.data.projects;
else if (item.match === "contacts") item.value = response.data.contacts;
});
setItems(updatedItems);
console.log("here")
});
}, []);
return (
<div className="sidebar shadow">
{items &&
items.map((item) => (
<Link
key={item.match}
to={item.link}
className={
paths[2] === item.match ? "sidebar-item active" : "sidebar-item"
}
>
<span>
<i className={item.icon}></i> {item.title}
</span>
{item.value && <div className="pill">{item.value}</div>}
</Link>
))}
</div>
);
}
export default Sidebar;
Here is the sidebar items i am getting from getItems().
sidebarItems.ts
const items = () => {
return [
{
title: "Dashboard",
icon: "fas fa-home",
link: "/admin/dashboard",
match: "dashboard",
value: "",
},
{
title: "Main Page",
icon: "fas fa-star",
link: "/admin/main-page",
match: "main-page",
value: "",
},
{
title: "Projects",
icon: "fab fa-product-hunt",
link: "/admin/projects",
match: "projects",
value: "00",
},
{
title: "Contacts",
icon: "fas fa-envelope",
link: "/admin/contacts",
match: "contacts",
value: "00",
},
];
};
export default items;
Thank to AKX. I found my problem. I had to use useMemo Hook so that my getItem() function doesnt cause infinte loop when i add it to dependency array.
const fItems = useMemo(() => {
return getItems();
}, []);
instead of
const fItems = getItems();
Another fix is that,
If i dont send the items from SidebarItems.ts as function but as an array then it wont cause the infinte loop even if i dont use useMemo hook.

How to add right click menu to react table row, and access its properties?

I've added react-table package to my project and everything is fine, but I also wanted to have a possibility to right click on a row and perform some actions on it (cancel, pause etc). I'm using React with Typescript but I hope it doesn't add any complexity.
My initial idea was to use react-contextify, however I can't find any working examples that would combine react-table and react-contextify together.
The only "working" example I have found is this one:
React Context Menu on react table using react-contexify
I ended up not using react-contextify and it "kind of works" but I'm not totally certain about this one as I sometimes keep getting exceptions like this:
Uncaught TypeError: Cannot read property 'original' of undefined
The code I have now is this:
const columns = [
{
Header: "Name",
accessor: "name"
},
{
Header: "Age",
accessor: "age",
Cell: (props: { value: React.ReactNode }) => (
<span className="number">{props.value}</span>
)
},
{
id: "friendName", // Required because our accessor is not a string
Header: "Friend Name",
accessor: (d: { friend: { name: any } }) => d.friend.name // Custom value accessors!
},
{
Header: (props: any) => <span>Friend Age</span>, // Custom header components!
accessor: "friend.age"
}
];
return (
<div>
<ContextMenuTrigger id="menu_id">
<ReactTable
data={data}
columns={columns}
showPagination={false}
getTdProps={(
state: any,
rowInfo: any,
column: any,
instance: any
) => {
return {
onClick: (e: any, handleOriginal: any) => {
const activeItem = rowInfo.original;
console.log(activeItem);
},
onContextMenu: () => {
console.log("contextMenu", rowInfo);
this.setState({
showContextMenu: true,
rowClickedData: rowInfo.original
});
}
};
}}
/>
</ContextMenuTrigger>
{this.state.showContextMenu ? (
<MyAwesomeMenu clickedData={this.state.rowClickedData} />
) : null}
</div>
);
}
}
const MyAwesomeMenu = (props: { clickedData: any }) => (
<ContextMenu id="menu_id">
<MenuItem
data={props.clickedData}
onClick={(e, props) => onClick({ e, props })}
>
<div className="green">ContextMenu Item 1 - {props.clickedData.id}</div>
</MenuItem>
</ContextMenu>
);
const onClick = (props: {
e:
| React.TouchEvent<HTMLDivElement>
| React.MouseEvent<HTMLDivElement, MouseEvent>;
props: Object;
}) => console.log("-------------->", props);
What is the best (and simplest) way to add a context menu to react-table so I can use clicked row's props? I really like react-contextify but haven't found any examples.
Thanks
React Hooks exmaple on dev.to
Class Based Compnent example on codepen
class App extends React.Component {
constructor() {
super();
this.state = {
value: ''
};
}
render() {
return(
<div>
{
['row1', 'row2', 'row3'].map((row) => {
return (
<ContextMenu
key={row}
buttons={[
{ label: 'Editovat', onClick: (e) => alert(`Editace ${row}`) },
{ label: 'Smazat', onClick: (e) => alert(`Mažu ${row}`) }
]}
>
<div className="row">{row}</div>
</ContextMenu>
);
})
}
</div>
);
}
}
class ContextMenu extends React.Component {
static defaultProps = {
buttons: []
};
constructor() {
super();
this.state = {
open: false
};
}
componentDidMount() {
document.addEventListener('click', this.handleClickOutside);
document.addEventListener('contextmenu', this.handleRightClickOutside);
}
handleClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const context = ReactDOM.findDOMNode(this.context);
const isInRow = (!root.contains(e.target) || root.contains(e.target));
const isInContext = !context.contains(e.target);
if (isInRow && isInContext) {
this.setState({
open: false
});
}
}
handleRightClickOutside = (e) => {
if (!this.state.open) {
return;
}
const root = ReactDOM.findDOMNode(this.div);
const isInRow = !root.contains(e.target);
if (isInRow) {
this.setState({
open: false
});
}
}
handleRightClick = (e) => {
e.preventDefault();
console.log(e.nativeEvent, window.scrollY);
this.setState({
open: true,
top: window.scrollY + e.nativeEvent.clientY,
left: e.nativeEvent.clientX,
});
}
render() {
return (
<div
onContextMenu={this.handleRightClick}
ref={(node) => this.div = node}
>
{this.props.children}
{
!this.state.open
? null
: <div
className="context"
ref={(div) => this.context = div}
style={{ top: this.state.top, left: this.state.left }}
>
<ul>
{
// button - name, onClick, label
this.props.buttons.length > 0 &&
this.props.buttons.map((button) => {
return <li key={button.label}>
<a href="#" onClick={button.onClick}>
{button.label}
</a>
</li>
})
}
</ul>
</div>
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));

Resources