I'm fairly new to React and I'm working on a website for a friend that uses a lot of react features. One thing this website needs is a navbar where every item in the navbar has a dropdown selection of additional nav items. I'm able to both render pages conditionally as independent nav items and create the hover dropdown on each nav item, but my issue comes into merging them together. I've tried a few things such as mapping through props twice, creating a large object where the nav item is a name and the dropdown items are subnames, but neither of those worked.
Here is the code I'm using:
function Nav(props) {
const [navItemList, setNavItemList] = useState([
{name: 'About', dropdownItem1: 'About Me', dropdownItem2: 'About Tampa Bay', id: 1},
]);
const { pages = [], setCurrentPage, currentPage } = props;
return (
<header className="flex-row">
<h1 class="name-tag">
<img src={"../../assets/Logo1.png"} />
</h1>
<nav>
<NavItems items={navItemList} />
<ul className="flex-row nav-list">
{pages.map(navItem => (
<li className={`li-spacing text-format ${currentPage.name === navItem.name && 'navActive'}`} key={navItem.id}>
<span onClick={() => { setCurrentPage(navItem) }}>{navItem.name}</span>
</li>
))}
</ul>
</nav>
</header>
)
}
function App() {
const [pages] = useState([
{
id: 1,
name: 'Home'
},
{
id: 2,
name: 'About Me'
},
{
id: 3,
name: 'About Tampa Bay'
},
])
const [currentPage, setCurrentPage] = useState(pages[0])
return (
<div>
<Nav
pages={pages}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
></Nav>
<main>
<Pages currentPage={currentPage}></Pages>
</main>
</div>
);
}
function NavItems(props) {
const items = props.items
return (
<ul className=" flex-row nav-list">
{/* map through the props so each navitem receives unique information */}
{items.map((navItem) => (
<div className="dropdown" key={navItem.id}>
<li className="nav-list-item">{ navItem.name }</li>
<div className="dropdown-item">
<p>{ navItem.dropdownItem1 }</p>
<p>{ navItem.dropdownItem2 }</p>
</div>
</div>
)) }
</ul>
)
}
export default NavItems;
Something like this maybe? This might need to be adjusted a bit to fit your styling needs.
const pages = {
home: {
name: 'Home',
subPages: {},
},
about: {
name: 'About',
subPages: {
aboutMe: {
name: 'About Me',
},
aboutTampaBay: {
name: 'About Tampa Bay',
},
},
},
}
function App() {
const [currentPageKey, setCurrentPageKey] = useState('home')
return (
<div>
<Nav pages={pages} currentPage={currentPage} setCurrentPageKey={setCurrentPageKey} />
<main>
<Pages currentPage={pages[currentPageKey]} />
</main>
</div>
)
}
function Nav(props) {
const { setCurrentPageKey, currentPage, pages } = props
return (
<header className="flex-row">
<h1 class="name-tag">
<img src={'../../assets/Logo1.png'} />
</h1>
<nav>
<ul className="flex-row nav-list">
{Object.entries(pages).map(([key, { name, subPages }]) => (
<li className={`li-spacing text-format ${currentPage.name === name && 'navActive'}`} key={key}>
<NavItems setCurrentPageKey={setCurrentPageKey} title={name} items={subPages} />
<button onClick={() => setCurrentPageKey(key)}>{name}</button>
</li>
))}
</ul>
</nav>
</header>
)
}
export default function NavItems(props) {
const { setCurrentPageKey, items, title } = props
return (
<ul className="flex-row nav-list">
<div className="dropdown">
<li className="nav-list-item">{title}</li>
<div className="dropdown-item">
{/* map through the props so each navitem receives unique information */}
{Object.entries(items).map(([key, { name }]) => (
<button onClick={() => setCurrentPageKey(key)} key={key}>
{name}
</button>
))}
</div>
</div>
</ul>
)
}
Related
I am trying to implement onClick event handle to get the details of the card. However, when clicking on it I am getting the details of some other card, not the card which I am trying to click. The Card component is recursive as I am creating a tree. Attaching the image for the reference.
const Card = (props: any) => {
const handleClick = (item: any) => {
console.log("This is value: ", item)
}
const [selectedOption, setSelectedOption] = useState(null);
return (
<ul>
{props.data.map((item: any, index: any) => (
<React.Fragment key={item.name}>
<li>
<div className="card">
<div className="card-body">
<p>{item.name}</p>
</div>
<div onClick={() => handleClick(item)}>
<Select
defaultValue={selectedOption}
onChange={handleChange}
className="select"
options={props.users}
/>
</div>
<div></div>
</div>
{item.children?.length && <Card data={item.children} users={[]} />}
</li>
</React.Fragment>
))}
</ul>
);
};
const AccountabilityChartComponent = () => {
return (
<div className="grid">
<div className="org-tree">
<Card
users={users}
data={hierarchy}
/>
</div>
</div>
);
};
export default AccountabilityChartComponent;
Currying the onClick handler is a useful technique. It's particularly convenient because the item and the click event are colocated within the same function -
function App({ items = [] }) {
const onClick = item => event => { // <--
console.log(event.target, item)
}
return <div>
{items.map((item, key) =>
<button key={key} onClick={onClick(item)} children={item.name} />
)}
</div>
}
const items = [
{ name: "apple", price: 3 },
{ name: "pear", price: 4 },
{ name: "kiwi", price: 5 }
]
ReactDOM.render(<App items={items} />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
How can I pass map items (title, category and images) in my id.jsx file.
Basically, I just want to create a single page for my projects. But I can only access post ID. I don't know how to pass other data items.
'Projects folder'
[id].js
import { useRouter } from "next/router";
const Details = () => {
const router = useRouter();
return <div>Post #{router.query.id}
// Single Project Title = {project.title} (like this)
</div>;
};
export default Details;
index.js
import { MyProjects } from "./MyProjects";
const Projects = () => {
const [projects, setProjects] = useState(MyProjects);
{projects.map((project) => (
<Link
href={"/projects/" + project.id}
key={project.id}
passHref={true}
>
<div className="project__item">
<div className="project__image">
<Image src={project.image} alt="project" />
</div>
<div className="project_info">
<h5>{project.category}</h5>
<h3>{project.title}</h3>
</div>
</div>
</Link>
))}
If I understand your question correctly, you want to send some "state" along with the route transition. This can be accomplished using an href object with the "state" on the query property, and the as prop to hide the query string.
Example:
{projects.map((project) => (
<Link
key={project.id}
href={{
pathname: "/projects/" + project.id,
query: {
id: project.id,
category: project.category,
title: project.title
}
}}
passHref={true}
as={"/projects/" + project.id}
>
<div className="project__item">
<div className="project__image">
<Image src={project.image} alt="project" />
</div>
<div className="project_info">
<h5>{project.category}</h5>
<h3>{project.title}</h3>
</div>
</div>
</Link>
))}
...
const Details = () => {
const router = useRouter();
return (
<div>
<div>Post #{router.query.id}</div>
<div>Title {router.query.title}</div>
<div>Category {router.query.category}</div>
</div>
);
};
In <Nav/> component, "click event" on chevron <button>, triggers nextTitle(length) function in useNextTitle.js custom hook. This function sets the value of val, which is being returned by useNextTitle.js. How to pass that new val to App.js ?
Bigger picture: I want to change the display between <Dod /> and <Analogia /> (in App.js), after "click event" in <Nav /> (figured out val would be helpful for that, as a parameter in Conditional Statement).
Functionality I`m trying to achieve is visualized on the website I done with vanilla Java Script : link (the blue navigation, changes "main pages" with "titles" when chevron clicked)
App.js
import Nav from "./components/Nav";
import Dod from "./components/Dod";
import Analogia from "./components/Analogia";
function App() {
return (
<div className="App">
<Nav />
<Dod />
<Analogia />
</div>
);
}
export default App
Nav.js
import useNextTitle from './useNextTitle';
import './Nav.css';
const Nav = () => {
const navData = [
{id: 0, text: "DOD"},
{id: 1, text: "analogia"}
]
const length = navData.length;
const { val, nextTitle } = useNextTitle();
return (
<nav>
<div>
{/* titles */}
<ul>
<li key="li1">
{navData.map((title, index) => {
return (
<div
className={index === val ? "active" : "display-none"} key={title.id}>
{title.text}
</div>
)
})}
</li>
</ul>
{/* chevron button */}
<div>
<button onClick={() => nextTitle(length)}>
<span className="material-icons">
chevron_right
</span>
</button>
</div>
</div>
</nav>
)
}
export default Nav
useNextTitle.js
import { useState } from 'react';
const useNextTitle = () => {
const [val, setVal] = useState(0);
const nextTitle = (length) => {
setVal(val === length -1 ? 0 : val + 1 )
console.log("hook vav = " + val)
}
return { val, nextTitle }
}
export default useNextTitle;
Move the useNextTitle hook/state up to App and pass val and nextTitle down to Nav to toggle/update the state. Use val to conditionally render Dod and Analogia.
Example:
function App() {
const { val, nextTitle } = useNextTitle();
return (
<div className="App">
<Nav {...{ val, nextTitle }} />
{val === 0 && <Dod />}
{val === 1 && <Analogia />}
</div>
);
}
...
const Nav = ({ val, nextTitle }) => {
const navData = [
{ id: 0, text: "DOD" },
{ id: 1, text: "analogia" }
];
const length = navData.length;
return (
<nav>
<div>
{/* titles */}
<ul>
<li key="li1">
{navData.map((title, index) => {
return (
<div
className={index === val ? "active" : "display-none"}
key={title.id}
>
{title.text}
</div>
);
})}
</li>
</ul>
{/* chevron button */}
<div>
<button onClick={() => nextTitle(length)}>
<span className="material-icons">chevron_right</span>
</button>
</div>
</div>
</nav>
);
};
In <Nav/> component, "click event" on chevron <button>, triggers nextTitle(length) function in useNextTitle.js custom hook. This function sets the value of val, which is being returned by useNextTitle.js. How to pass that new val to App.js ?
Bigger picture: I want to change the display between <Dod /> and <Analogia /> (in App.js), after "click event" in <Nav /> (figured out val would be helpful for that, as a parameter in Conditional Statement).
Functionality I`m trying to achieve is visualized on the website I done with vanilla Java Script : link (the blue navigation, changes "main pages" with "titles" when chevron clicked)
App.js
import Nav from "./components/Nav";
import Dod from "./components/Dod";
import Analogia from "./components/Analogia";
function App() {
return (
<div className="App">
<Nav />
<Dod />
<Analogia />
</div>
);
}
export default App
Nav.js
import useNextTitle from './useNextTitle';
import './Nav.css';
const Nav = () => {
const navData = [
{id: 0, text: "DOD"},
{id: 1, text: "analogia"}
]
const length = navData.length;
const { val, nextTitle } = useNextTitle();
return (
<nav>
<div>
{/* titles */}
<ul>
<li key="li1">
{navData.map((title, index) => {
return (
<div
className={index === val ? "active" : "display-none"} key={title.id}>
{title.text}
</div>
)
})}
</li>
</ul>
{/* chevron button */}
<div>
<button onClick={() => nextTitle(length)}>
<span className="material-icons">
chevron_right
</span>
</button>
</div>
</div>
</nav>
)
}
export default Nav
useNextTitle.js
import { useState } from 'react';
const useNextTitle = () => {
const [val, setVal] = useState(0);
const nextTitle = (length) => {
setVal(val === length -1 ? 0 : val + 1 )
console.log("hook vav = " + val)
}
return { val, nextTitle }
}
export default useNextTitle;
Move the useNextTitle hook/state up to App and pass val and nextTitle down to Nav to toggle/update the state. Use val to conditionally render Dod and Analogia.
Example:
function App() {
const { val, nextTitle } = useNextTitle();
return (
<div className="App">
<Nav {...{ val, nextTitle }} />
{val === 0 && <Dod />}
{val === 1 && <Analogia />}
</div>
);
}
...
const Nav = ({ val, nextTitle }) => {
const navData = [
{ id: 0, text: "DOD" },
{ id: 1, text: "analogia" }
];
const length = navData.length;
return (
<nav>
<div>
{/* titles */}
<ul>
<li key="li1">
{navData.map((title, index) => {
return (
<div
className={index === val ? "active" : "display-none"}
key={title.id}
>
{title.text}
</div>
);
})}
</li>
</ul>
{/* chevron button */}
<div>
<button onClick={() => nextTitle(length)}>
<span className="material-icons">chevron_right</span>
</button>
</div>
</div>
</nav>
);
};
In my News component I have an array of posts:
const cardInfo = [
{
id: 1,
date: "12/07/2020",
title: "Machine Learning",
author: "Alex M.",
text: "text1"
},
{
id: 2,
date: "12/07/2020",
title: "AI",
author: "Alex E.",
text: "text2"
}
]
A renderCard function that renders the array:
renderCard = (card, index) => {
return (
<Row id={index}>
<Col sm="10">
<Card key={index}>
<CardBody>
<CardTitle tag="h5" className="mb-2 text-muted">
{card.title}
</CardTitle>
<CardSubtitle tag="h6" className="mb-2 text-muted">
Written on {card.date} by {card.author}
</CardSubtitle>
<CardText className="mb-2 text-muted">
<ReadMoreReact
text={card.text}
readMoreText="Read more."
min={160}
ideal={200}
/>
</CardText>
<CardSubtitle tag="h6" className="mb-2 text-muted">
Category: {card.category}
</CardSubtitle>{" "}
<Button>Read more.</Button>
</CardBody>
</Card>
</Col>
</Row>
);
};
Return statement that displays the array of posts and also titles of recent posts sorted by date:
return (
<div>
<CareersHeader />
<div className="news">
<h1 className="news__title" id="news-title">
Blog
</h1>
<div className="news__content">
<div className="item1 news__posts">
{cardInfo.map(this.renderCard)}
</div>
<div className="item2 news__recent_posts">
<div>Recents Posts:</div>
<div>
{cardInfo
.sort((a, b) => (a.date > b.date ? 1 : -1))
.map((post, index) => (
<button onClick={what to do} key={index}>
{post.title}
</button>
))}
</div>
</div>
</div>
</div>
</div>
);
Post titles in recent posts list are buttons. When you click on a button, it should open that particular blog post. I have added in App.js this route:
<Route exact path="/news/:id" component={BlogPostDetails} />
BlogPostDetails component
return (
<div>
<CareersHeader />
<div className="news">
<h1 className="news__title" id="news-title">
Blog
</h1>
<div className="news__content">
<div className="item1 news__posts">
<p>Blog post</p>
{cardInfo.map((card) => (
<div key={card.id}>{renderCard}</div>
))}
</div>
</div>
</div>
</div>
);
However, I don't know how to match the id from URL with the id of the post title clicked.
I have tried with useParams() hook, but I was getting errors that I couldn't resolve (invalid hook call). Also, I am wondering if I could just create a function that would be called on a button click, something like this:
const displayPost= (index) => {
const url = "/news/" + index;
window.open(url, "_top");
};
I'm sure there is quite simple way in React to do this, just that I'm still quite new and don't understand the lifecycle. Help appreciated.
Title part.
import { useHistory } from 'react-router-dom'
.
.
.
function MyPage({ ...rest }){
const history = useHistory()
return(
.
.
.
{cardInfo
.sort((a, b) => (a.date > b.date ? 1 : -1))
.map((post, index) => (
<button onClick={() => history.push(post.id)} key={index}>
{post.title}
</button>
))}
.
.
.
}
.
.
.
)
BlogPostDetails component
import { useRouteMatch } from 'react-router-dom'
const DetailPage = ({
...rest
}){
const match = useRouteMatch()
const { id } = match.params
const [ cardInfo, setCardInfo ] = useState([])
const [ loading, setLoading ] = useState(false)
const [ error, setError ] = useState(null)
useEffect(() =>{
setLoading(true)
//fetch your details from API and set Loading, Error
}, [ id ])
return (
<div>
<CareersHeader />
<div className="news">
<h1 className="news__title" id="news-title">
Blog
</h1>
<div className="news__content">
<div className="item1 news__posts">
<p>Blog post</p>
{!loading && cardInfo.map((card) => (
<div key={card.id}>{renderCard}</div>
))}
</div>
</div>
</div>
</div>
);
}