I'm currently struggling with React Context. I'd like to pass functions allowing the show / hide cart logic in the context, instead of using props between components.
I dont understand why when clicking on the button in HeaderCartButton component, it doesn't trigger the **onClick={ctx.onShowCart}** that is in my context, even though when I console log the cartCtx.state it is properly updated, which should then add the component in the App.js
//App.js
import { useContext } from "react";
import Header from "./components/Layout/Header";
import Meals from "./components/Meals/Meals";
import Cart from "./components/Cart/Cart";
import CartProvider from "./store/CartProvider";
import CartContext from "./store/cart-context";
function App() {
const ctx = useContext(CartContext);
return (
<CartProvider>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</CartProvider>
);
}
export default App;
//cart-context.js
import React from "react";
const CartContext = React.createContext({
state: false,
onShowCart: () => {},
onHideCart: () => {},
items: [],
totalAmount: 0,
addItem: (item) => {},
removeItem: (id) => {},
});
export default CartContext;
//CartProvider.js
import CartContext from "./cart-context";
import { useState } from "react";
const CartProvider = (props) => {
const [cartIsShown, setCartIsShown] = useState(false);
const showCartHandler = () => {
setCartIsShown(true);
};
const hideCartHandler = () => {
setCartIsShown(false);
};
const handleAddItem = (item) => {};
const handleRemoveItem = (id) => {};
const cartCtx = {
state: cartIsShown,
onShowCart: showCartHandler,
onHideCart: hideCartHandler,
items: [],
totalAmount: 0,
addItem: handleAddItem,
removeItem: handleRemoveItem,
};
return (
<CartContext.Provider value={cartCtx}>
{props.children}
</CartContext.Provider>
);
};
export default CartProvider;
//Header.js
import { Fragment } from "react";
import HeaderCartButton from "./HeaderCartButton";
import mealsImage from "../../assets/meals.jpg";
import classes from "./Header.module.css";
const Header = (props) => {
return (
<Fragment>
<header className={classes.header}>
<h1>ReactMeals</h1>
<HeaderCartButton />
</header>
<div className={classes["main-image"]}>
<img src={mealsImage} alt="A table full of delicious food!" />
</div>
</Fragment>
);
};
export default Header;
//HeaderCartButton.js
import CartIcon from "../Cart/CartIcon";
import { useContext } from "react";
import classes from "./HeaderCartButton.module.css";
import CartContext from "../../store/cart-context";
const HeaderCartButton = (props) => {
const ctx = useContext(CartContext);
const numberOfCartItems = ctx.items.reduce((accumulator, item) => {
return accumulator + item.amount;
}, 0);
return (
<button className={classes.button} onClick={ctx.onShowCart}>
<span className={classes.icon}>
<CartIcon />
</span>
<span>Your Cart</span>
<span className={classes.badge}>{numberOfCartItems}</span>
</button>
);
};
export default HeaderCartButton;
Thanks for your help
If you look at your App component, you are using CartContext outside the provider.
function App() {
const ctx = useContext(CartContext);
return (
<CartProvider>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</CartProvider>
);
}
You should modify it so that it is similar to the following, where you are using the context inside the provider.
const Main = () => {
return <CartProvider><App /></CartProvider>
}
function App() {
const ctx = useContext(CartContext);
return (
<>
{ctx.state && <Cart />}
<Header />
<main>
<Meals />
</main>
</>
);
}
Related
On my component render, my useEffects hooks called, a function. the function updates the state status depending on the condition within the useEffects produce.
So in this case how to test the `mobileMenu` and how to set different condition in useEffect to test it?
I hope both my useEffects and useState need to mocked. I am in learning process with react. I could not get any correct answer upon searching, any one help me please?
here is my app.tsx
my ts file:
import { Footer, Header, ProductCart, ProductPhotoGallery, Tabs } from '#mcdayen/components';
import { Cart, Logo, MobileMenu, NaviLinks, QuickSearch, User } from '#mcdayen/micro-components';
import { initialNaviLinksProps, initialPhotoProps, initialTabsProps, NaviLinksProps, sizeProps } from '#mcdayen/prop-types';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCartDetails, sizeHandler } from './store/cart.slice';
import { AppDispatch, RootState } from './store/store.config';
export function App() {
const dispatch:AppDispatch = useDispatch();
dispatch(fetchCartDetails());
const {product} = useSelector((state:RootState) => state.cartStore)
const [mobileMenu, setMobileMenu] = useState<boolean>(false);
const [linkProps, setLinkProps] = useState<NaviLinksProps | null>(null);
function mobileMenuHandler() {
setMobileMenu((current: boolean) => !current);
}
useEffect(() => {
setLinkProps(initialNaviLinksProps);
const mobileId = document.getElementById('mobileMenu');
if (mobileId?.offsetParent) {
mobileMenuHandler();
}
}, []);
useEffect(() => {
setLinkProps((props) => {
return mobileMenu ? { ...initialNaviLinksProps } : { ...initialNaviLinksProps, classProps: props?.classProps + ' hidden' }
})
}, [mobileMenu]);
function onSizeSelect(selectedSize: sizeProps) {
dispatch(sizeHandler(selectedSize));
}
return (
<section className="box-border m-auto flex flex-col pl-[18px] py-6 min-h-screen flex-wrap px-5 md:container md:w-[1440px] md:pl-[70px] pr-5 ">
<Header>
<Logo />
{linkProps && <NaviLinks passNaviLinks={linkProps} />}
<div className="flex gap-3">
<QuickSearch />
<Cart />
<User />
<MobileMenu menuHandler={mobileMenuHandler} />
</div>
</Header>
<main className='flex flex-col justify-between lg:flex-row'>
<div className='hidden lg:block w-[325px]'>
<div>
<Tabs tabProps={initialTabsProps} />
</div>
</div>
<div className='grow-0 flex-auto' >
{initialPhotoProps.length && <ProductPhotoGallery gallery={initialPhotoProps} />}
</div>
<div className='flex bg-white'>
{product && <ProductCart sizeSelect={onSizeSelect} passCartProps={product} />}
</div>
</main>
<Footer />
</section>
);
}
export default App;
My spec:
import { configureStore } from '#reduxjs/toolkit';
import { render } from '#testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './app';
import cartReducer from './store/cart.slice';
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
export function createTestStore() {
const store = configureStore({
reducer: {
cartStore:cartReducer,
}
})
return store;
}
describe('App', () => {
const setMobileMenu = jest.fn();
const useStateMock = (initState: boolean) => [initState, setMobileMenu];
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
afterEach(() => {
jest.clearAllMocks();
});
const store = createTestStore();
it('should render successfully', () => {
const { baseElement } = render(
<BrowserRouter>
<Provider store={store}>{<App />}</Provider>
</BrowserRouter>
);
expect(baseElement).toBeTruthy();
useStateMock(true);
expect(setMobileMenu).toHaveBeenCalledWith(true);
});
});
I am getting an error at: `
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
`
as : Argument of type '(initState: boolean) => (boolean | jest.Mock<any, any>)[]' is not assignable to parameter of type '() => [unknown, Dispatch<unknown>]'.
and my test failing.
Need help for:
test the useEffect hook on anonymous function ( mocking )
fixing the error highlighted
testing the state on setMobileMenu
Any one please help me with the correct way?
Try to declare useStateMock as:
const useStateMock = (initState: any) => [initState, setMobileMenu];
I am following the WhatsApp clone on YouTube, I did exactly what they were doing but I don't know why I'm getting this error. I was
I read a lot of blogs, but I couldn't resolve it.
In app, it gives this error and couldn't dismiss.
./src/App.js Line 10: 'dispatch' is assigned a value but never used no-unused-vars
In login, it gives this error.
./src/Login.js Line 9: Unexpected empty object pattern no-empty-pattern
<!-- begin snippet: js hide: false console: true babel: false -->
import React from "react";
import "./Login.css";
import { Button } from "#mui/material";
import { auth, provider } from "./firebase";
import { useStateValue } from "./StateProvider";
import { actionTypes } from "./reducer";
function Login() {
const [value, dispatch] = useStateValue({});
// const [value, dispatch] = useStateValue({})
// const [{ type, user }, dispatch] = useStateValue();
const signIn = () => {
auth
.signInWithPopup(provider)
.then((result) => {
dispatch({
type: actionTypes.SET_USER,
user: result.user,
});
})
.catch((error) => alert(error.message));
};
return (
<div className="login">
<div className="login__container">
<img
src="https://www.freepnglogos.com/uploads/whatsapp-logo-png-hd-2.png"
alt=""
/>
<div className="login__text">
<h1>Sign in to WhatsApp</h1>
</div>
<Button onClick={signIn}>Sign In with Google</Button>
</div>
</div>
);
}
export default Login;
import React from "react";
import "./App.css";
import Sidebar from "./Sidebar";
import Chat from "./Chat";
import Login from "./Login";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useStateValue } from "./StateProvider";
function App() {
const [{ user }, dispatch] = useStateValue();
return (
<div className="app">
{!user ? (
<Login />
) : (
<div className="app__body">
<Router>
<Sidebar />
<Routes>
<Route path="/rooms/:roomId" element={<Chat />} />
<Route path="/" element={<Chat />} />
</Routes>
</Router>
</div>
)}
</div>
);
}
export default App;
import React, { createContext, useContext, useReducer } from "react";
export const StateContext = createContext();
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
export const initialState = {
user: null,
};
export const actionTypes = {
SET_USER: "SET_USER",
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case actionTypes.SET_USER:
return {
...state,
user: action.user,
};
default:
return state;
}
};
export default reducer;
I have a context api in my application. At the moment I only keep the categories and i have 1 initial category. I print the categories in App.js with the map function. I have defined a function called addCategoryHandler in context api and I want to update my state by calling it in AddCategory component. But when I click the button state.categories returns undefined. I guess I'm missing something about lifecyle but I couldn't quite understand. Can you help?
Here is the codesandbox link: https://codesandbox.io/s/hungry-zeh-kwolr8
App.js
import AvailableProducts from './components/AvailableProducts.js';
import Category from './components/Category.js';
import Review from './components/Review.js';
import AddCategory from './components/AddCategory';
import { useAppContext } from './context/appContext';
import './assets/styles/App.scss';
export default function App() {
const { categories } = useAppContext();
return (
<main>
<h1>Initial Screen</h1>
<div className='container'>
<div className='container__left-side'>
<AvailableProducts />
<Review />
</div>
<div className='container__right-side'>
{categories.map((category) => (
<Category
key={category.id}
id={category.id}
title={category.title}
/>
))}
<AddCategory />
</div>
</div>
</main>
);
}
Context Api
import React, { useContext, useState } from "react";
import generateCategoryTitle from "../utils/GenerateCategoryTitle";
const AppContext = React.createContext();
const initialState = {
categories: [{ id: 1, title: "Category 1", products: [] }]
};
const AppProvider = ({ children }) => {
const [state, setState] = useState(initialState);
console.log(state);
const addCategoryHandler = () => {
// const { newId, newCategoryTitle } = generateCategoryTitle(state.categories);
// // const newCategory = [{ id: newId, title: newCategoryTitle, products: [] }];
// setState((prevState) => {
// console.log([...prevState.categories,...newCategory]);
// });
console.log("add category clicked");
};
return (
<AppContext.Provider value={{ ...state, addCategoryHandler }}>
{children}
</AppContext.Provider>
);
};
const useAppContext = () => useContext(AppContext);
export { AppProvider, useAppContext };
Add Category Component
import "../assets/styles/AddCategory.scss";
import { useAppContext } from "../context/appContext";
const AddCategory = () => {
const { addCategoryHandler } = useAppContext();
return (
<button
className="add-categoryn-btn"
type="button"
onClick={addCategoryHandler}
>
Add Category
</button>
);
};
export default AddCategory;
I am working with react. When I try to play audio from a button I am receiving this error. Everything worked before when the website was only a one page website. Then I turned it into a multi-page website using react-router-dom router, switch, and route everything started to error out with the onClick. I am not sure what went wrong or how to fix it. I am still pretty new with react. Here is the code:
Player.js
import React, { useState } from "react";
import "./Button.css";
import {useTranslation} from "react-i18next";
import teaser from '../sounds/teaser-final.mp3';
const Player = ({ url }) => {
const useAudio = url => {
const [audio] = useState(new Audio(teaser));
const [playing, setPlaying] = useState(true); //nothing is playing on default
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
console.log("audio is playing" + toggle);
};
return [playing, toggle];
};
const [toggle] = useAudio(url);
const {t} = useTranslation('common');
return (
<div>
<audio id="player" style={{'display': 'none'}} src={teaser}></audio>
<button
className="btns hero-button btn--outline btn--large"
onClick={toggle}
>
{t('heroSection.button')}
</button>
</div>
);
};
export default Player;
HeroSection.js (file where Player.js is used)
import React from 'react'
import './HeroSection.css'
import Player from '../Player';
function HeroSection() {
return (
<div className="hero-btns">
<Player />
</div>
)
}
export default HeroSection;
App.js
import React, { useState, useEffect } from 'react';
import './index.css';
import {BrowserRouter, Switch, Route} from 'react-router-dom';
import Axios from 'axios';
import AdminHome from './Components/auth/Admin';
import Login from './Components/auth/Login';
import Register from './Components/auth/Register';
import UserContext from './Context/UserContext';
import Navbar from './Components/Navbar/Navbar';
import HeroSection from './Components/HeroSection/HeroSection';
import ShareSection from './Components/ShareSection/ShareSection';
import Subscribe from './Components/Subscribe/Subscribe';
import Footer from './Components/Footer/Footer';
import About from './Components/About/About';
import TheApp from './Components/TheApp/TheApp';
import Contact from './Components/SocialSection/Contact';
import CookiesPopUp from './Components/Cookies/CookiesPopUp';
function Home() {
return (
<>
<CookiesPopUp />
<Navbar />
<HeroSection />
<ShareSection />
<Subscribe />
<TheApp />
<About />
<Contact />
<Footer />
</>
);
};
function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
});
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
const checkedLoggedIn = async () => {
let token = localStorage.getItem("auth-token");
if(token === null) {
localStorage.setItem("auth-token", "");
token = "";
}
const tokenRes = await Axios.post(
"http://localhost:5000/users/tokenIsValid", null,
{headers: { "x-auth-token": token }}
);
if (tokenRes.data) {
const userRes = await Axios.get("http://localhost:5000/users/", {
headers: { "x-auth-token": token },
});
setUserData({
token,
user: userRes.data,
});
}
return () => setDidMount(false);
};
checkedLoggedIn();
}, [])
if(!didMount) {
return null;
}
return (
<>
<BrowserRouter>
<UserContext.Provider value={{userData, setUserData}}>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/admin" component={AdminHome} exact />
<Route path="/admin/login" component={Login} exact />
<Route path="/admin/register" component={Register} exact />
</Switch>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
export default App;
And this is the error I get in console when I try to play the audio.
You are overriding the definition of toggle with this code :
const [toggle] = useAudio(url);. The Player.js has multiple declarations and definitions of toggle. See:
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
console.log("audio is playing" + toggle);
};
return [playing, toggle];
};
..
...
..
const [toggle] = useAudio(url);
Hence the Error Expected OnClick to be a function but provided a boolean
Thank you for your feedback! I managed to get everything to work by changing my code in Player.js to:
import React, { useState } from "react";
import "./Button.css";
import {useTranslation} from "react-i18next";
import teaser from '../sounds/teaser-final.mp3';
const Player = () => {
const [playing, setPlaying] = useState(true);
const [audio] = useState(new Audio(teaser));
const toggle = () => {
setPlaying(!playing);
playing ? audio.play() : audio.pause()
};
const {t} = useTranslation('common');
return (
<div>
<audio id="player" style={{'display': 'none'}} src={teaser}></audio>
<button
className="btns hero-button btn--outline btn--large"
onClick={toggle}
>
{t('heroSection.button')}
</button>
</div>
);
};
export default Player;
I wonder if there is not a better way to manage the open and close of Dialogs in a functional component? You can find an example below:
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
const handleDeleteOpen = () => {
setDeleteOpen(true);
};
const handleDeleteClose = () => {
setDeleteOpen(false);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
<EditDialog handleClose={handleEditClose} open={editOpen} />
<DeleteDialog handleClose={handleDeleteClose} open={deleteOpen} />
</>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
I think this is super redundant but I cannot find a nicer way to manage several different dialogs.
const handleEditOpen = () => {
setEditOpen(true);
};
const handleEditClose = () => {
setEditOpen(false);
};
const handleDeleteOpen = () => {
setDeleteOpen(true);
};
const handleDeleteClose = () => {
setDeleteOpen(false);
};
Many thanks for your time and advice!
To reduce some of the redundancy of your code, you could set the open/close in one function, by essentially toggling the current state. I did mine inline, but you could still create a handleEdit function and toggle the state there.
import React, {useState} from "react";
import ReactDOM from "react-dom";
function App() {
const [editCard, setEditCard] = useState(false)
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setEditCard(!editCard)}>Toggle Edit</button>
{editCard && <div>Card is open for editing</div>}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is another example with your code. I didn't run it, but it should look something like this.
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEdit = () => {
setEditOpen(!editOpen);
};
const handleDelete = () => {
setDeleteOpen(!deleteOpen);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{
editOpen && <EditDialog handleEdit={handleEdit} />
}
{
deleteOpen && <DeleteDialog handleClose={handleClose} />
}
</>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
The responsibility of open the dialog should be of the main component. This way the modal is only rendered if the state property is true.
Another tip is use <React.Fragment> insted <>
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import EditDialog from './EditDialog';
import DeleteDialog from './DeleteDialog';
const ContactCard = ({ contact }) => {
const [editOpen, setEditOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
const handleEditOpen = () => {
setEditOpen(!editOpen);
};
const handleDeleteOpen = () => {
setDeleteOpen(!deleteOpen);
};
const { type, firstName, lastName, phoneNumber, mail } = contact;
return (
<React.Fragment>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{
editOpen && <EditDialog handleClose={handleEditOpen} />
}
{
deleteOpen && <DeleteDialog handleClose={handleDeleteOpen} />
}
</React.Fragment>
);
};
ContactCard.propTypes = {
contact: PropTypes.object.isRequired
};
export default ContactCard;
To incapsulate logic of changing dialog opening state, I'd recommend to create separate hook:
const useToggle = (defaultValue) => {
return useReducer((value) => !value, !!defaultValue)
}
This hook is basically useState but setState function isn't waiting for argument to update state, it updates state with the inverse of current state.
This might be useful while working with dialogs:
const ContactCard = () => {
const [editOpen, toggleEditOpen] = useToggle(false);
const [deleteOpen, toggleDeleteOpen] = usetoggle(false);
return (
<>
<div className={classes.main}>
{/* All my contact informations */}
</div>
{editOpen && <EditDialog handleEdit={toggleEditOpen} />}
{deleteOpen && <DeleteDialog handleClose={toggleDeleteOpen} />}
</>
);
};