everyone in the StackOverflow community, I wanna try to have a sidebar like amazon but I fail to have the same. As I see all data of my code is working very well I try it all with console.log() function. But the inverse happens when I try to use it in the component function it doesn't work as I want. It doesn't display the subcategories list under every header category.
It returns this error:
Uncaught TypeError: Cannot read properties of undefined (reading 'map').
at CategoriesBar.js:69:1
So the categories[category] are undefined for the code. Sorry for my bad English
this is my code :
import * as React from "react";
import { useState, useEffect } from "react";
import List from "#mui/material/List";
import ListItem from "#mui/material/ListItem";
import ListItemText from "#mui/material/ListItemText";
import ListSubheader from "#mui/material/ListSubheader";
import axios from "axios";
function CategoriesBar() {
let [categories, setCategories] = useState({});
const [data, setData] = useState([]);
useEffect(() => {
try {
axios.get("/api/categories").then((res) => {
const categoriesData = res.data.map((cat) => cat.name);
let newSub = {};
const catg = res.data;
catg.forEach((category, i) => {
let categoryId = category.id;
if (catg) {
setData(categoriesData);
}
axios.get(`/api/subcategories/${categoryId}`).then((res) => {
const subcategories = res.data.subcategories;
const subName = subcategories.map((sub) => sub.name);
newSub[category.name] = subName;
});
setCategories(newSub);
});
});
} catch (err) {
console.log(err);
}
}, []);
console.log(categories);
return (
<div>
<div className="text-white font-bold bg-green-700 pt-2.5 pl-2 pb-2.5 pr-8 w-96 text-2xl">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-10 inline-block pr-2 "
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
Hello, Sign In
</div>
<List
sx={{
width: "100%",
maxWidth: 384,
bgColor: "background.paper",
position: "absolute",
overflowY: "scroll",
height: "100%",
"& ul": { padding: 0 },
}}
subheader={<li />}
>
{data.map((category, index) => (
<li key={index}>
<ul>
<ListSubheader>{category}</ListSubheader>
{categories[category].map((item, index) => (
<ListItem key={item}>
<ListItemText primary={item} />
</ListItem>
))}
</ul>
</li>
))}
</List>
</div>
);
}
export default CategoriesBar;
I think it causes because of timing issue
you need to await for subcategories to come and then setCategory state with newSub
like this :
catg.forEach(async (category, i) => {
let categoryId = category.id;
if (catg) {
setData(categoriesData);
}
await axios.get(`/api/subcategories/${categoryId}`).then(res => {
const subcategories = res.data.subcategories;
const subName = subcategories.map(sub => sub.name);
newSub[category.name] = subName;
});
setCategories(newSub);
});
Related
I'm trying to test that a click handler in my child component that is changing the parent component's state and then displaying conditional jsx in my parent component, but I can't figure out the best way to do so and I'm also having trouble debugging. My other tests that test the parent component and child component separately are working (as in I'm able to find dom elements that I expect to be present), but when I try to test the clicking of a button in the child component by rendering the parent component, my test fails.
Expected behavior:
User clicks the div with className 'open-comparison-btn'
Child component calls the props.setModalShowing function with 'true'
Parent component modalShowing state is updated to true
Parent component re-renders and displays the conditional jsx className 'comparison-modal'
The functionality is working in the localhost browser, but not in my test, and I can't even find the child component's html at all in my test.
Parent component:
import React, { useState, useEffect } from 'react';
import ProductCard from './ProductCard.jsx';
const RelatedProducts = (props) => {
const [position, setPosition] = useState(0);
const componentName = 'RelatedProducts';
const [modalShowing, setModalShowing] = useState(false);
const [currentProduct, setCurrentProduct] = useState({});
const [comparisonProduct, setComparisonProduct] = useState({});
useEffect(() => {
setCurrentProduct(props.currentProduct);
}, [props.currentProduct]);
const getFeatures = () => {
return [...currentProduct.features, ...comparisonProduct.features]
.filter((v, i, a)=>a.findIndex(v2=>(v.feature === v2.feature && v.value === v2.value)) === i);
};
return (
<>
<div className='related-products-container' role="listbox" aria-label="related products" style={{marginLeft: `-${position}px`}}>
{props.relatedProducts ?
props.relatedProducts.map((product) => {
return <ProductCard
modalShowing={modalShowing}
setModalShowing={setModalShowing}
setComparisonProduct={setComparisonProduct}
key={product.id}
product={product}
generateStars={props.generateStars}
isFetching={props.isFetching}
setIsFetching={props.setIsFetching}
parentComponent={componentName}
yourOutfit={props.yourOutfit}
addToOutfit={props.addToOutfit}
/>;
})
: null
}
</div>
<div className='fade-top'>
{ position > 0 ?
<div className="arrow-container-left" role="button" aria-label="scroll left" onClick={() => { setPosition(position - 250); }}>
<div className="arrow-left"></div>
</div>
: null
}
{ props && props.relatedProducts && position <= (props.relatedProducts.length - 4) * 250 ?
<div className="arrow-container-right" role="button" aria-label="scroll right" onClick={() => { setPosition(position + 250); }}>
<div className="arrow-right"></div>
</div>
: null
}
</div>
{modalShowing ?
<div className='comparison-modal' role='dialog' aria-label='comparison window'>
<div className='modal-top'>COMPARING</div>
<div className='modal-product-names'>
<div className='product-1'>{currentProduct.name}</div>
<div className='product-2'>{comparisonProduct.name}</div>
</div>
<table className='modal-table'>
<tbody>
{getFeatures().map((feature, index) => {
return (
<tr key={`${feature}-${index}`}>
<td className='left-check'>{currentProduct.features.filter(item => item.feature === feature.feature && item.value === feature.value).length > 0 ? '✓' : null}</td>
<td>{feature.value} {feature.feature}</td>
<td className='right-check'>{comparisonProduct.features.filter(item => item.feature === feature.feature && item.value === feature.value).length > 0 ? '✓' : null}</td>
</tr>
);
})}
</tbody>
</table>
<div className="close-btn" onClick={() => { setModalShowing(false); }}></div>
</div>
: null}
</>
);
};
export default RelatedProducts;
Child component:
import React, { useState, useEffect } from 'react';
import ratingsAPI from '../../API/Ratings.js';
import { useNavigate } from 'react-router-dom';
const ProductCard = (props) => {
const navigate = useNavigate();
const [averageRating, setAverageRating] = useState();
const stars = props.generateStars(averageRating, 'related');
useEffect(() => {
ratingsAPI.getReviewMetadata(props.product.id)
.then((metadata) => {
setAverageRating(getAverageRating(metadata.ratings));
props.setIsFetching(false);
});
}, []);
const routeChange = () => {
const path = `/${props.product.id.toString()}`;
navigate(path);
};
const displayComparison = (e) => {
props.setComparisonProduct(props.product);
props.setModalShowing(true);
e.stopPropagation();
};
const getAverageRating = (ratings) => {
var sum = 0;
var count = 0;
Object.keys(ratings).forEach(function(rating) {
sum += rating * parseInt(ratings[rating]);
count += parseInt(ratings[rating]);
});
return sum / count;
};
return (
!props.isFetching ?
<>
<div className='product-card-container' onClick={() => routeChange(props.product.id)}>
<img className='product-card-image' src={props.product.styles.results[0].photos[0].thumbnail_url}>
</img>
{props.parentComponent === 'RelatedProducts'
?
<svg className="open-comparison-btn" role='button' aria-label='open comparison' width="20px" height="20px" viewBox="0 0 32 32" onClick={(e) => { displayComparison(e); }}>
<path fill="White" stroke="black" strokeWidth="2px" d="M20.388,10.918L32,12.118l-8.735,7.749L25.914,31.4l-9.893-6.088L6.127,31.4l2.695-11.533L0,12.118
l11.547-1.2L16.026,0.6L20.388,10.918z"/>
</svg>
:
<div className="close-btn" onClick={() => { props.removeFromOutfit(props.product); }}></div>
}
<div className='product-card-description'>
<div className='product-card-category'>{props.product.category}</div>
<div className='product-card-name'>{props.product.name}</div>
<div className='product-card-price'>${props.product.default_price}</div>
<div className='product-card-stars'>{ stars }</div>
</div>
</div>
</>
: null
);
};
export default ProductCard;
Test:
it('tests that clicking the open-comparison-btn opens the modal window', async () => {
render(<RelatedProducts
addToOutfit={() => { return; }}
yourOutfit={() => { return; }}
relatedProducts={relatedProducts}
generateStars={ generateStars }
isFetching={() => { return false; }}
setIsFetching={() => { return; }}
/>, {wrapper: Router});
fireEvent(
screen.getByRole('button', {name: 'open comparison'}),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
}),
);
const modal = screen.getByRole('dialog', {name: 'comparison window'});
expect(modal).toBeInTheDocument();
});
Any advice would be appreciated.
The answer ended up being simply using await before render...
I think it might be because my child component was doing an API call, but I'm not sure why I did not need to use await when rendering and testing the child component separately.
The SelectedColumn value doesn't come in the CustomHeader component. However, setSelectedColumn works! Why🧐 ?
Also, I'm passing CustomHeader to constant components that use useMemo. Without useMemo CustomHeader doesn't work.
const [selectedColumn, setSelectedColumn] = useState(null);
console.log("selected Column Outside:", selectedColumn); // It works!
const CustomHeader = (props) => {
const colId = props.column.colId;
console.log("selected Column In CustomHeader:", selectedColumn); // Doesn't work
return (
<div>
<div style={{float: "left", margin: "0 0 0 3px"}} onClick={() => setSelectedColumn(props.column.colId)}>{props.displayName}</div>
{ selectedColumn === colId ? <FontAwesomeIcon icon={faPlus} /> : null}
</div>
)
}
const components = useMemo(() => {
return {
agColumnHeader: CustomHeader
}
}, []);
UPDATE: If I use the useState hook inside the CustomHeader component, it adds a "+" sign to each column and does not remove from the previous one. Here is a picture:
After reading your comment, your issue is clearly about where you want to place your useState.
First of all, you should always place useState inside a component. But in your case, apparently what you're trying to achieve is that when you select a column, the other columns get deselected.
Therefore, you need to pass both selectedColumn and setSelectedColumn as props to your component, and create the useState on the parent component.
Assuming all your CustomHeader components share the same parent component, in which my example I'll call CustomHeadersParent, you should do something like this:
// added mock headers to have a working example
const headers = [
{
displayName: "Game Name",
column: {
colId: 1,
},
},
{
displayName: "School",
column: {
colId: 2,
},
},
];
const CustomHeadersParent = (props) => {
const [selectedColumn, setSelectedColumn] = useState(null);
return headers.map((h) => (
<CustomHeader
column={h.column}
displayName={h.displayName}
setSelectedColumn={setSelectedColumn}
selectedColumn={selectedColumn}
/>
));
};
const CustomHeader = (props) => {
const colId = props.column.colId;
return (
<div>
<div
style={{ float: "left", margin: "0 0 0 3px" }}
onClick={() => props.setSelectedColumn(props.column.colId)}
>
{props.displayName}
</div>
{props.selectedColumn === colId ? <FontAwesomeIcon icon={faPlus} /> : null}
</div>
);
};
const components = useMemo(() => {
return {
agColumnHeader: CustomHeader,
};
}, []);
You should use hooks inside your component
const CustomHeader = (props) => {
const colId = props.column.colId;
const [selectedColumn, setSelectedColumn] = useState(null);
console.log("selected Column In CustomHeader:", selectedColumn); // Should work
return (
<div>
<div style={{float: "left", margin: "0 0 0 3px"}} onClick={() => setSelectedColumn(props.column.colId)}>{props.displayName}</div>
{ selectedColumn === colId ? <FontAwesomeIcon icon={faPlus} /> : null}
</div>
)
}
hopefully you are having a great day!
I am making a website for uni group project,
Logic: I'll post data from database to localhost:3001, this is backend made using node.
Then I'll collect that information into 3 const (foodList, clothesList, techList) and then add value of these const into a variable called 'data'
data will be used to render cards to display on website.
Now the problem I am having is, in Logic.js, that when I made filter for categories, it is partially working, but new data is not rendered and displayed on website
Categories.js:
const catgs = [
{
"_id": 1,
"category": "Food"
},
{
"_id": 2,
"category": "Clothes"
},
{
"_id": 3,
"category": "Tech"
}
]
export {catgs};
CheckBox.js:
import React from 'react'
import { Checkbox, Collapse } from 'antd'
import { useState } from 'react'
function CheckBox(props) {
const { Panel } = Collapse;
const [Checked, setChecked] = useState([])
const catgs = [
{
"_id": 1,
"category": "Food"
},
{
"_id": 2,
"category": "Clothes"
},
{
"_id": 3,
"category": "Tech"
}
];
const handleToggle = (value) => {
const currentIndex = Checked.indexOf(value)
const newChecked = [...Checked]
if(currentIndex === -1){
newChecked.push(value)
}else{
newChecked.splice(currentIndex, 1)
}
setChecked(newChecked)
props.handleFilters(newChecked)
}
const renderCheckboxLists = () => catgs.map((value, index) => (
<React.Fragment key={index}>
<Checkbox
onChange = {() => handleToggle(value._id)}
type="checkbox"
checked= {Checked.indexOf(value._id) === -1 ? false : true }
/>
<span>{value.category}</span>
</React.Fragment>
))
return (
<div>
<Collapse defaultActiveKey={['0']} >
<Panel header="Categories" key="1">
{renderCheckboxLists()}
</Panel>
</Collapse>
</div>
)
}
export default CheckBox
Logic.js:
import React from 'react'
import {useState, useEffect} from "react"
import Axios from 'axios';
import { Col, Card, Row } from 'antd';
import 'antd/dist/antd.css';
import CheckBox from './CheckBox';
const Logic = () => {
const { Meta } = Card;
const [foodList, setfoodList] = useState([]);
const [clothesList, setclothesList] = useState([]);
const [techList, settechList] = useState([]);
let [data, setdata] = useState([])
const [Limit, setLimit] = useState(8);
const [Filters, setFilters] = useState({
category: []
})
useEffect(() => {
Axios.get('http://localhost:3001/api/get/food').then((response) => {
setfoodList(response.data);
});
}, []);
useEffect(() => {
Axios.get('http://localhost:3001/api/get/clothes').then((response) => {
setclothesList(response.data);
});
}, []);
useEffect(() => {
Axios.get('http://localhost:3001/api/get/tech').then((response) => {
settechList(response.data);
});
}, []);
data = [...foodList, ...clothesList, ...techList];
const onLoadMore = () => {
setLimit(Limit + 8);
}
const renderCards = data.slice(0, Limit).map((val, index) => {
return <Col key={index} lg={6} md={8} xs={24}>
<Card
hoverable={true}
cover = {<a href = {val.Link}>{val.Image}</a>}
>
<Meta
title={val.Company}
description={val.Description}
/>
</Card>
</Col>
})
const showFilteredResults = (filters) => {
}
const handleFilters = (filters, category) => {
const newFilters = { ...Filters }
newFilters[category] = filters
showFilteredResults(newFilters)
setFilters(newFilters)
}
return (
<div style = {{ width : '75%', margin: '3rem auto' }}>
<div style = {{ textAlign: 'center' }}>
<h2>Eco Friendly Companies </h2>
</div>
<CheckBox
handleFilters = {filters => handleFilters(filters, "category")}
/>
{data.length === 0 ?
<div style={{ display: 'flex', height: '300px', justifyContent: 'center', alignItems: 'center' }}>
<h2>No post yet...</h2>
</div> :
<div>
<Row gutter={[16, 16]}>
{renderCards}
</Row>
</div>
}
<br></br>
<div style = {{ display: 'flex', justifyContent: 'center'}}>
<button onClick={onLoadMore}>Load More</button>
</div>
</div>
)
}
export default Logic
I try to change the state of my cursor in a Next.js app with useContext but got this error :
TypeError: Cannot destructure 'Object(...)(...)' as it is undefined.
The state should change to isActive: true when hover the button
My ContextProvider :
import { createContext, useState } from "react";
export const CursorContext = createContext();
const CursorContextProvider = ({ children }) => {
const [cursor, setCursor] = useState({ active: false });
return (
<CursorContext.Provider value={[cursor, setCursor]}>
{children}
</CursorContext.Provider>
);
};
export default CursorContextProvider;
in the App :
import App from "next/app";
import Head from "next/head";
import Layout from "../components/Layout";
import CursorContextProvider from "../components/CursorContextProvider";
import Cursor from "../components/Cursor";
import { getCategories } from "../utils/api";
import "../styles/index.css";
import "../styles/style.scss";
const MyApp = ({ Component, pageProps, children }) => {
return (
<Layout categories={pageProps.categories}>
<Head>
<link rel="preconnect" href="https://app.snipcart.com" />
<link rel="preconnect" href="https://cdn.snipcart.com" />
<link
rel="stylesheet"
href="https://cdn.snipcart.com/themes/v3.0.16/default/snipcart.css"
/>
<script src="https://cdn.snipcart.com/themes/v3.0.16/default/snipcart.js" />
</Head>
<CursorContextProvider>
<Cursor />
{children}
</CursorContextProvider>
<Component {...pageProps} />
</Layout>
);
};
// getInitialProps disables automatic static optimization for pages that don't
// have getStaticProps. So [[...slug]] pages still get SSG.
// Hopefully we can replace this with getStaticProps once this issue is fixed:
// https://github.com/vercel/next.js/discussions/10949
MyApp.getInitialProps = async (ctx) => {
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(ctx);
// Fetch global site settings from Strapi
const categories = await getCategories();
// Pass the data to our page via props
return { ...appProps, pageProps: { categories, path: ctx.pathname } };
};
export default MyApp;
Here my Cursor component :
import React, { useContext } from "react";
import useMousePosition from "./useMousePosition";
import { CursorContext } from "./CursorContextProvider";
const Cursor = () => {
const { clientX, clientY } = useMousePosition();
const [cursor] = useContext(CursorContext);
return (
<div
style={{
position: "fixed",
top: 0,
bottom: 0,
left: 0,
right: 0,
zIndex: 9999,
pointerEvents: "none"
}}
>
<svg
width={50}
height={50}
viewBox="0 0 50 50"
style={{
position: "absolute",
left: clientX,
top: clientY,
transform: `translate(-50%, -50%) scale(${cursor.active ? 2.5 : 1})`,
stroke: cursor.active ? "black" : "white",
strokeWidth: 1,
fill: cursor.active ? "rgba(255,255,255,.5)" : "black",
transition: "transform .2s ease-in-out",
}}
>
<circle
cx="25"
cy="25"
r="8"
/>
</svg>
</div>
);
};
export default Cursor;
And a test Button component :
import { useContext, useCallback } from "react";
import { CursorContext } from "./CursorContextProvider";
const Button = () => {
const [, setCursor] = useContext(CursorContext);
const toggleCursor = useCallback(() => {
setCursor(({ active }) => ({ active: !active }));
});
return (
<button
type="button"
style={{ padding: "1rem" }}
onMouseEnter={toggleCursor}
onMouseLeave={toggleCursor}
>
HOVER ME
</button>
);
};
export default Button;
Do you see where I did something wrong to make the setState undefined ?
This is most likely due to your CursorContextProvider not wrapping the component where Button is being used. Try moving it to the top of your returned JSX in App.
const MyApp = ({ Component, pageProps, children }) => {
return (
<CursorContextProvider>
<Layout categories={pageProps.categories}>
//...
</Layout>
</CursorContextProvider>
);
};
I'm trying to implement a Trello with virtual lists and different sized items, using react-beautiful-dnd and react-virtuoso (virtual lists with automated calculation of item sizes).
react-virtuoso is not part of the examples of beautiful-react-dnd, I'm facing 2 issues:
I can't scroll while dragging an item
I get this error often: Invariant failed: Can only recollect Droppable client for Droppables that have a scroll container.
Here's a codesandbox
DroppableList.tsx
import * as React from "react";
import { useState } from "react";
import "./styles.css";
import { Virtuoso } from "react-virtuoso";
import {
Draggable,
DragDropContext,
Droppable,
DropResult,
ResponderProvided
} from "react-beautiful-dnd";
import { Item } from "./Item";
import { reorder } from "./App";
import { createItemList } from "./data";
import { ItemList, ItemType } from "./dtos";
const itemCount = 30;
export const VirtualDragDropList = () => {
const [itemList, setItemList] = useState<ItemList>(createItemList(itemCount));
const onDragEnd = (result: DropResult, provided: ResponderProvided) => {
// dropped outside the list
if (!result.destination) {
return;
}
const items = reorder(
itemList,
result.source.index,
result.destination.index
);
setItemList(items);
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable
droppableId="droppable"
mode="virtual"
renderClone={(provided, snapshot, rubric) => {
// console.log("provided", provided);
// console.log("snapshot", snapshot);
// console.log("rubric", rubric);
return (
<Item
itemData={itemList[(rubric as any).source.index]}
provided={provided}
index={(rubric as any).source.index} // typing seems wrong, hence the any.
/>
);
}}
>
{droppableProvided => (
<div ref={droppableProvided.innerRef}>
<Virtuoso
style={{ width: "300px", height: "400px" }}
totalCount={itemCount}
// item={index => <Item itemData={itemList[index]} />}
item={index => <Row itemData={itemList[index]} index={index} />}
/>
</div>
)}
</Droppable>
</DragDropContext>
);
};
const Row = React.memo((args: { itemData: ItemType; index: number }) => {
const { itemData, index } = args;
return (
<Draggable draggableId={itemData.id} index={index} key={itemData.id}>
{(provided, snapshot) => (
<Item itemData={itemData} index={index} provided={provided} />
)}
</Draggable>
);
});
Item.tsx
import * as React from "react";
import { ItemType } from "./dtos";
export const Item: React.FC<{
index?: number;
itemData: ItemType | undefined;
provided?: any;
}> = props => {
const height = (props.itemData ? props.itemData.height : 10) * 3;
const style = {
margin: ".3rem",
padding: ".3rem",
display: "flex",
border: "1px solid lightgrey",
height: `${height}px`
};
return (
<div
ref={props.provided && props.provided.innerRef}
{...props.provided && props.provided.draggableProps}
{...props.provided && props.provided.dragHandleProps}
style={{ ...props.provided.draggableProps.style, ...style }}
>
{props.itemData && props.itemData.text}
</div>
);
};
data.ts
import { ItemList } from "./dtos";
export const createItemList = (itemCount: number): ItemList => {
const itemList: ItemList = [];
for (let i = 0; i < itemCount; i++) {
itemList.push({
id: i.toString(),
text: `Item ${i}`,
height: Math.random() * 20
});
}
return itemList;
}
remove ref={droppableProvided.innerRef} from div and add
scrollerRef={droppableProvided.innerRef} to virtuoso