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

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

Related

How do I check if multiple text contents are in an element?

I have a component like this:
export const MyComponent = props => {
return (
{
props.options.map(option =>
<>
<div>
<input type="radio" id={option.id} name="group" value={option.id} />
<label htmlFor={option.id}>{option.label}</label>
</div>
<span>Some other text</span>
</>
)
}
)
}
And in my test, I want to make sure that both that all the radio buttons are rendered with the right label text and the extra text in the span are present.
import { render, screen, within } from '#testing-library/react'
describe('MyComponent', () => {
const props = {
options: [
{ id: 1, label: 'Apple' },
{ id: 2, label: 'Banana' },
{ id: 3, label: 'Cherry' },
]
}
it('Renders the component', () => {
render(<MyComponent {...props} />)
const options = screen.queryAllByRole('radio')
expect(options).toBeArrayOfSize(3)
options.forEach((option, index) => {
const { getByText } = within(option)
expect(getByText(props.options[index])).toBeInTheDocument() // Assertion fails
expect(getByText("Some other text")).toBeInTheDocument() // Also fails
})
})
})
However, I'm getting errors on the two expect assertions.
You can try the following:
import { render, screen } from "#testing-library/react"
import { MyComponent } from "./MyComponent"
describe("MyComponent", () => {
const props = {
options: [
{ id: 1, label: "Apple" },
{ id: 2, label: "Banana" },
{ id: 3, label: "Cherry" },
],
}
it("Renders the component", () => {
render(<MyComponent {...props} />)
const options = screen.queryAllByRole("radio")
expect(options).toHaveLength(3)
props.options.forEach((option) => {
const label = screen.getByLabelText(option.label)
const radioBtn = screen.getByRole("radio", { name: option.label })
// Need to use getAllByText query since the string "Some other text" is repeated... getByText will throw because of multiple matches
const [someOtherText] = screen.getAllByText("Some other text")
expect(label).toBeInTheDocument()
expect(radioBtn).toBeInTheDocument()
expect(someOtherText).toHaveTextContent(someOtherText.textContent)
})
})
})

React Card Example issue - Card is replaced instead of being appended to a list of cards in another column

I have been persistently working on this problem where the goal is to drag a card form 'Column 1' and copy that into another column say 'Column 2'.
Now when my first card is dragged and drop it into 'Column 2, the card is accordingly added to that column, but when I drag another card and drop into 'Column 2' instead of being appended it just replaces the existing card with itself.
I have been debugging the state, but the issue still persists. I haven't gotten a clue what am I doing wrong here?
Here's my code
// Card Component
function Card({ id, text, isDrag }) {
const [, drag] = useDrag(() => ({
type: "bp-card",
item: () => {
return { id, text}
},
collect: monitor => ({
isDragging: !!monitor.isDragging(),
}),
canDrag: () => isDrag
}));
return (
<div
className='card'
ref={drag}
style={{
cursor: isDrag ? 'pointer' : 'no-drop'
}}
>
{text}
</div>
)
}
// Column Component
function Column({ title, children, onCardDropped }) {
const [, drop] = useDrop(() => ({
accept: "bp-card",
drop: item => {
onCardDropped(item);
}
}));
return (
<div className="flex-item" ref={title === 'Column 2' ? drop : null}>
<p>{title}</p>
{children.length > 0 && children.map(({ id, text, isDrag }) => (
<Card
key={id}
id={id}
text={text}
isDrag={isDrag}
/>
))}
</div>
)
}
// Main App
function App() {
const [cards] = useState([
{ id: 1, text: 'Card 1', isDrag: true },
{ id: 2, text: 'Card 2', isDrag: true },
]);
const [columns, setColumns] = useState([
{
id: 1,
title: 'Column 1',
children: cards
},
{
id: 2,
title: 'Column 2',
children: []
},
]);
const onCardDropped = ({ id, text }) => {
// let card = null;
const targetColumnId = 2;
const transformedColumns = columns.map(column => {
if (column.id === targetColumnId) {
return {
...column,
children: [
...column.children,
{ id, text }
]
}
}
return column;
});
setColumns(transformedColumns);
}
return (
<DndProvider backend={HTML5Backend}>
<div className='flex-container'>
{columns.map((column) => (
<Column
key={column.id}
title={column.title}
children={column.children}
onCardDropped={onCardDropped}
/>
))}
</div>
</DndProvider>
);
}
Any help is highly appreciated. Thanks.
You need to consider the previous state using the callback of the set state method. It starts to work after changing the onCardDropped as below.
const onCardDropped = ({ id, text }) => {
// let card = null;
const targetColumnId = 2;
setColumns((prevColumns) =>
prevColumns.map((column) => {
if (column.id === targetColumnId) {
return {
...column,
children: [...column.children, { id, text }]
};
}
return column;
})
);
};
It's always a good idea to use the state from the callback method as opposed to using the state object directly which might be stale.
Working Demo

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

Converting to Typescript: Passing mapped props

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

Pass props from 'Tab' to 'Pane' (Semantic UI with React JS)

I am using Semantic UI 'Tab' module with React and passing some props from 'Tab' to each 'Pane'.
How to access the props in each 'Pane'?
const VideoGallery = () => {
const [video, setVideo] = useState([]);
return (
<div>
<Tab
panes={panes}
video={video} //passing 'video' as a prop
/>
</div>
)
}
const panes = [
{
menuItem: { key: 'allVideos'},
render: () =>
<Tab.Pane>
// Need to access 'video' prop here
</Tab.Pane>,
},
{
menuItem: { key: 'oneToOne'},
render: () =>
<Tab.Pane>
// Need to access 'video' prop here
</Tab.Pane>,
},
]
If you want use video in panes. you can update panes to a function:
const panes = (video) => {
return [
{
menuItem: { key: "allVideos" },
render: () => <Tab.Pane >// Using video in here</Tab.Pane>,
},
{
menuItem: { key: "oneToOne" },
render: () => <Tab.Pane>// Using video in here</Tab.Pane>,
},
];
};
<Tab
panes={panes(video)}
/>

Resources