When I click on a button, I am adding a product to the cart but I also want to set the state of a side drawer to true so it appears. This is working but trying to pass that state to the component so that when I click on close is giving me trouble. Here are basically all the moving parts:
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
<CartDrawer toggleOpen={isOpen} />
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
This is working fine. I click on add to cart, it adds to cart and my modal shows up as expected.
The modal is basically component and I am receiving toggleOpen as props. Here is the CartDrawer component
const CartDrawer = (props) => {
const [isOpen, setIsOpen] = useState(false);
const closeNavHandler = () => {
setIsOpen(false);
};
return (
<div
id="mySidenav"
className={props.toggleOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={closeNavHandler}
>
×
</a>
</div>
);
};
export default CartDrawer;
I know this is wrong but I can't figure out how to update the state here correctly to close it.
Just control everything from the parent. The cartDrawer only needs to receive a ìsOpen prop to know its state. Don't write another state in it.
A component like this should be stupid. It receives informations, and display them. Don't spill the logic all over your components. Just have a single source of truth.
// Main
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
<CartDrawer isOpen={isOpen} onClose={()=> setIsOpen(false)}/>
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
// CartDrawer
const CartDrawer = ({isOpen, onClose}) => {
return (
<div
id="mySidenav"
className={isOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={onClose}
>
×
</a>
</div>
);
};
export default CartDrawer;
I think that's what you want?
const [isOpen, setIsOpen] = useState(false);
const addToCartHandler = async (id) => {
setIsOpen(true);
// add to cart logic here
}
const toggleOpenDrawer = (val) => {
setIsOpen(val);
}
<CartDrawer toggleOpenDrawer={toggleOpenDrawer} toggleOpen={isOpen} />
<Button
variant="primary"
onClick={() => addToCartHandler(id)}
>
Add to Cart
</Button>
const CartDrawer = (props) => {
const { toggleOpenDrawer } = props
return (
<div
id="mySidenav"
className={props.toggleOpen ? "sidenav open" : "sidenav"}
>
<a
className="closebtn"
onClick={toggleOpenDrawer(!props.toggleOpen)}
>
×
</a>
</div>
);
};
export default CartDrawer;
Related
I made a JSON file for the upcoming NFL season. In this component I have a working fetch method that gets my data, and I've named the variable "squads". Now I want to press a button to filter out the selected team's schedule and display it in a modal. I've hard coded my button in this example. My modal component works fine, and I have {props.children} in the modal's body to accept my data.
In the code below you'll see that I'm trying to assign the filtered team to the selectedTeam variable using useState. The error message I'm getting just says my variables are undefined.
import React, { useState, useEffect } from "react";
import Modal from "./Components/Modal";
export default function App() {
const [show, setShow] = useState(false);
const [title, setTitle] = useState("");
const [squads, setSquads] = useState([]);
const [modalTitleBackground, setModalTitleBackground] = useState("");
const [image, setImage] = useState("");
const [selectedTeam, setSelectedTeam] = useState([]);
const url = "../nfl2021.json";
const fetchData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setSquads(data.teams);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchData();
}, []);
// const filterTeam = (team) => {
// const theTeam = squads.filter((squad) => squad.name === team);
// setModalTitleBackground(theTeam[0].topBG);
// // setTitle(theTeam[0].name);
// setNickname(theTeam[0].nickname);
// setImage(`./images/${theTeam[0].img}`);
// setShow(true);
// };
const filterTeam = (team) => {
setSelectedTeam(squads.filter((squad) => squad.name === team));
console.log(selectedTeam);
setTitle(selectedTeam[0].name);
setModalTitleBackground(selectedTeam[0].topBG);
setImage(`./images/${selectedTeam[0].img}`);
setShow(true);
};
return (
<div className="App">
<button onClick={() => filterTeam("New England Patriots")}>
Show Modal
</button>
<button onClick={() => filterTeam("Green Bay Packers")}>
Show Modal 2
</button>
<button onClick={() => filterTeam("Cincinnati Bengals")}>
Show Modal 3
</button>
<Modal
image={image}
title={title}
backgroundColor={modalTitleBackground}
onClose={() => setShow(false)}
show={show}
>
<p>
This is the modal body using props.children in the modal component.
</p>
<p>The {title} 2021 schedule.</p>
{selectedTeam[0].schedule.map((schedule, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam[0].nickname} play the{" "}
{selectedTeam[0].schedule[index].opponent}.
</p>
);
})}
</Modal>
</div>
);
}
1- In react, the state is set asynchronously. selectedTeam is not set until next render.
2- You can use find instead of filter and get rid of array access.
const [selectedTeam, setSelectedTeam] = useState({schedule: []});
...
const filterTeam = (team) => {
let temp = squads.find((squad) => squad.name === team);
setSelectedTeam(temp);
console.log(temp);
setTitle(temp.name);
setModalTitleBackground(temp.topBG);
setImage(`./images/${temp.img}`);
setShow(true);
};
...
{selectedTeam.schedule.map((match, index) => {
return (
<p>
Week {index + 1}: The {selectedTeam.nickname} play the {match.opponent}.
</p>
);
})}
I am trying to create a function close modal when click outside but I am keep getting this error:
TypeError: node.current is not a function
Here is my following code in MemberCard.js:
const [modalStatus, setModalStatus] = useState(false);
const node = useRef(null);
const openModal = () => {
setModalStatus(!modalStatus);
};
const handleClick = (e) => {
if (node.current(e.target)) {
return;
}
// outside click
setModalStatus(false);
};
useEffect(() => {
document.addEventListener("mousedown", handleClick);
return () => {
document.removeEventListener("mousedown", handleClick);
};
}, []);
return (
<div className="member-card">
<div className="member-edit" onClick={openModal}>
<Symlink />
</div>
{modalStatus && (
<TeamStatusModal
active={modalStatus}
ref={node}
tab={tab}
member={member}
/>
)}
...
}
Here is my modal that I open after click:
const TeamStatusModal = (props) => {
const { active, tab, member, ref } = props;
console.log(ref);
return (
<div
className={`team-status-modal-container ${active ? "ACTIVE_CLASS" : ""}`}
>
<button className="status">
<ProfileIcon /> <span>View Profile</span>
</button>
<hr />
<button className="status">
<MessageIcon /> <span>Message Me</span>
</button>
</div>
);
};
How can I implement this feature?
In react, there are some good libraries that can help you with modals, one of them is called react-modal, you can give it a check.
If you want to implement a modal by yourself, we can follow some steps.
First we need to define a context, because the modal state needs to be accesed by more than one component or page in your app.
In the context, you could store the modal in a isModalOpen state, and add functions to manipulate it, such as openModal and closeModal. It really depends on the amount of features you want to add to this implementation.
Finally, you make the context globally accessible wrapping your app around a provider.
an example implementation
const ModalContext = createContext({})
export const ModalContextProvider = ({children}) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const toggleModalState = () => {
setIsModalOpen(state => !state)
}
return <ModalContext.Provider value={{isModalOpen, toggleModalState}}>{children}<ModalContext.Provider>
}
export const useModal = () => {
return useContext(ModalContext)
}
Now the modal will be available globally
I'm new to react and I'm learning by doing. Right now I'm stacked on using a custom Hook to share logic among components.
here is the logic inside the custom Hook
const useDropDownLogic = () => {
const { menu } = useContext(MenuContext)
const brand = [...new Set(menu.map(i => i.brand))]
const [category, setCategory] = useState([])
const brandChange = (e) => {
const { type, textContent } = e.target
setCategory([...new Set(menu.filter(i => i.[type] == textContent).map(i => i.category))])}
return {
brandChange,
brand,
category
}
}
export default useDropDownLogic;
here is the first component consuming the custom Hook
const DropDownBrand = () => {
const {brandChange, brand} = useDropDownLogic();
const dropdownRef = useRef(null);
const [isActive, setIsActive] = useDetectOutsideClick(dropdownRef, false);
const onClick = () => setIsActive(!isActive)
return (
<div id="dropdownbrand">
<div className="menu-container menu-brand">
<label className = 'nav-label'>Select a Brand</label>
<button onClick={onClick} className="menu-trigger brand-btn">
<i className="fas fa-chevron-down fa-2x drop-arrow"></i>
</button>
<nav
ref={dropdownRef}
className={`drop-menu ${isActive ? "active" : "inactive"}`}
>
<ul>
{brand.map((i) => (<li key={nanoid()} type = 'brand' value={i} onClick = {(e) => brandChange(e)}>{i}</li>))}
</ul>
</nav>
</div>
</div>
);
};
export default DropDownBrand;
and here the second
const DropDownCategory = () => {
const { category } = useDropDownLogic()
const dropdownRef = useRef(null);
const [isActive, setIsActive] = useDetectOutsideClick(dropdownRef, false);
const onClick = () => setIsActive(!isActive)
console.log(category);
return (
<div id="dropcategory">
<div className="menu-container menu-brand">
<label className = 'nav-label'>Select a Brand</label>
<button onClick={onClick} className="menu-trigger brand-btn">
<i className="fas fa-chevron-down fa-2x drop-arrow"></i>
</button>
<nav
ref={dropdownRef}
className={`drop-menu ${isActive ? "active" : "inactive"}`}
>
<ul>
{category.map((i) => (<li key={nanoid()} type = 'category'>{i}</li>))}
</ul>
</nav>
</div>
</div>
);
};
export default DropDownCategory;
Basically I'm able to populate the first dropdown but not the second. I don't understand why the category state is not update into the DropDownCategory component
any hint in what I'm doing wrong?
Many thanks in advance
I see,
I'm changing the approach. Moving state to a parent component and passing down function to handle click event. Then on Parent I'll update the dstate and pass it as props down to the child.
Nyhting to share yet but it will come
There is no persisted state in a hook, it's a way to share logic and functions. Because you are defining
const [category, setCategory] = useState([])
inside of the hook, it is defined each time you call the hook. In your case, dropdowncategory and dropdownbrand each have their personal state variable called category.
To solve this, define [category, setCategory] inside of the menu context, (or any other context) and pull in the values into your hook.. That way all instances of useDropDownLogic() are associated to global context value.
Good day Everyone,
I'm trying to run a query that increases a number whenever a button is clicked. I'm using a course from udemy is kinda old and our setup/installation is different. Please look at my code down below.
let count = 0;
const addOne = () => {
count++;
rendertheCounterApp();
};
const minus =() => {
console.log('Minus',);
}
const Reset = () => {
console.log ('Reset');
};
const rendertheCounterApp = () => {
const App = () => {
return (
<div className='App'>
<h1>Toggler {count} </h1>
<button onClick ={addOne}> +1 </button>
<button onClick={minus}>-1</button>
<button onClick ={Reset}>Rest</button>
</div>
);
};
export default App;
};
rendertheCounterApp();
is there any way I can modify that code to make the number increase whenever I click on the button?
Thanks in advance.
You need to have state to store the count and you can increase or decrease the count without creating any special functions. Try something like:
import React, { useState } from 'react';
const RenderTheCounterApp = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<h1>count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Add</button>
<button onClick={() => setCount(count - 1)}>Subtract</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
};
I am much confused as I don't know what I am doing wrong. Each time I clicked on the plus sign, all the other div elements display instead of the specific one I click on. I tried to use id argument in my show and hide functions, it is complaining of too many re-rendering . I have been on this for the past 12 hours. I need your help to solving this mystery. All I want to do is to click on the plus sign to display only the content and minus sign to hide it.
import React, {useState, useEffect} from 'react'
function Home() {
const [userData, setUserData] = useState([]);
const [showing, setShowing] = useState(false)
const [search, setSearch] = useState("");
const [clicked, setClicked] = useState("")
async function getData()
{
let response = await fetch('https://api.hatchways.io/assessment/students');
let data = await response.json();
return data;
}
useEffect(() => {
getData()
.then(
data => {
setUserData(data.students ) }
)
.catch(error => {
console.log(error);
})
}, [])
const handleFilterChange = e => {
setSearch(e.target.value)
}
function DataSearch(rows) {
const columns = rows[0] && Object.keys(rows[0]);
return rows.filter((row) =>
columns.some((column) => row[column].toString().toLowerCase().indexOf(search.toLowerCase()) > -1)
);
}
const searchPosts = DataSearch(userData);
const show = (id, e) => {
setShowing(true);
}
const hide = (id, e) => {
setShowing(false);
}
return (
<>
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search by name"} />
</div>
{
searchPosts.map((student) => (
<div key={student.id} className="holder">
<div className="images">
<img src={student.pic} alt="avatar" width="130" height="130" />
</div>
<div className="data-container">
<span className="name">{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}</span>
<span>Email: {student.email}</span>
<span></span>
<span>Company: {student.company}</span>
<span>Skill: {student.skill}</span>
<span>City: {student.city}</span>
{ showing ?
<button id={student.id} onClick={hide}>-</button>
: <button id={student.id} onClick={show}>+</button>
}
<div data-id={student.id}>
{ (showing )
? student.grades.map((grade, index) => (
<span id={index} key={index}>Test {index}: {grade}%</span>
)) : <span>
</span>
}
</div>
</div>
</div>
))
}
</>
)
}
export default Home
Change,
const [showing, setShowing] = useState(false)
to:
const [showing, setShowing] = useState({});
Here change the useState from boolean to object.. Reason for this is we will store the ids as keys and a boolean value indicating if the grade should be shown or not.
And remove Show and hide function and have a common toggle function like,
const toggleGrades = (id) => {
setShowing((previousState) => ({
...previousState,
[id]: !previousState[id]
}));
};
You are using setShowing(true) in show function and setShowing(false) in hide function which is the reason for opening all and closing all at any click.. Because you have never mentioned which exact grade should be shown so you need to make use of id here..
And buttons click handler will be like,
{showing[student.id] ? (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
-
</button>
) : (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
+
</button>
)}
So pass student id () => toggleGrades(student.id) in both show and hide button an make the button gets toggled.
Display the grades like,
<div data-id={student.id}>
{showing[student.id] ? (
student.grades.map((grade, index) => (
<span id={index} key={index}>
Test {index}: {grade}%
</span>
))
) : (
<span></span>
)}
</div>
Here if showing[student.id] will display only the grades of clicked item.
And that is why id plays a major role in such case.
Working Example: