I need to dynamically change the class to "active". I do this by checking indexes. The problem is that the active class changes IMMEDIATELY in all displayed li, and not in one particular one. This only happens when the logic is in the parent component, if you move the useState and onTypeActive to the child component everything works fine.
Parent component
const [activeSyze, setActiveSyze] = useState(0);
const onSyzeActive = (index) => {
setActiveSyze(index);
};
return (
<>
{pizzaJson.map((item, index) => (
<PizzaBlock
key={item.id}
onSyzeActive={(index) => onSyzeActive(index)}
activeSyze={activeSyze}
{...item}
item={item}
indexPizza={index}
/>
))}
</>.
Child comp
return (
<ul>
{sizes.map((size, index) => (
<li
key={index}
onClick={() => onSyzeActive(index)}
className={activeSyze === index ? "active" : ""}
>
{size} см.
</li>
))}
</ul>
)
Related
I know about key prop, so i made it in listitem component
const ListItem = ({item}) => {
const {result, time, id} = item;
return(
<li key={id} className='list__item'>
<span className='item__result'>{result} cps</span>
<span className='item__date'>{time}</span>
<button className='item__delete'>delete</button>
</li>
)}
And here is component, where I use it
const Leadboard = () => {
const [data, setData] = useState([{result:'5,63', time:'08.06.2022', id: (new Date()).toString(16)}, {result:'5,63', time:'08.06.2022', id: +(new Date() - 1)}, {result:'5,63', time:'08.06.2022', id: +(new Date() - 2)}]);
let elements=data.map(item => {
return (
<>
<ListItem item={item} />
</>
)
});
return(
<div className='app-leadboard'>
<span className='app-leadboard__title'>Your's best results:</span>
<ol className='app-leadboard__list' type='1'>
{elements}
</ol>
</div>
)}
But after render I still see "key prop" error
I spent so much time on this, but can't understand what's wrong. So smb, help pls with it
You’ve got the wrong list. It’s the <ListItem> components that need the key. (And you can get rid of the react fragments around them because they are pointless).
React first accesses the empty bracket (<> </> ) before accessing the key attribute in your child component.
So you need to either
Make use of the empty brackets and pass the key attribute to it
// Use React.Fragment
let elements=data.map(item => { return
(
<React.Fragment key={item.id}>
<ListItem item={item} />
</React.Fragment>
)
});
and remove the key in the child (ListItem) component
ListItem.js
<li
/* Remove this
key={id}
*/
className='list__item'>
OR
Get rid of the empty brackets (<> </>) and reference the child's component directly.
let elements=data.map(item => {
return (<ListItem item={item} />)
});
and leave the key attribute in the child component
More on React Fragment. React Official Docs
I have a map function displaying items:
{data.map((item, index) => ())}
Within this function I would like to have an onClick that conditionally displays a loading state when loading is true.
const [loading, setLoading] = useState(false)
{data.map((item, index) => (
<span
onClick={() =>
handleDelete()}>
{loading ? (
<LoadingSpinner />
) : (
<TrashIcon />)}
</span>
))}
When I do this it causes all items in the list to display the loading state, not just the item clicked on.
Is there a way I can achieve this using the index?
All your map items are listening to a single boolean state value, instead, you can keep track of the index of the clicked item, or if you want to be able to click and show the loading state for multiple items you can use an array or Set.
Below is an approach using Set
const [loadingIndices, setLoadingIndices] = useState(new Set());
{data.map((item, index) => (
<span
onClick={() =>
handleDelete(index)}>
{loadingIndices.has(index) ? (
<LoadingSpinner />
) : (
<TrashIcon />)}
</span>
))}
Now in your handleDelete function, you can add index of the clicked element to the set in state.
handleDelete = (selectedIndex) => {
setLoadingIndices((prev) => new Set([...prev, selectedIndex]));
// ...
// ...
// And to remove the element from loading state
setLoadingIndices((prev) => {
const updated = new Set(prev);
updated.delete(selectedIndex);
return updated;
});
};
You should move your state in to the items instead of the parent. Updating state inside the parent component will cause all children to re-render.
const childComponent = (props) => { // you can pass the main loading state as a starting point
const [loading,setLoading] = useState(props.loading) // initial loader state
if(loading) return <LoadingSpinner />
return (
<button onClick={()=> setLoading(prev => !prev)} />
)}
you can then mutate this to display different elements using if statements like above.
If you don't mind the re-render of children, You can use this index logic.
const [loading, setLoading] = useState(null);
const handleDelete = (index) => {
setLoading(index);
}
{data.map((item, index) => (
<span
onClick={() =>
handleDelete(index)}>
{loading === index ? (
<LoadingSpinner />
) : (
<TrashIcon />)}
</span>
))}
I have an onClick in map function, if I click onClick it will change state of all map item not that item which I clicked. I am using useState hook.
const [open, setOpen] = useState(true);
{
FilterAvailable.map((item, id) => {
return (
<li className="filterItem">
<div
className="filterBtn flex align-center justify-between"
onClick={() => setOpen(!open)}
>
<h2>{item.catogery}</h2>
<div>
{open ? (
<IoIosArrowDown className="downArrow" />
) : (
<IoIosArrowUp className="downArrow" />
)}
</div>
</div>
{item.options.map(option => {
return (
<>
{open && (
<ul className="filterOption">
<li className="filterOptionitem">
<button>{option}</button>
</li>
</ul>
)}
{/* <div className="hrLine"></div> */}
</>
);
})}
</li>
);
});
}
I want to open only those item option which click.
Your state is in the wrong place, you need to break the component and have a open state per filterableItem
const Filters = () => {
return FilterAvailable.map((item, id) => {
return <FilterItem item={item} />;
});
};
const FilterItem = ({ item }) => {
const [open, setOpen] = useState(true);
return (
<li className="filterItem">
<div
className="filterBtn flex align-center justify-between"
onClick={() => setOpen(!open)}
>
<h2>{item.catogery}</h2>
<div>
{open ? (
<IoIosArrowDown className="downArrow" />
) : (
<IoIosArrowUp className="downArrow" />
)}
</div>
</div>
{item.options.map(option => {
return (
<>
{open && (
<ul className="filterOption">
<li className="filterOptionitem">
<button>{option}</button>
</li>
</ul>
)}
</>
);
})}
</li>
);
};
Put the index inside state on onclick , inside map check if state index equals to map index and you are done
Consider moving the mapped elements to their own component with props - then you create and set your open and closed state there.
What I am doing:
Creating my first website in Gatsby.js. Trying to render an HTML element "onClick" of a navigation link. When someone clicks one of the links, I want it to show a dropdown menu.
export function DropDownMenu(props) {
return (
<p>{props}</p>
)
}
const Header = () => {
// const [open, setOpen] = useState(false);
return (
<Nav>
<StyledLinkBox to="/"><Logo/></StyledLinkBox>
<Bars />
<NavMenu>
{headerMenuData.map((item, index, dropdown) => (
<NavLink to={item.link} key={index} onClick={() => {
item.dropdown.map((item, index) => (
<DropDownMenu props={item} key={index}/>
))}
}>
{item.title}
</NavLink>
))}
<StyledButton>Early Access</StyledButton>
</NavMenu>
</Nav>
)
}
Notes:
I have tried to use useState to call another function here, but that doesn't seem to work, as then you have to click twice for anything to happen.
If you replace <DropDownMenu...> within the map function with a console.log, it will print out all the elements that need to appear, which is strange.
So if it can do that, and the mapping function is working correctly, why can't I see <p>{props}</p> for every item?
Use:
export function DropDownMenu({props}) {
return (
<p>{props}</p> /* same as props.props */
)
}
const Header = () => {
// const [open, setOpen] = useState(false);
return (
<Nav>
<StyledLinkBox to="/"><Logo/></StyledLinkBox>
<Bars />
<NavMenu>
{headerMenuData.map((item, index, dropdown) => (
<NavLink to={item.link} key={index} onClick={() => {
item.dropdown.map((item, index) => (
<DropDownMenu props={item} key={index}/>
))}
}>
{item.title}
</NavLink>
))}
<StyledButton>Early Access</StyledButton>
</NavMenu>
</Nav>
)
}
You are sending item as props and the component is also, by default, is getting props (if you send it), so you need to restructure it or access the data by props.props.
You can do:
<DropDownMenu item={item} key={index}/>
And:
export function DropDownMenu({item}) {
return (
<p>{item}</p> /* same as props.item */
)
}
For a more succinct approach.
I coded a table of content using nested components. Each component is a list of headers.
I want to style each component with an indentation effect (margin-left: "20px") to differentiate each level of nesting.
Example:
<Parent>
-->indent <Child/>
-->indent <Child2/>
-->indent (etc.)
</Parent>
Any idea of how to do it dynamically?
Here's my code:
import React from "react";
const TocContent = ({ props }) => {
return (
<div className="TOC">
{props.TOC.map((header) => (
<HeaderList key={header.objectId} header={header} props={props} />
))}
</div>
);
};
const HeaderList = ({ header, props }) => {
return (
<div>
<li
onMouseDown={(e) => e.stopPropagation()}
className="listing"
style={{}}
onClick={(e) =>
props.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.secNum,
header.objectId,
header.id,
e.stopPropagation(),
)
}
>
{header._id}
</li>
{/* // if savedIndex === CurrentParent Index */}
{props.headerIndex === header.objectId &&
props.headers2.map((node2) => (
<HeaderList key={node2.objectId} header={node2} props={props} />
))}
{props.headerIndex2 === header.objectId &&
props.headers3.map((node3) => (
<HeaderList key={node3.objectId} header={node3} props={props} />
))}
{props.headerIndex3 === header.objectId &&
props.headers4.map((node4) => (
<HeaderList header={node4} key={node4.objectId} props={props} />
))}
</div>
);
};
export default TocContent;
Put the margin (or padding) on the element that contains both the HeaderList's main content and the sub-HeaderList components (instead of just the main content as you have now). Specifically this would be the div that wraps all other returned content in the HeaderList component. The margins will stack up and each nested header list will be more indented than the parent.
For example (just HTML & CSS):
.header-list {
margin-left: 20px;
}
<div class="header-list">
First Element
<div class="header-list">
Second Element
<div class="header-list">
Third Element
</div>
</div>
</div>