I have a doubt with hierarchy in react.
My Main.js has 2 articles as following:
<article className="angel__flow">
{steps.map((step, index) => {
if (procedures[step]){
return (
<Flow
key={`${index}-${step}`}
index={index}
opened={currentStepIndex === index}
procedure={procedures[step]}
/>
);
} else return "";
})}
</article>
<article className="angel__steps">
{steps.map((step, index) => {
if (procedures[step]){
return (
<Steps
key={`${index}-${step}`}
index={index}
opened={currentStepIndex === index}
procedure={procedures[step]}
commandSender={sendViewerMessage}
/>
);
} else return "";
})}
</article>
Each article has a map, and for each item, it calls a function. The first one calls the Flow function and the second one calls the Steps function.
My Flow function is the following:
function Flow({ index, opened, procedure }) {
const { t, i18n } = useTranslation();
const [show, setShow] = useState(false);
useEffect(()=>{
setShow(opened)
},[opened])
return (
<a onClick={() => setShow(!show)} className={`angel__flow__button ${show ? "active" : ""}`}>
{t(procedure.title[i18n.language])}
<span class="angel__flow__button__number">{index+1}</span>
</a>
);
}
And my Steps function is the following:
function Steps({ index, opened, procedure, commandSender }) {
const { i18n } = useTranslation();
const [selected, setSelected] = useState([]);
function clickHandler(command, index, key, procedure) {
if (!isSelected(key)) commandSender(`${command}|${index}|${procedure.id}|${key}`)
if (isSelected(key)) setSelected(selected.filter(s => s !== key))
else setSelected([...selected, (key)])
}
function isSelected(key) {
return selected.includes(key);
}
return (
<>
{ opened && (
<>
{procedure.guide &&
map(procedure.guide, (value, key) => (
<a
key={key}
className={`angel__steps__button blue ${isSelected(key) ? "active" : ""}`}
onClick={() => clickHandler('GUIDE', index, key, procedure)}
>
{value[i18n.language]}
</a>
))
}
<hr />
{procedure.error &&
map(procedure.error, (value, key) => (
<a
key={key}
className={`angel__steps__button red ${isSelected(key) ? "active" : ""}`}
onClick={() => clickHandler('ERROR', index, key, procedure)}
>
{value[i18n.language]}
</a>
))
}
{procedure.success &&
map(procedure.success, (value, key) => (
<a
key={key}
className={`angel__steps__button green ${isSelected(key) ? "active" : ""}`}
onClick={() => clickHandler('SUCCESS', index, key, procedure)}
>
{value[i18n.language]}
</a>
))
}
</>
)}
</>
);
}
What I have to do is: When I click an A tag in the Flow function, it has to open the Steps of that tag.
So, I dont know what I have to do. Maybe send back to my Main function a flag that says that I clicked that tag, so, my Steps function will have it's tag opened.
Can you guys help me with this?
pass in a click handler to Flow within Main.js:
<Flow setCurrentStepHandler={(i) => this.setState({currentStepIndex: this.state.currentStepIndex === i ? null : i})}/>
And in Flow:
<a onClick={() => {
setShow(!show)
setCurrentStepHandler(index)
}}
That way when currentStepIndex is updated, it will pass as a prop to <Steps .../>
Related
I have a component with several elements. I'm trying to figure out how to update the code with hooks so that only one element will be open at a time - when a element is open, the other's should be closed. This is the code:
const HowItWorks = ({ content, libraries }) => {
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
const [open, setOpen] = useState(false);
const onToggle = () => {
setOpen(!open);
};
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${open ? "open" : "closed"}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{open ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};
Instead of having the open state be a boolean, make it be the ID of the element that is open. Then you can have a function that returns if the element is open by comparing the state with the ID.
const HowItWorks = ({ content, libraries }) => {
const [open, setOpen] = useState(0); //Use the element ID to check which one is open
const onToggle = (id) => {
setOpen(id);
};
const isOpen = (id) => {
return id === open ? "open" : "closed";
}
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${isOpen(i)}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{!!isOpen(i) ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};
(I apologize for the ugly code in advance -- currently refactoring)
I'm making a Table of content where the nested content appear when I click on its parent component.
For my logic, I need to pass the value of the list key to its children but I keep receiving an undefined error or nothing at all. I tried to pass the value like this: key={node2.objectId} and keyId={node2.objectId}
I read the specifications on how to pass the key value as a prop here and here
Yet, nothing works.
Here's my code:
import React from "react";
const TocContent = (props) => {
return (
<div className="">
{props.TOC.map((header) => (
<ul
key={header.objectId}
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) =>
props.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.secNum,
header.objectId,
header.id,
e.stopPropagation(),
)
}
className="TOC TOCsection"
>
{header._id}
{props.headerIndex === header.objectId
? props.headers2.map((node2) => (
<HeaderList
key={node2.objectId}
header={node2}
props={props}
keyId={node2.objectId}
>
{console.log(props.keyId)}
//--problem is here-- {props.headerIndex2 === props.keyId
? props.headers3.map((node3) => (
<HeaderList
key={node3.objectId}
header={node3}
props={props}
>
{props.headerIndex3 === node3.objectId
? props.headers4.map((node4) => (
<HeaderList
header={node4}
key={node4.objectId}
props={props}
/>
))
: null}
</HeaderList>
))
: null}
</HeaderList>
))
: null}
</ul>
))}
</div>
);
};
const HeaderList = ({ header, props }) => {
return (
<ul
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) =>
props.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.secNum,
header.objectId,
header.id,
e.stopPropagation(),
)
}
>
{header._id}
</ul>
);
};
export default TocContent;
I finally resorted to change the structure a bit. Instead of the code above, I opted to render the HeaderList component directly in its own component (as a child of itself). This way, I'm able to read header.objectId and make the code shorter.
Here's the new code:
import React from "react";
const TocContent = (props) => {
return (
<div className="">
{props.TOC.map((header) => (
<HeaderList key={header.objectId} header={header} props={props} />
))}
</div>
);
};
const HeaderList = ({ header, props }) => {
return (
<ul
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) =>
props.handleHeaderClick(
header.level,
header.treepath,
header.containsLaw,
header.sections,
header.secNum,
header.objectId,
header.id,
e.stopPropagation(),
)
}
>
{header._id}
{/* // 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} />
))}
</ul>
);
};
export default TocContent;
I understand this is maybe not the cleanest code, but an improvement nonetheless. If someone wants to propose something better, it will be much appreciated.
I'm having some issues with child re-rendering, I pass methods to children to see if a button should be displayed or not but when the state of the parent changes, the children are not re-rendered.
I tried with the disabled attribute for the button but didn't work either.
Here's my code (I removed unnecessary part):
function Cards(props) {
const isCardInDeck = (translationKey) => {
return props.deck.some(
(card) => !!card && card.translationKey === translationKey
);
};
const addToDeck = (card) => {
if (!isCardInDeck(card.translationKey) && !!card) {
props.deck.push(card);
}
};
const removeFromDeck = (card) => {
if (isCardInDeck(card.translationKey) && !!card) {
var index = props.deck.findIndex(
(c) => c.translationKey === card.translationKey
);
props.deck.splice(index, 1);
}
};
return (
<div className="cardsContent">
<div className="cards">
{cardList.length > 0 ? (
cardList.map((item, index) => {
return (
<Card key={index} card={item} addToDeckDisabled={isCardInDeck(item.translationKey)} addToDeckClick={addToDeck} removeFromDeckClick={removeFromDeck} />
);
})
) : (
<span>
<FormattedMessage id="app.cards.label.no.card.found" defaultMessage="No card found with filter."/>
</span>
)}
</div>
</div>
);
}
function Card(props) {
const toggleShowDescription = () => {
if (!showDescription) {
setShowDescription(!showDescription);
}
};
return (
<div onClick={toggleShowDescription} onBlur={toggleShowDescription} >
<img src={"../images/cards/" + props.card.image} alt={props.card.image + " not found"} />
{showDescription ? (
<div className="customCardDetail">
<div className="cardName"></div>
<div className="cardType">
{props.addToDeckDisabled ? (
<Button onClick={() => { props.removeFromDeckClick(props.card);}} startIcon={<RemoveIcon />}>
Remove from deck
</Button>
) : (
<Button onClick={() => { props.addToDeckClick(props.card); }} startIcon={<AddIcon />}>
Add to deck
</Button>
)}
</div>
<div className="cardDescription">
<span>
<FormattedMessage id={props.card.description} defaultMessage={props.card.description} />
</span>
</div>
</div>
) : (
""
)}
</div>
);
}
You code does not update state. Cards mutates the props that it is receiving.
To use state in a functional component in React you should use the useState hook.
Cards would then look something like this:
function Cards(props) {
const [deck, setDeck] = useState(props.initialDeck)
const isCardInDeck = (translationKey) => {
return deck.some(
(card) => !!card && card.translationKey === translationKey
);
};
const addToDeck = (card) => {
if (!isCardInDeck(card.translationKey) && !!card) {
setDeck([...deck, card])
}
};
const removeFromDeck = (card) => {
if (isCardInDeck(card.translationKey) && !!card) {
setDeck(deck.filter(deckItem => deckItem.translationKey !== card.translationKey))
}
};
return (
<div className="cardsContent">
<div className="cards">
{cardList.length > 0 ? (
cardList.map((item, index) => {
return (
<Card key={index} card={item} addToDeckDisabled={isCardInDeck(item.translationKey)} addToDeckClick={addToDeck} removeFromDeckClick={removeFromDeck} />
);
})
) : (
<span>
<FormattedMessage id="app.cards.label.no.card.found" defaultMessage="No card found with filter."/>
</span>
)}
</div>
</div>
);
}
I have a simple component in which I map through an array of objects and display a li element for each object:
const [isHover, setHover] = useState(false);
return (
<ResultList>
{ props.movieList.length === 0 ? (<NoResults>No results were found...</NoResults>) : (null)}
{ props.movieList.map(movie => {
return (
<React.Fragment>
<li
key={movie.id}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}>
<p>
<span>{movie.original_title}</span>
</p>
{isHover && <AddMovieToDashboard />}
</li>
)
})}
</ResultList>
)
When I onMouseEnter on a li element I want to display another component AddMovieToDashboard. I've set this up by using the useState hook but as you might expect; this causes each li in the ul to display the AddMovieToDashboard component, and not just the one which is being hovered. I should probably use the unique key value from the li element somewhere but I can't find a good example how to do it.
How about moving the items to a separate component so they can have their own state:
function ResultListItem(props) {
const [isHover, setHover] = useState(false);
return (
<li
key={props.movie.id}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<p>
<span>{props.movie.original_title}</span>
</p>
{isHover && <AddMovieToDashboard />}
</li>
)
}
function Component(props) {
return (
<ResultList>
{ props.movieList.length === 0 ? (<NoResults>No results were found...</NoResults>) : (null) }
{ props.movieList.map(movie => {
return (
<ResultListItem movie={movie} />
)
})}
</ResultList>
)
}
I want to delete a class or its effect if the parent element has no thanks element, or be able to choose to refer to its reference so that it can be selected, but I can't find a solution on the Internet and some of my ideas have already been used and I need some suggestion in that situation do. The situation is as follows: I have a menu in which the submenu is nested and the whole is guided by a recursive function that calls itself going down levels if taxonomies in the menu have children. And now I want to remove the effect on items that don't have children, which means that the menu can be expanded with the arrow.
A recursive function that calls itself creating categories (parents) and subcategories (children).
function NavBarLink({ ...props }) {
const [classOpener, setClassOpener] = useState(false);
const pathToCategories = `/categories/${props.codename}`;
const dispatch = useDispatch();
function handleTaxonomies(codename) {
dispatch(catchCodeNameTaxonomies(codename));
setClassOpener(!classOpener);
return codename;
}
return (
<span
className={(classOpener && "opener active") || (!classOpener && "opener")}
>
<Link
to={ pathToCategories }
onClick={() => handleTaxonomies(props.codename)}
>
{props.name}
</Link>
</span>
);
}
function NavBarItem({ ...props }) {
function generateLink() {
return (
<NavBarLink
name={props.name}
codename={props.codename}
/>
);
}
function generateSubmenu() {
return <Menu items={props.terms} />;
}
function generateContent() {
const content = [generateLink()];
if (props.terms) {
content.push(generateSubmenu());
}
return content;
}
const content = generateContent();
return (
<li> {content}</li>
);
}
export default function Menu({ ...props }) {
function generateItem(item) {
return (
<NavBarItem
name={item.name}
terms={item.terms}
codename={item.codename}
onClick={props.handleId}
/>
);
}
const items = props.items.map(generateItem);
return <ul className="menu-ul-child">{items}</ul>;
}
EDIT::
My new solution with purpose:
function NavBarLink({ ...props }) {
const [classOpener, setClassOpener] = useState(false);
//add two icons
const arrow = <i class="fas fa-chevron-up"></i>;
const arrowDown = <i class="fas fa-chevron-down"></i>;
function handleTaxonomies() {
setClassOpener(!classOpener);
}
return (
//your purpose
<span
className={`opener ${classOpener ? "active" : ""}`}
>
<Link
className="d-flex justify-content-between"
to={pathToCategories}
onClick={() => handleTaxonomies(props.codename)}
>
<span>
{props.name}
</span>
//add two icons depending on the state
<span>
{props.showArrow ? (classOpener ? arrow : arrowDown) : ""}
</span>
</Link>
</span>
);
}
function NavBarItem({ ...props }) {
function generateLink() {
return (
<NavBarLink
name={props.name}
codename={props.codename}
//your purpose
showArrow={props.terms && props.terms.length > 0}
/>
);
}
}
The easiest way to do this is to provide a new property like showArrowto NavbarLink. From your code snippets I think the showArrow can be set by item.terms. So you can do something like this:
in NavbarItem
function generateLink() {
return (
<NavBarLink
name={props.name}
codename={props.codename}
showArrow={props.terms && props.terms.length>0}
/>
);
}
Then extract the CSS part for drawing the arrow to a special class and check for showArrow in NavBarLink:
<span
className={`opener ${classOpener ? "active" : ""} ${props.showArrow ? "arrow" : ""}`}
>
....
I've changed the className concatination to string literals. For me this is much more readable and it's easier to do all permutations.