React vertical Nested List open and close sub List - reactjs

i have a react.js component in which i am displaying states and under i am displaying districts. To display this list it's working fine. The problem i want when i press any sates only that particular state sublist should display not all states sublist.
import React,{useState} from "react"
Const [open,,setOpen]=useState(false);
//Wrapper component
</div>
{states.map((city, index) => {
return <StateList state={state} key={index} />;
})}
</div>
//state component
<div onClick={()=>setOpen(!open)}>
<span >{state.name}</span>
<svg
viewBox="0 0 24 24"
className={`
${open ? "rotate-180" : ""}
`}
>
</svg>
</h2>
{open && <AreaList area={city} />}
</div>
//district component
const AreaList = ({ state }) => {
return state.districts.map((district) => (
<li>
<span className="ml-2 text-outer-space">
{district.name}
</span>
</li>
));
};

Here is working solution (without styles):
Codesandbox
import { useState } from "react";
const data = [
{
name: "Fujairah",
districts: [
{ name: "Al Buthna" },
{ name: "Al Bedia" },
{ name: "Town Center" },
{ name: "Wadi Al Sedr" }
]
},
{
name: "Abu Dhabi",
districts: [{ name: "Al Aman" }, { name: "Al Bahya" }]
}
];
const App = () => {
return (
<div>
{data.map((city) => {
return <City key={city.name} city={city} />;
})}
</div>
);
};
const City = ({ city }) => {
const [open, setOpen] = useState(false);
return (
<div onClick={() => setOpen(!open)}>
<h2>
<span>{city.name}</span>
</h2>
{open && <DistrictList city={city} />}
</div>
);
};
const DistrictList = ({ city }) => {
return city.districts.map((district) => (
<li key={district.name}>
<span>{district.name}</span>
</li>
));
};
export default App;

Related

Currently How to test a callback function with JEST + React?

I would like to test this component:
the parameter received in onClick={() => onConfirmVote(number)}
The value returned in getVoteText
MY CODE:
function CandidateList({ data }: IProps) {
const { convertToCurrency, convertToPercentage } = useNumberConversion();
const getVoteText = useCallback(
(total: number, percentage: number) => `${convertToCurrency(total)} (${convertToPercentage(percentage)})`,
[convertToCurrency, convertToPercentage]
);
return (
<ListGroup data-testid='candidate-list-component'>
{data.map(({ onConfirmVote, avatar, number, votesConfirmed, hasVoted }) => (
<ListGroup.Item key={`${number}`} as='li' className='d-flex flex-wrap gap-2 align-items-center'>
<div className='d-flex justify-content-between align-items-center w-100'>
<div className='d-flex flex-wrap align-items-center gap-2'>
<Avatar alt={`Candidate #${number}`} src={avatar} />
<h5 className='fw-bold mb-0'>#{number}</h5>
</div>
<div className='mt-1 d-flex flex-wrap align-items-center gap-2'>
<span className='mb-1'>Votes:</span>
<Badge bg='primary' pill style={{ width: '155px' }} data-testid='badge-vote-value-component'>
{getVoteText(votesConfirmed.total, votesConfirmed.totalPercentage)}
</Badge>
</div>
</div>
<Button className='w-100' variant='success' onClick={() => onConfirmVote(number)} disabled={hasVoted}>
CONFIRM
</Button>
</ListGroup.Item>
))}
</ListGroup>
);
}
MY TESTE CODE:
describe('[CANDIDATE LIST] - Testing Confirm Vote Button Component', () => {
test('Should be work on click to Confirm Vote!', () => {
render(<CandidateList data={MOCKED_DATA} />);
const confirmVoteButton = screen.getAllByRole('button');
confirmVoteButton.forEach(button => {
console.log(button);
expect(fireEvent.click(button)).toBe(true);
});
});
});
I have simplified your component and you can try sth like this:
the component
import React from "react";
const ManyButtons = ({ data }) => {
const getVoteText = (total, percentage) => `${total} (${percentage})`;
return (
<div>
{data.map(({ onConfirmVote, number, votesConfirmed: { total, totalPercentage } }, index) => (
<div key={index}>
<div>{getVoteText(total, totalPercentage)}</div>
<button onClick={() => onConfirmVote(number)}>
CONFIRM
</button>
</div>
))}
</div>
);
};
export default ManyButtons;
The test:
import { screen, render, fireEvent } from "#testing-library/react";
import ManyButtons from "../ManyButton";
const mockOnConfirmVote = jest.fn();
const mockedData = [
{ onConfirmVote: mockOnConfirmVote, number: 1, votesConfirmed: { total: 1, totalPercentage: 1 } },
{ onConfirmVote: mockOnConfirmVote, number: 2, votesConfirmed: { total: 2, totalPercentage: 2 } },
{ onConfirmVote: mockOnConfirmVote, number: 3, votesConfirmed: { total: 3, totalPercentage: 3 } },
]
describe('it should work', () => {
it('should work', () => {
render(<ManyButtons data={mockedData} />);
const buttons = screen.getAllByRole('button', { name: /confirm/i });
expect(buttons).toHaveLength(mockedData.length);
buttons.forEach(btn => fireEvent.click(btn))
expect(mockOnConfirmVote).toHaveBeenCalledTimes(buttons.length);
mockedData.forEach(({ number, votesConfirmed: { total, totalPercentage }}) => {
expect(mockOnConfirmVote).toHaveBeenCalledWith(number);
expect(screen.getByText(`${total} (${totalPercentage})`)).toBeInTheDocument();
})
})
})

How get fetch api data?

For example, when you press the science button, the information (author, date, title etc) of the science category should appear.
import React, { useState } from "react";
const App = () => {
const [state, setState] = useState([]);
const Category = [
"All",
"Business",
"Sports",
"World",
"Technology",
"Entertainment",
"Science",
];
const fetchValue = (category) => {
fetch(`https://inshorts-api.herokuapp.com/news?category=${category}`)
.then((res) => res.json())
.then((res) => setState(res.data));
};
const CategoryButton = ({ category }) => (
<button onClick={() => fetchValue(category)}> {category}</button>
);
return (
<>
<div className="d-flex">
{Category.map((value, index) => {
return <CategoryButton category={value} key={index} />;
})}
</div>
<br />
{state?.map((data) => {
return (
<div class="card">
<img src={data.imageUrl} alt="Avatar" style={{ width: "300px" }} />
<div class="container">
<h4>
<b>{data.author}</b>
</h4>
<p>{data.content}</p>
</div>
</div>
);
})}
</>
);
};
export default App;

Function invoking only for first item React

i wrote code to expand "more info" block after clicking button, but function invoking only for first item.
Is it happening beacuse i use let more = document.getElementById("more"); ?
How can i change code for expanding only specifed item?
const Currency = ({ filteredItems, isLoading }) => {
const addListeners = () => {
let more = document.querySelectorAll(".more-info");
more.forEach(item => {
item.addEventListener("click", toggle)
})
console.log(more)
}
const toggle = () => {
let more = document.getElementById("more");
if (more.className === "more-info") {
more.className = "more-info-active";
} else {
more.className = "more-info";
}
}
return isLoading ? (<div className="loader">Loading...</div>) : (
<div items={filteredItems}>
{filteredItems.map((item) => (
<div key={item.id} className="item-wrapper">
<div className="item">
<h2>{item.name}</h2>
<img src={item.image} alt="crypto symbol"></img>
<h3>{item.symbol}</h3>
<p>{item.current_price} pln</p>
<button onLoad={addListeners} onClick={toggle} className="info-btn" id="item-btn" >➜</button>
</div>
<div id="more" className="more-info">
<div className="more-data">
<div className="info-text">
<p>high_24: {item.high_24h}</p>
<p>low_24: {item.low_24h}</p>
</div>
<div>
<p>price_change_24h: {item.price_change_24h}</p>
<p>price_change_percentage_24h: {item.price_change_percentage_24h}</p>
</div>
<div>
<Sparklines className="sparkline" height={60} margin={10} data={item.sparkline_in_7d.price}>
<SparklinesLine style={{fill:"none"}} color="#b777ff" />
</Sparklines>
</div>
</div>
</div>
</div>
))}
</div>
);
}
Dont use document.getElement... , this is a Real DOM but React uses Virtual DOM.
Instead create a state with an array and on onClick event pass item as an argument and store in state , you can store only id e.g.
Last step, check in JSX if state includes item.id , if true then expand
this is an example , keep in mind this is not the only solution. Just simple example.
import React, { useState } from "react";
const fakeData = [
{
id: "123123-dfsdfsd",
name: 'Title One',
description: "Description bla bla bla One"
},
{
id: "343434-dfsdfsd",
name: 'Title Two',
description: "Description bla bla bla Two"
},
{
id: "6767676-dfsdfsd",
name: 'Title Three',
description: "Description bla bla bla Three"
}
]
function App() {
const [tabs, setTabs] = useState([]);
function _onToggle(item) {
const isExist = tabs.includes(item.id)
if (isExist) {
setTabs(prevData => prevData.filter(pd => pd !== item.id))
} else {
setTabs(prevData => [item.id, ...prevData])
}
}
return (
<div className="app">
<div>
{
fakeData.map((item, i) => (
<div key={i}>
<h3 onClick={() => _onToggle(item)}>{item.name}</h3>
<p style={{ display: tabs.includes(item.id) ? 'block' : 'none' }}>
{ item.description }
</p>
</div>
))
}
</div>
</div>
);
}
export default App;

How I can resolve this error message in react? Cannot read property 'details' of null

Hello Stackoverflow community!
I'am practicing with react. I am building a very simple shopping cart system. With the app you can select from products. It adds to the shoppingcart. I'm got this error message: TypeError: Cannot read property 'details' of null.
I'am attaching my code.
App.js
import React, {useState, useEffect } from "react";
import Shop from "./Shop";
import Cart from "./Cart"
const ShoppingItems = [{
id: 1,
details: {
type: "cloth",
name: "Blue jacket",
price: 15000
}
},
{
id: 2,
details: {
type: "cloth",
name: "Trousers",
price: 9990
}
},
{
id: 3,
details: {
type: "cloth",
name: "T-shirt",
price: 5000
}
}
];
const App = () => {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {console.log(selectedItem)}, [selectedItem]);
return(
<div className="ui container">
<Shop Shopitems={ShoppingItems} setSelectedItem={setSelectedItem}/>
<Cart selectedItem={selectedItem}/>
</div>
);
};
export default App;
Shop.js
import React from "react";
const Shop = ({Shopitems, setSelectedItem}) => {
const AddItem = (id) => {
const selectedItem = Shopitems.find( item => item.id === id);
if(selectedItem)
{
setSelectedItem(selectedItem);
}
return;
};
const renderedItems = Shopitems.map((shopitem) => {
return(
<div key={shopitem.id} className="card">
<div className="content">
<div className="header">{shopitem.details.name}</div>
<div className="description">
{shopitem.details.price + " Ft"}
</div>
</div>
<div onClick={() => AddItem(shopitem.id)} className="ui bottom attached button">
<i className="cart icon"></i>
Add to cart
</div>
</div>
);
});
return (
<div className="ui cards">{renderedItems}</div>
);
};
export default Shop;
Cart.js
import React, {useState, useEffect} from "react";
const Cart = ({selectedItem}) => {
const [shoppingCart, setShoppingCart] = useState([]);
useEffect(() => {
//Adding to the shopping cart when an element selected
setShoppingCart([...shoppingCart, selectedItem]);
}, [selectedItem]);
const renderedItems = shoppingCart.map((item) => {
return(
<ul>
<li key={item.id}>
<div className="item">
{item.details.name}
</div>
</li>
</ul>
);
});
return(
<div>
<h1>Shopping Cart</h1>
{renderedItems}
</div>
);
};
export default Cart;
You need to verify that selectItem is not null because you cannot use .map on null, it needs to be an array ([]).
change you code to
import logo from './logo.svg';
import './App.css';
import { useEffect, useState } from 'react';
import Records from './Records';
import Shop from "./Shop";
import Cart from "./Cart"
const ShoppingItems = [{
id: 1,
details: {
type: "cloth",
name: "Blue jacket",
price: 15000
}
},
{
id: 2,
details: {
type: "cloth",
name: "Trousers",
price: 9990
}
},
{
id: 3,
details: {
type: "cloth",
name: "T-shirt",
price: 5000
}
}
];
const App = () => {
const [selectedItem, setSelectedItem] = useState(null);
useEffect(() => {console.log(selectedItem)}, [selectedItem]);
return(
<div className="ui container">
{selectedItem !== null ? <><Shop Shopitems={ShoppingItems} setSelectedItem={setSelectedItem}/> <Cart selectedItem={selectedItem}/> </>: <></>}
</div>
);
};
export default App;
This wil render without error but it is blanc page because you pass 'null' as value.
It may not be a value for the first time. To solve this problem, you can bet where you have this error:for example:
const renderedItems = Shopitems?Shopitems.map((shopitem) => {
return(
<div key={shopitem.id} className="card">
<div className="content">
<div className="header">{shopitem.details.name}</div>
<div className="description">
{shopitem.details.price + " Ft"}
</div>
</div>
<div onClick={() => AddItem(shopitem.id)} className="ui bottom attached button">
<i className="cart icon"></i>
Add to cart
</div>
</div>
);
}) :[] ;
return (
<div className="ui cards">{renderedItems}</div>
);
};
export default Shop;
'

How to take a link from one array and assign it to another?

Friends, how to show the logo for the company from "arr1", the link to which is located in "arr2"? That is, to make the logo consistent with the company
import React from "react";
import "./styles.css";
export default function App() {
const arr1 = [
{
id: "random1",
companyName: "Apple"
},
{
id: "random2",
companyName: "Samsung"
}
];
const arr2 = [
{
id: "random1",
companyName: "Apple",
logoUrl: "img.com/url"
},
{
id: "random2",
companyName: "Samsung",
logoUrl: "img.com/url"
}
];
const blockCreate = () => {
return arr1.map(item => {
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src="#" />
</span>
</p>
);
});
};
return (
<div className="App">
<div>{blockCreate()}</div>
</div>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
arr1 - Apple === arr2 - AppleLogo (logoUrl)
Now trying to do like this
https://codesandbox.io/s/kind-bush-zlyi6
companyDetails is your array so, do like this arr2.companyDetails.find()
Change this
const blockCreate = (arr1, arr2) => {
return arr1.company.map(item => {
const src = arr2.companyDetails.find(a => a.id === item.id).logoUrl;
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src={src} alt="pic"/>
</span>
</p>
);
});
};
Something like this:
import React from "react";
import "./styles.css";
export default function App() {
const arr1 = {
company: [
{
id: "random1",
companyName: "Apple"
},
{
id: "random2",
companyName: "Samsung"
}
]
};
const arr2 = {
companyDetails: [
{
id: "random1",
companyName: "Apple",
logoUrl: "img.com/url"
},
{
id: "random2",
companyName: "Samsung",
logoUrl: "img.com/url"
}
]
};
const blockCreate = (arr1, arr2) => {
return arr1.company.map(item => {
const src = arr2.find(a => a.id === item.id).logoUrl;
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src={src} />
</span>
</p>
);
});
};
return (
<div className="App">
<div>{blockCreate(arr1, arr2)}</div>
</div>
);
}
Note that I have passed arr1 and arr2 into the blockCreate function. This is better form than just having it access the variables on the higher scope since it makes your blockCreate function more modular, hence reusable.
As a react developer you should think about how this component can be re-usable meaning you can pass a single array of object and then have the component to always render the list of details.
The right way of doing this is to prepare the data before passing it to this component (can be handled in the backend or front-end depends on how you are getting this data). This is not the component's job to map through two different arrays and figure out the relation between those and render the list.
Here is an example in ECMAScript6:
// Prepare the final data
const hash = new Map();
arr1.company.concat(arr2.companyDetails).forEach(obj => {
hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj));
});
const finalArray = Array.from(hash.values());
const blockCreate = (finalArray) => {
return arr3.map(item => {
return (
<p>
<span>ID {item.id} - </span>
<br />
{item.companyName}
<span>
<br />
<img src={item.logoUrl} alt="logo" />
</span>
</p>
);
});
};

Resources