Reactjs Child Component - reactjs

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

React <details> - have only one open at a time

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>
);
};

How to pass the list "key" value as a props in nested component?

(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.

React child not re-rendered when parents props change

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>
);
}

onMouseEnter active on all mapped elements

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>
)
}

How can I remove a item class from a child's item in css and react?

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.

Resources