HTML elements not rendering on Gatsby website - javascript

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.

Related

How to add active on Click after map in child component? React

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

Each child in a list should have a unique "key" prop. I made it, but still see this error

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

React Toggle open and close using onclick on map function

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.

React Toggle Hook State

I tried to toggle individual item but unfortunately whenever I try to toggle an item the other item gets affected. Here is my code:
const FAQ = () => {
const [open, setOpen] = useState(false);
const [data, setData] = useState(faqData);
return (
<FAQSection>
<FAQTitle>Frequently Asked Questions</FAQTitle>
<Questions>
<QuestionItemDetail>
{data.map((item) => {
const { id, question, answer } = item;
return (
<div key={id}>
<QuestionItem onClick={() => setOpen(!open)}>
<QuestionItemTitle>{question}</QuestionItemTitle>
{open ? <Close /> : <Add />}
</QuestionItem>
<ReadQuestion>
{open && (
<ReadQuestionDetail>
<ReadQuestionDesc>{answer}</ReadQuestionDesc>
</ReadQuestionDetail>
)}
</ReadQuestion>
</div>
);
})}
</QuestionItemDetail>
</Questions>
</FAQSection>
);
};
What might be wrong with this because I ensured the dummy data has a unique ID.
Because you use a boolean to control all open/close. You need to use index/id to control this.
const [open, setOpen] = useState(null);
...
onClick={() => setOpen(preOpen => preOpen === id ? null : id)}
...
{open === id && (<ReadQuestionDetail>...</ReadQuestionDetail>)}
Your open state is used for all of the items in your data array, which is why it affects all of the items when toggled.
I recommend:
putting all of the data item html/jsx inside a new component.
Inside this new component, create an open state like so:
const MyItemComponent = (id, question, answer) => {
const [open, setOpen] = useState(false);
return (
<div key={id}>
<QuestionItem onClick={() => setOpen(!open)}>
<QuestionItemTitle>{question}</QuestionItemTitle>
{open ? <Close /> : <Add />}
</QuestionItem>
<ReadQuestion>
{open && (
<ReadQuestionDetail>
<ReadQuestionDesc>{answer}</ReadQuestionDesc>
</ReadQuestionDetail>
)}
</ReadQuestion>
</div>
);
}
const FAQ = () => {
const [data, setData] = useState(faqData);
return (
<FAQSection>
<FAQTitle>Frequently Asked Questions</FAQTitle>
<Questions>
<QuestionItemDetail>
{data.map((item) => {
const { id, question, answer } = item;
return (
<MyItemComponent id={id} question={question} answer={answer} />
);
})}
</QuestionItemDetail>
</Questions>
</FAQSection>
);
};
This will give you an individual open state for each item.

Sharing component states with useState() for active link in Nav

I'm trying to learn react and fairly new to the framework. I am trying to create a simple navbar component wih material-ui that is responsive (will show all links on medium devices and up, and open a side drawer on small devices). I have most of it setup to my liking, however, the issue I am currently having, is getting and setting the active link according to the page I am on.
It seems to works correctly on the medium devices and up, but when transitioning to a smaller device, the link is not updated correctly, as it will keep the active link from the medium screen set, while updating the side drawer active link.
Navbar.js
const Navbar = () => {
const classes = useStyles();
const pathname = window.location.pathname;
const path = pathname === '' ? '' : pathname.substr(1);
const [selectedItem, setSelectedItem] = useState(path);
const handleItemClick = (event, selected) => {
setSelectedItem(selected);
console.log(selectedItem);
};
return (
<>
<HideNavOnScroll>
<AppBar position="fixed">
<Toolbar component="nav" className={classes.navbar}>
<Container maxWidth="lg" className={classes.navbarDisplayFlex}>
<List>
<ListItem
button
component={RouterLink}
to="/"
selected={selectedItem === ''}
onClick={event => handleItemClick(event, '')}
>
<ListItemText className={classes.item} primary="Home" />
</ListItem>
</List>
<Hidden smDown>
<List
component="nav"
aria-labelledby="main navigation"
className={classes.navListDisplayFlex}
>
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === 'account/login'}
onClick={event => handleItemClick(event, 'account/login')}
>
<ListItemText className={classes.item} primary="Login" />
</ListItem>
<ListItem
button
component={RouterLink}
to="/account/register"
selected={selectedItem === 'account/register'}
onClick={event => handleItemClick(event, 'account/register')}
>
<ListItemText className={classes.item} primary="Register" />
</ListItem>
</List>
</Hidden>
<Hidden mdUp>
<SideDrawer />
</Hidden>
</Container>
</Toolbar>
</AppBar>
</HideNavOnScroll>
<Toolbar id="scroll-to-top-anchor" />
<ScrollToTop>
<Fab aria-label="Scroll back to top">
<NavigationIcon />
</Fab>
</ScrollToTop>
</>
)
}
SideDrawer.js
const SideDrawer = () => {
const classes = useStyles();
const [state, setState] = useState({ right: false });
const pathname = window.location.pathname;
const path = pathname === "" ? "" : pathname.substr(1);
const [selectedItem, setSelectedItem] = useState(path);
const handleItemClick = (event, selected) => {
setSelectedItem(selected);
console.log(selectedItem);
};
const toggleDrawer = (anchor, open) => (event) => {
if (
event &&
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setState({ ...state, [anchor]: open });
};
const drawerList = (anchor) => (
<div
className={classes.list}
role="presentation"
onClick={toggleDrawer(anchor, false)}
onKeyDown={toggleDrawer(anchor, false)}
>
<List component="nav">
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === "account/login"}
onClick={(event) => handleItemClick(event, "account/login")}
>
<ListItemText className={classes.item} primary="Login" />
</ListItem>
<ListItem
button
component={RouterLink}
to="/account/login"
selected={selectedItem === "account/register"}
onClick={(event) => handleItemClick(event, "account/register")}
>
<ListItemText className={classes.item} primary="Register" />
</ListItem>
</List>
</div>
);
return (
<React.Fragment>
<IconButton
edge="start"
aria-label="Menu"
onClick={toggleDrawer("right", true)}
>
<Menu fontSize="large" style={{ color: "white" }} />
</IconButton>
<Drawer
anchor="right"
open={state.right}
onClose={toggleDrawer("right", false)}
>
{drawerList("right")}
</Drawer>
</React.Fragment>
);
};
Code Sandbox - https://codesandbox.io/s/async-water-yx90j
I came across this question on SO: Is it possible to share states between components using the useState() hook in React?, which suggests that I need to lift the state up to a common ancestor component, but I don't quite understand how to apply this in my situation.
I would suggest to put aside for a moment your code and do a playground for this lifting state comprehension. Lifting state is the basic strategy to share state between unrelated components. Basically at some common ancestor is where the state and setState will live. there you can pass down as props to its children:
const Parent = () => {
const [name, setName] = useState('joe')
return (
<>
<h1>Parent Component</h1>
<p>Child Name is {name}</p>
<FirstChild name={name} setName={setName} />
<SecondChild name={name} setName={setName} />
</>
)
}
const FirstChild = ({name, setName}) => {
return (
<>
<h2>First Child Component</h2>
<p>Are you sure child is {name}?</p>
<button onClick={() => setName('Mary')}>My Name is Mary</button>
</>
)
}
const SecondChild = ({name, setName}) => {
return (
<>
<h2>Second Child Component</h2>
<p>Are you sure child is {name}?</p>
<button onClick={() => setName('Joe')}>My Name is Joe</button>
</>
)
}
As you can see, there is one state only, one source of truth. State is located at Parent and it passes down to its children. Now, sometimes it can be troublesome if you need your state to be located at some far GreatGrandParent. You would have to pass down each child until get there, which is annoying. if you found yourself in this situation you can use React Context API. And, for most complicated state management, there are solutions like redux.

Categories

Resources