React-select, open sub-menu when hover over an option - reactjs

I'm trying to build a submenu inside a main menu with React-select, it should be something like this:
When hovering over an option from the main menu, it triggers the submenu to open at the side.
Is there a way to do this using react-select? I couldn't find any example or documentation on this, is there a function like ```optionOnMouseover`` for this? Thank you in advance!
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
...
<Select
value={...}
onChange={...}
options={options}
/>```

This is on click, but if you need on hover,
just modify it
import React, { useState } from "react";
import ReactDOM from "react-dom";
import Select, { components } from "react-select"
const CustomOption = (props) => {
const [submenu, setSubmenu] = useState(false)
const [height, setHeight] = useState(0)
const handleOption = (e) => {
if(submenu) {
setSubmenu(false)
} else {
setHeight(e.clientY)
setSubmenu(true)
}
}
const handleSubOption = (e) => {
console.log('clicked')
}
const { data } = props;
return data.custom ? (
<>
<div onClick={handleOption} className="customs">
{data.label} <span className="caret"/>
{
submenu && (
<div className="dropdown-submenu">
<div className="drops" onClick={handleSubOption}>
Test dropdown 1
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 2
</div>
<div className="drops" onClick={handleSubOption}>
Test dropdown 3
</div>
</div>
)
}
</div>
<style jsx>{`
.customs {
height: 36px;
padding: 8px;
position: relative;
}
.drops {
height: 36px;
padding: 8px;
}
.customs:hover, .drops:hover {
background-color: #17cf76;
}
.dropdown-submenu {
position: fixed;
top: ${height - 10}px;
left: 410px;
min-height: 36px;
overflow: auto;
border: 1px solid hsl(0,0%,80%);
border-radius: 4px;
color: #212529;
}
`}</style>
</>
) : (
<components.Option {...props} />
);
};
const options = [
{ custom: true, label: "I'm a custom link", value: "cust" }
];
function App() {
return (
<>
<Select classNamePrefix="category-select" className="w-30" components={{ Option: CustomOption }} options={options} />
<style jsx global>{`
* {
font-family: sans-serif;
text-align: center;
}
.w-30 {
width: 30% !important;
}
`}</style>
</>
)
}
export default App

Related

Test accordion isCollapsed with overflow hidden and maxHeight

What I Need
I need a way to test if an accordion isCollapsed or not. I tried seeing if there was a way to grab if the maxHeight hasChanged, but from what I have read, it doesn't include measurements within the dom objects for the tests
The Problem
Writing the following test:
import React from 'react';
import { render, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
import Accordion from './Accordion';
import AccordionItem from './AccordionItem';
const accordionTitle = 'Title: Hello World';
const accordionContent = 'Content: Hello World';
function TestContainer() {
return (
<Accordion>
<AccordionItem title={accordionTitle}>
<p>{accordionContent}</p>
</AccordionItem>
</Accordion>
);
}
describe('Accordion', () => {
const userViewing = userEvent.setup();
it.only('expands content on item control click', async () => {
render(<TestContainer />);
expect(await screen.findByText(accordionContent)).not.toBeInTheDocument();
const accordionItem = screen.getByRole('button', { name: accordionTitle });
userViewing.click(accordionItem);
expect(await screen.findByText(accordionContent)).toBeInTheDocument();
});
});
Results in the following Error:
The Reason
I think this is because the content component still exists in the DOM, but is only being hidden by the overflow: hidden; and maxHeight: 0 or heightOfContent(for animation purposes).
The Component Code
Accordion.tsx:
import React, { ReactNode } from 'react';
function Accordion({ children }: PROPS): JSX.Element {
return <div>{children}</div>;
}
interface PROPS {
children: ReactNode;
}
export default Accordion;
AccordionItem.tsx
import React, { ReactNode, useState } from 'react';
import AccordionControlClick from './AccordionControlClick';
import AccordionContent from './AccordionContent';
import { useStyles } from './Styles';
function AccordionItem({ title, children }: PROPS): JSX.Element {
const classes = useStyles();
const [isCollapsed, setIsCollapsed] = useState(true);
return (
<div className={isCollapsed ? classes.accordionItemClosed : classes.accordionItemOpen}>
<AccordionControlClick
title={title}
isCollapsed={isCollapsed}
toggleIsCollapsed={setIsCollapsed}
/>
{children !== null && (
<AccordionContent isCollapsed={isCollapsed}>{children}</AccordionContent>
)}
</div>
);
}
interface PROPS {
title: string;
children?: ReactNode;
}
export default AccordionItem;
AccordionControlClick.tsx
import React from 'react';
import { useStyles } from './Styles';
function AccordionControlClick({ title, isCollapsed, toggleIsCollapsed }: PROPS): JSX.Element {
const classes = useStyles();
return (
<button
className={classes.accordionControlClick}
type="button"
onClick={() => toggleIsCollapsed(!isCollapsed)}
>
<h2>{title}</h2>
<span className={isCollapsed ? classes.iconChevronWrapper : classes.iconChevronWrapperRotate}>
<i class="fa-solid fa-chevron-down" />
</span>
</button>
);
}
interface PROPS {
title: string;
isCollapsed: boolean;
toggleIsCollapsed: (isOpen: boolean) => void;
}
export default AccordionControlClick;
AccordionContent.tsx
import React, { ReactNode, useRef, useLayoutEffect } from 'react';
import { useStyles } from './Styles';
function AccordionContent({ isCollapsed, children }: PROPS): JSX.Element {
// variables
const componentDomRef = useRef<any>(null);
const componentHeight = useRef(0);
const classes = useStyles();
// setup
useLayoutEffect(() => {
componentHeight.current = componentDomRef.current ? componentDomRef.current.scrollHeight : 0;
}, []);
// render
return (
<div
ref={componentDomRef}
className={classes.accordionContent}
style={isCollapsed ? { maxHeight: '0px' } : { maxHeight: `${componentHeight.current}px` }}
>
{children}
</div>
);
}
interface PROPS {
isCollapsed: boolean;
children: ReactNode;
}
export default AccordionContent;
Styles.ts
import { createUseStyles } from 'react-jss';
import { cssColors, cssSpacing } from '../../utils';
const accordionBoxShadow = '2px 3px 8px 1px rgba(0, 0, 0, 0.2)';
const accordionBoxShadowTransition = 'box-shadow 0.3s ease-in-out 0s;';
export const useStyles = createUseStyles({
accordionContent: {
boxSizing: 'border-box',
width: '100%',
padding: `0 ${cssSpacing.m}`,
overflow: 'hidden',
transition: 'max-height 0.3s ease-in-out'
},
accordionControlClick: {
boxSizing: 'border-box',
display: 'flex',
width: '100%',
alignItems: 'center',
padding: `9px ${cssSpacing.m}`,
border: 'none',
borderRadius: '8px',
outline: 'none',
backgroundColor: `${cssColors.backgroundLevel2}`,
cursor: 'pointer'
},
accordionItemClosed: {
boxSizing: 'border-box',
width: '100%',
marginBottom: cssSpacing.l,
border: `2px solid ${cssColors.accordionTitleBorder}`,
borderRadius: '8px',
boxShadow: 'none',
transition: 'none',
'&:hover': {
border: `2px solid ${cssColors.accordionTitleBorder}`,
boxShadow: accordionBoxShadow,
transition: accordionBoxShadowTransition
}
},
accordionItemOpen: {
boxSizing: 'border-box',
width: '100%',
marginBottom: cssSpacing.l,
border: `2px solid ${cssColors.accordionTitleBorder}`,
borderRadius: '8px',
boxShadow: accordionBoxShadow,
transition: accordionBoxShadowTransition,
'&:hover': {
border: `2px solid ${cssColors.accordionTitleBorder}`,
boxShadow: accordionBoxShadow,
transition: accordionBoxShadowTransition
}
},
iconChevronWrapper: {
marginLeft: 'auto',
transform: 'none',
transition: 'transform 300ms ease'
},
iconChevronWrapperRotate: {
marginLeft: 'auto',
transform: 'rotate(180deg)',
transition: 'transform 300ms ease'
}
});
In the AccordionContent.tsx file, I added in the following visibility
style={ isCollapsed ? { maxHeight: '0px', visibility: 'hidden' } : { maxHeight: `${componentHeightRef.current}px`, visibility: 'visible' } }
In the test file I make the following changes to test if the content is ToBeVisible vs toBeInTheDocument:
it.only('expands content on item control click', async () => {
render(<TestContainerOneItem />);
const accordionContentContainer = (await screen.findByText(accordionContent)).parentElement;
await waitFor(() => expect(accordionContentContainer).not.toBeVisible());
const accordionControlButton = screen.getByRole('button', { name: accordionTitle });
await userViewing.click(accordionControlButton);
await waitFor(() => expect(accordionContentContainer).toBeVisible());
});

Issue in removing Grandchild in a recursive component

What I have been trying to achieve?
Create a nested context menu that is driven by a config.
Where am I stuck:
Sub menus are rendering correctly, but if there is more than 2 level, the change in root level only affects its sub-menu and not its entire tree
Here is the sandbox link for you to check.
Steps to reproduce:
On load, a menu is displayed (say menu)
Click on File, it will open its sub menu (say sub-menu1).
Click on Open in the sub-menu1, again another sub menu (say sub-menu2) is open.
Now when you click on Edit in menu, sub-menu1 disappears but not sub-menu2
I think, I know the problem. sub-menu2 is not refreshing because props or state is not changed. To hide it, we will need to trickle down some prop but can't think of elegant way to do it without state management system.
You'll have a better time if the ContextMenu component is responsible for state management and recursion is flattened into iteration.
function ContextItem({ item, onClick }) {
return (
<div className="menu-item" onClick={() => onClick(item)}>
<p className="menu-title">{item.title}</p>
{item.children && item.children.length > 0 ? <i className="right-icon">{">"}</i> : null}
</div>
);
}
function MenuList({ list, onClick }) {
return (
<div className="menu-container">
{list.map((listItem) => (
<ContextItem item={listItem} key={listItem.title} onClick={onClick} />
))}
</div>
);
}
const ContextMenu = ({ list }) => {
const [openSubmenus, setOpenSubmenus] = React.useState([]);
const clickHandler = React.useCallback((item, level) => {
if (item.children && item.children.length) {
setOpenSubmenus((oldItems) => {
return [...oldItems.slice(0, level), item.children];
});
} else {
setOpenSubmenus([]); // item selected, clear submenus
alert(item.title);
}
}, []);
const menus = [list, ...openSubmenus];
return (
<div className="menu">
{menus.map((menu, level) => (
<MenuList
key={level}
list={menu}
level={level}
onClick={(item) => clickHandler(item, level)}
/>
))}
</div>
);
};
const menuList = [{
title: "File",
children: [{
title: "Close",
children: [],
action: "fileClose",
}, {
title: "Open",
children: [{
title: "A:\\",
children: [],
action: "",
}, {
title: "C:\\",
children: [],
action: "",
}, {
title: "\\",
children: [],
action: "",
}],
action: "",
}, {
title: "Find",
children: [{
title: "here",
children: [],
}, {
title: "elsewhere",
children: [],
}],
action: "",
}, {
title: "Backup",
children: [],
action: "backup",
}],
action: "",
}, {
title: "Edit",
children: [],
action: "edit",
}];
function App() {
return <ContextMenu list={menuList} />;
}
ReactDOM.render(<App />, document.getElementById("root"));
.menu {
display: flex;
flex-direction: row;
}
.menu-container {
display: flex;
flex-direction: column;
background-color: #eee;
border: 1px solid gray;
border-radius: 4px;
}
.menu-item {
display: flex;
flex-direction: row;
margin: 2px;
max-width: 200px;
line-height: 30px;
padding: 5px 10px;
}
.menu-title {
min-width: 80px;
height: 30px;
flex-grow: 1;
margin: 0;
vertical-align: middle;
}
.menu-title.active {
background-color: blue;
color: white;
}
.right-icon {
width: 25px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.0.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You could use the label as the key to reset the ContextMenu state when selectedItem change (assuming the label is unique at a given depth, but it seems reasonable, otherwise you could add unique ids).
export const ContextMenu = ({list}) => {
const [selectedItem, setSelectedItem] = useState();
return (
<div className="menu">
<div className="menu-container">
{list.map((listItem) => {
return (
<ContextItem
item={listItem}
key={listItem.title}
onClick={setSelectedItem}
/>
);
})}
</div>
{selectedItem?.children.length > 0 && <ContextMenu
key={selectedItem.title}
list={selectedItem.children}/>}
</div>
);
};

How do I make sidebar automatically disappear once a menu item clicked

I implemented a some Sidebar code and would like the sidebar to automatically close once an item is clicked. Currently have to click the 'X' to hide sidebar. Below is the existing code for the Sidebar and Sidebar sub-menu.
Probably a one line fix if you know what you are doing, but I am new to React, so any help would be appreciated.
...
import React, { useState } from 'react';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import * as FaIcons from 'react-icons/fa';
import * as AiIcons from 'react-icons/ai';
import { SidebarData } from './SidebarData';
import SubMenu from './SubMenu';
import { IconContext } from 'react-icons/lib';
const Nav = styled.div`
// background: #15171c;
background: black;
height: 60px;
display: flex;
justify-content: flex-start;
align-items: center;
`;
const NavIcon = styled(Link)`
margin-left: 2rem;
font-size: 2rem;
height: 60px;
display: flex;
justify-content: flex-start;
align-items: center;
`;
// background: #15171c;
const SidebarNav = styled.nav`
background: #15171c;
width: 250px;
height: 150vh;
display: flex;
justify-content: center;
position: fixed;
top: 0;
left: ${({ sidebar }) => (sidebar ? '0' : '-100%')};
transition: 350ms;
z-index: 10;
`;
const SidebarWrap = styled.div`
width: 100%;
`;
const Sidebar = () => {
const [sidebar, setSidebar] = useState(false);
const showSidebar = () => setSidebar(!sidebar);
return (
<>
<IconContext.Provider value={{ color: '#fff' }}>
<Nav>
<NavIcon to='#'>
<FaIcons.FaBars onClick={showSidebar} />
</NavIcon>
</Nav>
<SidebarNav sidebar={sidebar}>
<SidebarWrap>
<NavIcon to='#'>
<AiIcons.AiOutlineClose onClick={showSidebar} />
</NavIcon>
{SidebarData.map((item, index) => {
return <SubMenu item={item} key={index} />;
})}
</SidebarWrap>
</SidebarNav>
</IconContext.Provider>
</>
);
};
export default Sidebar;
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
const SidebarLink = styled(Link)`
display: flex;
color: #e1e9fc;
justify-content: space-between;
align-items: center;
padding: 20px;
list-style: none;
height: 40px;
text-decoration: none;
font-size: 18px;
//#c6c8cf
//#252831
&:hover {
background: #c6c8cf;
border-left: 4px solid #632ce4;
cursor: pointer;
}
`;
const SidebarLabel = styled.span`
margin-left: 16px;
`;
const DropdownLink = styled(Link)`
background: #414757;
height: 40px;
padding-left: 3rem;
display: flex;
align-items: center;
text-decoration: none;
color: #f5f5f5;
font-size: 18px;
// background: #632ce4;
&:hover {
background: #d9dbd5;
cursor: pointer;
}
`;
const SubMenu = ({ item }) => {
const [subnav, setSubnav] = useState(false);
const showSubnav = () => setSubnav(!subnav);
return (
<>
<SidebarLink to={item.path} onClick={item.subNav && showSubnav}>
<div>
{item.icon}
<SidebarLabel>{item.title}</SidebarLabel>
</div>
<div>
{item.subNav && subnav
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</SidebarLink>
{subnav &&
item.subNav.map((item, index) => {
return (
<DropdownLink to={item.path} key={index}>
{item.icon}
<SidebarLabel>{item.title}</SidebarLabel>
</DropdownLink>
);
})}
</>
);
};
export default SubMenu;
import React from 'react';
// import * as FaIcons from 'react-icons/fa';
import * as AiIcons from 'react-icons/ai';
import * as IoIcons from 'react-icons/io';
import * as RiIcons from 'react-icons/ri';
import * as FaIcons from 'react-icons/fa';
export const SidebarData = [
{
title: 'Login',
path: '/login',
icon: <AiIcons.AiOutlineLogin />,
},
{
title: 'Site Master',
path: '/list-sitemaster',
icon: <FaIcons.FaBroadcastTower />,
},
{
title: 'Site Report Cards',
path: '/sitereportcardmaster',
icon: <IoIcons.IoIosPaper />,
},
{
title: 'Report Card Master',
path: '/reportcardmaster',
icon: <IoIcons.IoIosPaper />
},
{
title: 'Categories',
path: '/categories',
icon: <IoIcons.IoIosClipboard />,
iconClosed: <RiIcons.RiArrowDownSFill />,
iconOpened: <RiIcons.RiArrowUpSFill />,
subNav: [
{
title: 'Asset Type',
path: '/assettype',
icon: <IoIcons.IoIosPaper />,
cName: 'sub-nav'
},
{
title: 'Asset Sub Type',
path: '/assetsubtype',
icon: <IoIcons.IoIosPaper />,
cName: 'sub-nav'
},
{
title: 'Document Type',
path: '/documenttype',
icon: <IoIcons.IoIosPaper />,
cName: 'sub-nav'
},
{
title: 'Document Sub Type',
path: '/documentsubtype',
icon: <IoIcons.IoIosPaper />,
cName: 'sub-nav'
},
{
title: 'Report Card Category',
path: '/reportcarddetailcategory',
icon: <IoIcons.IoIosPaper />,
cName: 'sub-nav'
},
{
title: 'Contact Detail Type',
path: '/contactdetailtype',
icon: <IoIcons.IoIosPaper />,
cName: 'sub-nav'
},
{
title: 'Lighting Type',
path: '/lightingtype',
icon: <IoIcons.IoIosPaper />,
cName: 'sub-nav'
}
]
},
{
title: 'Company Master',
path: '/cpymaster-toplevel',
icon: <AiIcons.AiFillHome />,
iconClosed: <RiIcons.RiArrowDownSFill />,
iconOpened: <RiIcons.RiArrowUpSFill />,
subNav: [
{
title: 'CompanyMaster',
path: '/cpymaster',
icon: <AiIcons.AiFillHome />,
cName: 'sub-nav'
},
{
title: 'Users',
path: '/users',
icon: <IoIcons.IoIosPerson />,
cName: 'sub-nav'
},
{
title: 'Contacts',
path: '/contactmaster',
icon: <IoIcons.IoIosPeople />,
cName: 'sub-nav'
}
]
},
{
title: 'My Profile',
path: '/add-usersmall',
icon: <AiIcons.AiOutlineUser/>,
},
{
title: 'PDF',
path: '/pdfcomponent',
icon: <AiIcons.AiOutlineUser/>,
},
{
title: 'Users PageSort Test',
path: '/userstable',
icon: <AiIcons.AiOutlineUser/>,
},
{
title: 'Logout',
path: '/logout',
icon: <AiIcons.AiOutlineLogin />,
}
];
...
You have a state for the sidebar in
const Sidebar = () => {
const [sidebar, setSidebar] = useState(false);
const showSidebar = () => setSidebar(!sidebar);
...
If I'm not wrong, you close it in
<AiIcons.AiOutlineClose onClick={showSidebar} />
You can pass down that showSidebar function to the link components, somthing like
<SubMenu item={item} key={index} toggleSidebar={showSidebar}/>
(Also, avoid using index as key)
Receiving the funtion in the SubMenu component
const SubMenu = ({ item,toggleSidebar }) => {
thus, you are able to use it in the link
The following code in the subMenu.js along with #Daniel toggleSidebar code in sideBar.js rendered the desired results.
...
return (
<>
<SidebarLink to={item.path} onClick={item.subNav && showSubnav}>
<div>
{item.icon}
{/*following onClick={toggleSidebar} allows mainm menu items without subnav to close sidebar */}
<SidebarLabel onClick={toggleSidebar}>{item.title}</SidebarLabel>
</div>
<div>
{item.subNav && subnav
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</SidebarLink>
{subnav &&
item.subNav.map((item, index) => {
return (
//following onClick={toggleSidebar} allows submenu selections to close sidebar
<DropdownLink to={item.path} key={index} onClick={toggleSidebar}>
{item.icon}
<SidebarLabel>{item.title}</SidebarLabel>
</DropdownLink>
);
})}
</>
);
...

React Data-Table search component not rendering/working like it should

I am using an npm package called react-datatable for a project and I am trying to build out a way to search through the data. I found a repo where someone was able to do this; however, his react skills are above mine and I cannot seem to make his code work properly on my end. I would just like to know why it isn't rendering anything on my front end. Any advice would be helpful. Thank you.
His code:
import React from 'react';
import styled from 'styled-components';
import { storiesOf } from '#storybook/react';
import faker from 'faker';
import Button from '../shared/Button';
import DataTable from '../../../src/index';
const createUser = () => ({
id: faker.random.uuid(),
name: faker.name.findName(),
email: faker.internet.email(),
address: faker.address.streetAddress(),
bio: faker.lorem.sentence(),
image: faker.image.avatar(),
});
const createUsers = (numUsers = 5) =>
new Array(numUsers).fill(undefined).map(createUser);
const fakeUsers = createUsers(2000);
const TextField = styled.input`
height: 32px;
width: 200px;
border-radius: 3px;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border: 1px solid #e5e5e5;
padding: 0 32px 0 16px;
&:hover {
cursor: pointer;
}
`;
const ClearButton = styled(Button)`
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
height: 34px;
width: 32px;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
`;
const FilterComponent = ({ filterText, onFilter, onClear }) => (
<>
<TextField id="search" type="text" placeholder="Filter By Name" aria-label="Search Input" value={filterText} onChange={onFilter} />
<ClearButton type="button" onClick={onClear}>X</ClearButton>
</>
);
const columns = [
{
name: 'Name',
selector: 'name',
sortable: true,
},
{
name: 'Email',
selector: 'email',
sortable: true,
},
{
name: 'Address',
selector: 'address',
sortable: true,
},
];
const BasicTable = () => {
const [filterText, setFilterText] = React.useState('');
const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false);
const filteredItems = fakeUsers.filter(item => item.name && item.name.toLowerCase().includes(filterText.toLowerCase()));
const subHeaderComponentMemo = React.useMemo(() => {
const handleClear = () => {
if (filterText) {
setResetPaginationToggle(!resetPaginationToggle);
setFilterText('');
}
};
return <FilterComponent onFilter={e => setFilterText(e.target.value)} onClear={handleClear} filterText={filterText} />;
}, [filterText, resetPaginationToggle]);
return (
<DataTable
title="Contact List"
columns={columns}
data={filteredItems}
pagination
paginationResetDefaultPage={resetPaginationToggle} // optionally, a hook to reset pagination to page 1
subHeader
subHeaderComponent={subHeaderComponentMemo}
selectableRows
persistTableHead
/>
);
};
storiesOf('Filtering', module)
.add('Example 1', () => <BasicTable />)
My code:
import React from 'react';
import NotesListHtml from './NoteListHtml'
import DataTable from 'react-data-table-component';
const FilterComponent = ( { filterText, onClear, onFilter }) => (
<>
<input id="search" type="text" placeholder="Filter By Name" aria-label="Search Input" value={filterText} onChange={onFilter} />
<button type="button" onClick={onClear}>X</button>
</>
)
const BasicTable = (props) => {
let result = props.notes.map(function(el) {
var o = Object.assign({}, el);
o.fullName = `${props.data.first_name} ${props.data.last_name}`;
return o;
})
const ExpandableComponent = ({ data }) =>{
return(<p style={{padding:'20px'}}>{data.contentBody}</p>)
}
const columns = [
{
name: 'Organization',
selector: row => row.type[0].label || row.type,
sortable: true,
wrap:true
},
{
name: 'Title',
selector: 'title',
sortable: true,
},
{
name: 'Date',
selector: row=>row.createdAt.split('T')[0],
sortable: true,
},
{
name: 'Author',
selector: 'fullName',
sortable: true,
},
];
const customStyles = {
headCells: {
style: {
backgroundColor:'#f3f7f9',
},
},
cells: {
style: {
color:'#79838b'
},
},
};
const [filterText, setFilterText] = React.useState('');
const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false);
const filteredItems = props.notes.filter(item => item.type[0].label && item.type[0].label.toLowerCase().includes(filterText.toLowerCase()))
const subHeaderComponentMemo = React.useMemo(() => {
console.log('subheader')
const handleClear = () => {
console.log('hit1')
if (filterText) {
setResetPaginationToggle(!resetPaginationToggle);
setFilterText('');
}
}
console.log('beforereturn');
return (
<>
{/* <input id="search" type="text" placeholder="Filter By Name" aria-label="Search Input" value={filterText} onChange={onFilter} /> */}
<button type="button" onClick={handleClear}>X</button>
</>
)
}, [filterText, resetPaginationToggle])
return (
<>
<DataTable
subHeaderComponent={subHeaderComponentMemo}
className='table'
columns={columns}
data={filteredItems}
pagination={true}
highlightOnHover={true}
pointerOnHover={true}
expandableRows={true}
expandOnRowClicked={true}
expandableRowsComponent={<ExpandableComponent data={result} />}
paginationResetDefaultPage={resetPaginationToggle} // optionally, a hook to reset pagination to page 1
customStyles={customStyles}
subHeader={false}
/>
</>
);
}
export default BasicTable;

react map leaflet circleMarker doesn't render

I have a leaflet map, and I have one button. I want to display some circlesMarker when I click on a button and remove the circlesMarker when I click again (remove option is not implemented yet)
I don't want to render each time all map when I click the button, I want render only the circleMarker.
CircleMarker doesn't render on a map, but I can see the map
Here my code :
COMPONENT MAP
function Map1() {
const apartments = [
{ roomType: 'shared room', geometry: [41.402610, 2.204270] },
{ roomType: 'shared room', geometry: [41.411300, 2.217630] },
{ roomType: 'private room', geometry: [41.410220, 2.212520] },
{ roomType: 'apartament sencer', geometry: [41.410630, 2.216970] },
{ roomType: 'private room', geometry: [41.409190, 2.209030] },
]
let map = useRef(null);
useEffect(() => {
let url = 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}'
const access = 'pk.eyJ1Ijoiam9zZXQyMSIsImEiOiJjazF1bGZlcHowYjVlM2RwYW9ia2pwaWtlIn0.9n-6tKArfdSfd15Do6YxLA'
map.current = L.map("map");
const defaultCenter = [41.383, 2.173];
const defaultZoom = 13.10;
let layer = L.tileLayer(url, {
maxZoom: 18,
id: 'mapbox.streets',
accessToken: access,
});
map.current.setView(defaultCenter, defaultZoom);
layer.addTo(map.current);
}, [map])
const handleOnclik = (e) => {
e.preventDefault()
let color = (e.target.name)
if (color === 'pink') {
apartments.map(item => {
if (item.roomType === 'private room' || item.roomType === 'shared room') {
return (
<div>
<p>Carme</p>
<CircleMarker className="circle"
center={[41.409190, 2.209030]}
color={'#000080'}
width={.5}
fillColor={'blue'}
fillOpacity={0.5}
stroke={'black'}
>
</CircleMarker >
</div>
)
}
})
}
return (<>
<div className="container">
<div>
<div id="map" className="normalscreen"></div>
</div>
<div id="basemaps-wrapper" className="leaflet-bar">
<select id="basemaps">
<option>Visualització</option>
<option value="mapbox.streets">Streets</option>
<option value="mapbox.satellite">Satellite</option>
<option value="mapbox.outdoors">Outdoors</option>
<option value="mapbox.dark">Dark</option>
<option value="mapbox.light">Light</option>
<option value="mapbox.DarkGray">Dark Gray</option>
</select>
<button onClick={onChangeFullScreen}>Full Screen</button>
<div>
<img onClick={handleOnclik} name="pink" src="images/pink" alt="habitacio" />
<img onClick={handleOnclik} name="green" src="images/green" alt="apartament" />
</div>
</div>
</div>
</>)
}
export default Map1
MAP CSS
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.container {
display: flex;
flex-direction: column;
width: 100vw;
justify-content: center;
}
#map {
display: block;
}
.normalscreen {
width: 50%;
height: 50vh;
left: 49%;
position: relative;
}
.fullscreen {
width: 100%;
height: 80vh;
left: 0%;
text-align: center;
}
APP
import React from 'react'
import { withRouter } from 'react-router-dom'
import Navbar from './Navbar'
import Map from './Map'
function App() {
return (<>
<Navbar />
<Map />
</>);
}
export default withRouter(App)
There is no CircleMarker component on leaflet library exposed unless you use react-leaflet which you don't. Therefore here is the code you need to use in order to add to the map circle markers:
const handleOnclik = () => {
apartments.forEach(({ geometry }) => {
new L.CircleMarker(geometry, {
radius: 5,
fillColor: "blue",
width: 0.5,
stroke: "black",
color: "#000080",
fillOpacity: 0.5
}).addTo(map.current);
});
};
I removed the if statements for simplicity just to illustrate an example.
Demo

Resources