How to create a nested accordion in reactjs - reactjs

Intro -
I'm trying to show JSON data in a Accordion. So I used react-sanfona (github) to build that. I'm trying to call getComponent function recursively so that I can check if it is an array or object if it is array I'm calling same function. better show you the pogress so far.
Problem - I'm getting [object Object] at the second level even I call the getComponent recursively
Edit on codesandbox

Problem was that you didn't return anything when dealing with object
So this part
Object.keys(record).map((key, index) => {
console.log(44);
return (
<AccordionItem className="ml-5" title={`1 - ${index}`} expanded>
{getComponent(record[key])}
</AccordionItem>
);
});
should be
return Object.keys(record).map((key, index) => {
console.log(44);
return (
<AccordionItem className="ml-5" title={`1 - ${index}`} expanded>
{getComponent(record[key])}
</AccordionItem>
);
});
I added a default expanded property and now it displays all data.
Check
this sandbox

Hy, I don't know exactly what you want to display but here is a version working.
import { Accordion, AccordionItem } from "react-sanfona";
import "./styles.css";
const datalist = [
{
id: 3423235234,
name: "John",
address: [
{
first: "city1",
second: "city2"
}
]
}
];
export default function App() {
function getComponent(record) {
if (Array.isArray(record)) {
return record.map((b, index) => (
<AccordionItem className="ml-5" title={`${index}`} key={index}>
{getComponent(b)}
</AccordionItem>
));
}
if (typeof record === "object") {
return (
<div>
{Object.keys(record).map((key, index) => {
return (
<AccordionItem
className="ml-5"
title={`1 - ${index}`}
expanded
key={index}
>
{getComponent(record[key])}
</AccordionItem>
);
})}
</div>
);
}
if (typeof record === "string" || typeof record === "number") {
console.log("string or number: ", record);
return <AccordionItem className="ml-5" title={`2 - ${record}`} />;
}
return (
<AccordionItem className="ml-5" title={`3 - ${record.toString()}`} />
);
}
return (
<div className="App">
<div className="px-7">
<Accordion>{getComponent(datalist)}</Accordion>
</div>
</div>
);
}
The package you're using raise many errors in the console (with no configuration). Did you check the material ui's accordions ? https://material-ui.com/components/accordion/

This is working code, which might help you
Here, initially root folder will be hiding all children once click on root -> its direct children will be displayed/hidden and so on
//explorer is the json coming from parent component
import React, { useState } from "react";
function Accodian({ explorer }) {
const [expand, setExpand] = useState(false);
if (explorer.children) {
return (
<div>
{/* {explorer.children ? ( */}
<>
<div onClick={() => setExpand((prevState) => !prevState)}>
{explorer.name}
</div>
{expand ? (
<>
{explorer.children.map((child) => {
// return <div key={child.name} style = {{paddingLeft: '20px'}}>{child.name}</div>;
return <Accodian explorer={child} key={child.name} />;
})}
</>
) : null}
</>
{/* ) : null} */}
</div>
);
} else {
return <div style={{ paddingLeft: "20px" }}>{explorer.name}</div>;
}
}
export default Accodian;
Check this sandbox : https://codesandbox.io/s/js-recursion-accordian-v03y5q?file=/src/components/Accordian.js:0-823/

Related

how can I add any event to a specific part of component ? react

I have list of data that render it with map - I need to add an event just in one of the item from that list.
const UserModal = (props) => {
const {user,setUser} = props ;
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat />},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
this my code and for example I need to add an event on specific object that has id=5 in that list .
how can I do that
I don't know if there is some sort of built-in solution for this, but here is a simple workaround:
I changed a few things for simplicity's sake
The important part is the if statement with checks if item ID is 5 then if so adds a div with the desired event
function App() {
const list = [
,
{ id: 3, text: "comonent 3" },
{ id: 5, text: "comonent 5 (target)" }
];
return (
<>
<h1>Hello world<h1/>
{list.map((item) => (
<div key={item.id} style={{ backgroundColor: "red" }}>
<p>{item.text}</p>
{item.id == 5 ? (
<div
onClick={() => {
alert("This component has a event");
}}
>
{" "}
event
</div>
) : (
<></>
)}
</div>
))}
</>
);
}
const UserModal = (props) => {
const {user,setUser} = props ;
const myEvent = () => alert('event fired');
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat /> , event : myEvent},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p onClick={item.event}>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
list.map((item, i)=> (
item.id == 5 ?
<div onClick={handleClick} key={i}></div>
:
<div key={i}></div>
)

React dropdown function triggers all dropdowns instead of selected menu

So I'm trying to figure out how to only trigger the dropdown for the selected menu. Right now if I click on any of my menu items, it triggers every single dropdown.
I currently have a simple function that sets the state from false to true
const showSubnav = () => setSubnav(!subnav);
I attempted to use useRef() but for some reason the ref.current kept showing the wrong element that I clicked on.
Here is my current dropdown code
{SidebarData.map((item, index) => {
return (
<>
<li
ref={ref}
// Here's the function that checks if there's a sub menu, then it triggers
showSubnav
onClick={item.subNav && showSubnav}
key={index}
className={item.cName}
>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
{subnav ? (
<>
{item.subNav &&
item.subNav.map((item, index) => {
return (
<div key={index} className='sub-nav-container'>
<Link to={item.path} className={item.cName}>
{item.icon}
<span>{item.title}</span>
</Link>
</div>
);
})}
</>
) : null}
</>
);
})}
So the issue is that any li with a sub menu will display if I click on it using my code
onClick={item.subNav && showSubnav}
I need a function or way to check for the current element clicked and to only trigger that sub menu for that specific element.
I also have react icons that I used in my data file
So I'm trying to display them only if there's a sub nav
This code is the logic, but I can't seem to fit it anywhere properly
if(item.subNavExists) {
{item.downArrow}
} else if(subnav is click) {
{item.upArrow}
}
else {
return null
}
How would I fit this logic inside of my li tags?
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
Try this approach,
Create a separate component for Sidebar and track each sidebar changes separately using local state like below,
import { Link } from "#material-ui/core";
import React, { useState } from "react";
import "./styles.css";
const SidebarData = [
{
id: 1,
title: "Item1"
},
{
id: 2,
title: "Item2"
}
];
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{SidebarData.map((item, index) => {
return <Sidebar item={item} />;
})}
</div>
);
}
const Sidebar = ({ item }) => {
const [selected, setSelected] = useState(false);
return (
<div class="container">
<button onClick={() => setSelected(!selected)}>
{selected ? "hide" : "show"}
</button>
<li key={item.id} className={item.cName}>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
{selected && <div>SUB-NAV</div>}
</div>
);
};
Sample demo - https://codesandbox.io/s/compassionate-booth-mj9xo?file=/src/App.js

How to render multiple images from array in React JS?

everyone. I'm fairly new to React Js, and I got stuck while trying to render images from an array. My code is :
import { home } from "../homeObj";
const Logos = ({ title, img, img2, img3, key }) => {
return (
<>
<div className={styles.anaShif} key={key}>
<h2 className={styles.h2}> {title} </h2>
<div className={styles.logos}>
<img
id={img.id}
src={img}
alt={img.alt}
className={styles.img}
srcSet={`${img2} 2x,
${img3} 3x`}
/>
</div>
</div>
</>
);
};
function Trusted() {
const logoComponent = home.map((_objects, i, id) => {
if (home[i].id === "logos") {
return (
<>
<Logos
key={i}
id={id}
title={home[i].title}
img={home[i].logos[i].src}
/>
</>
);
}
});
return (
<>{logoComponent}</>
);
}
export default Trusted;
and { home } :
export const home = [
{
id: "logos",
title: "Welcome to",
logos: [
{
id: 1,
alt: "car",
src: require("./logo_06.png"),
src2: require("./logo_06#2x.png"),
src3: require("./logo_06#2x.png"),
},
{
id: 2,
alt: "red-car",
src: require("./logo_07.png"),
src2: require("./logo_07#2x.png"),
src3: require("./Tlogo_07#3x.png"),
},
],
]
What happens is that I can only display an image of the 2nd element of logos array, it's like React Js completely skips 1st element(id, img.src, alt).
What I want to do is to be able to display both images at the same time and also add elements dynamically, when a new element gets added to {home}, it should display without hardcoding.
Any help would be greatly appreciated.
You have to return the map of logos which is not happening in your current code. Hence you are getting only one Logos component.
import React from 'react';
import { home } from '../homeObj';
const Logos = ({ title, img, img2, img3, key }) => {
return (
<>
<div className={styles.anaShif} key={key}>
<h2 className={styles.h2}> {title} </h2>
<div className={styles.logos}>
<img
id={img.id}
src={img}
alt={img.alt}
className={styles.img}
srcSet={`${img2} 2x,
${img3} 3x`}
/>
</div>
</div>
</>
);
};
function Trusted() {
const logosIndex = home.findIndex((obj) => obj.id === 'logos');
const logos = home[logosIndex].logos.map(({ id, alt, src }) => {
return <Logos key={id} id={id} title={home[logosIndex].title} img={src} />;
});
return logos;
}
export default Trusted;
You not only need to map over home but also the logos array with-in each home object. Also in your case, you don't need the third arg which is the entire array.
Simplify your code like this:
home.map((_objects, i) => {
if (_objects.id === "logos") {
return (
<>
{
_objects.logos.map(logo => (
<Logos
key={logo.id}
id={logo.id}
title={logo.title}
img={logo.src}
/>
))
}
</>
);
}else {
return null
}
}
Without much if else, you can also write like this.
home.filter((_objects, i) => _objects.id === 'logos')
.map(({logos})=>logos.map(logo=>
<Logos
key={logo.id}
{...logo}
/>))

Reset pagination to the first page by clicking a button outside the component

I'm using material UI usePagination hook to create a custom pagination component, so far so good, the functionality works as expected but I was wondering how I can be able to reset the pagination to the first page by triggering a button that is not part of the pagination component.
Does anyone has an idea on how to trigger that?
This is my component.
import React from "react";
import PropTypes from "prop-types";
import { usePagination } from "hooks";
function arrow(type) {
return (
<i
className={`fa fa-chevron-${
type === "next" ? "right" : "left"
} page-icon`}
/>
);
}
function Pagination({ data, itemCount, onChange }) {
const { items } = usePagination({
count: Math.ceil(data.length / itemCount, 10),
onChange
});
return (
<nav aria-label="Paginator">
<ul className="pagination-component">
{items.map(({ page, type, selected, ...item }, index) => {
let children;
if (type === "start-ellipsis" || type === "end-ellipsis") {
children = "…";
} else if (type === "page") {
children = (
<button
type="button"
automation-tag={`page-${page}`}
className={`page-button ${selected ? "selected" : ""}`}
{...item}
>
{page}
</button>
);
} else {
children = (
<button
automation-tag={type}
className="page-button"
type="button"
{...item}
>
<span className="d-none">{type}</span>
{arrow(type)}
</button>
);
}
return (
// eslint-disable-next-line react/no-array-index-key
<li key={index} className="page-item">
{children}
</li>
);
})}
</ul>
</nav>
);
}
What I'm trying is to create a select component that the onChange function will sort the data, depending on the selection, but when the data is sorted I want to return the pagination component to the first page
const TableVizContainer = props => {
const [currentPage, setCurrentPage] = useState(1);
const [sortColumn, setSortColumn] = useState(1);
const [range, setRange] = useState({
start: 0,
end: 25
});
const onChangePage = (_event, page) => {
setCurrentPage(page);
setRange({
start: 25 * (page - 1),
end: 25 * page
});
};
const onSelectChange = event => {
const { value } = event.target;
setCurrentPage(1);
setSortColumn(parseInt(value, 10));
};
return (
<div
className="table-viz-container container-fluid my-4 float-left"
automation-tag={`table-viz-${automationId}`}
>
<div className="d-flex justify-content-between mb-3 leaderboard-meta">
<span className="leaderboard-title">{visualization.title}</span>
<div className="mr-5">
<label htmlFor="sort-table-select">
Sort By:
<select
id="sort-table-select"
onChange={onSelectChange}
value={sortColumn}
>
{visualization.columns.map((column, index) => {
const uniqueId = uuidv1();
return (
<option key={uniqueId} value={index}>
{setSelectValue(column, visualization.metrics)}
</option>
);
})}
</select>
</label>
</div>
</div>
<div className="d-block d-sm-flex justify-content-between align-items-center my-2 px-2">
<span className="page-items-count" automation-tag="pagination-count">
{`Showing ${range.start === 0 ? 1 : range.start + 1} - ${
range.end <= visualization.rows.length
? range.end
: visualization.rows.length
} of ${visualization.rows.length}.`}
</span>
<Pagination
currentPage={currentPage}
data={visualization.rows}
itemCount={25}
onChange={onChangePage}
/>
</div>
</div>
);
};
Does anyone has an idea on how to reset and move the pagination page to the first one without clicking the component?
There are two ways.
1. Passing Props
Let's just say you have a function called jump() and passing 1 as an argument will reset the pagination. So, you can pass the jump function as a property and reuse that on other components.
function jump(){
setCurrentPage(1)
}
<MyCompnent resetPage={jump} />
// MyComponent
function MyComponent({resetPage}){
return (
<button onClick={resetPage(1)}></button>
)
}
2. On Changing Route
You can reset your pagination when your route will change. For example, you are using a router npm package and that package has a method called onChange or routeChangeStart. With those methods or after creating that method you can implement a function like below.
Router.events.on("routeChangeStart", () => {
jump(1);
});

React material-ui, grid list cannot render grid items

export interface FlatsGridProps {
flats: IClusterFlats[];
}
export const FlatsGrid: React.StatelessComponent<FlatsGridProps> = (props: FlatsGridProps) => {
if (props.flats.length === 0) {
return (<div> empty </div>);
}
return (
<div style={styles.root}>
<GridList
cols={2}
style={styles.gridList}
cellHeight={180}>
{props.flats.map((f, i) => {
<div key={i}> element </div>
})}
</GridList>
</div>
)
};
When I render GridList control it throws exception
Cannot read property 'props' of null in GridList.js
View from console below.
I think you need to return the child from the map. The grid.js complains about its child being undefined.
<GridList
cols={2}
style={styles.gridList}
cellHeight={180}>
{props.flats.map((f, i) => {
return (<div key={i}> element </div>)
})}
</GridList>

Resources