Button count not working from different component

In my Test.jsx i’m importing <Button> from App.jsx but not sure what i’m doing wrong the function to add numbers does not work. When using <button> all working fine. But i want to use <Button> because of styles that i have set in App.jsx for it. So i want to understand what i’m missing that i cant use it.

App

import React, { useState } from 'react';
import { createContext } from 'react';

import Test from './views/Test';
export const CreateContext = createContext();

export const Button = ({ addToCart }) => {
  return <button onClick={addToCart}>Buttom from App.jsx</button>;
};

export default function App() {
  const [total, setCartTotal] = useState(0);
  const addToCart = () => setCartTotal(total + 1);

  return (
    <>
      <CreateContext.Provider
        value={{
          totalFromContext: total,
          addToCartFromContext: addToCart
        }}
      >
        <Test addToCart={addToCart} total={total} />
      </CreateContext.Provider>
    </>
  );
}

Test

import React from 'react';
// import { useContext } from 'react';
// import { CreateContext } from '../App';
import { Button } from '../App';

const Test = ({ addToCart, total }) => {
  // const { totalFromContext, addToCartFromContext } = useContext(CreateContext);

  return (
    <>
      <div>
        From from props {total}
        <Button onClick={addToCart}></Button>
        <button onClick={addToCart}>{`Button with <button>`}</button>
      </div>
      {/* <div>
        From context {totalFromContext}
        <button onClick={addToCartFromContext}>Add to cart</button>
      </div> */}
    </>
  );
};

export default Test;

You’re duplicating the functionality. You have a getter and a setter (total, setCartTotal). You wrap setCartTotal in another function (addToCart). Those you have added into the context.

The entire point of context is to avoid manually passing props down. So when you useContext, you’ve got access to the getter and setter: the references you get from useContext are the ones you should be using. But instead of using those, you’re passing the getter and setter manually down as props. So you have a conflict, and as a result, nothing happens.

It’s also a very bad idea to call your context CreateContext given that the name of the function you use to create contexts is called createContext but 🤷

EDIT: example. I’ve purposely made this slightly more complex than it is at the minute in an attempt to reflect a little bit more of a real-world situation. The issue is that Context makes things quite complicated, and needs to be used with care – it’s designed to pass things down anywhere in the app, so in your example, where there is only one level, it is much easier and safer to just pass props directly (I do realise you’re just testing out how it all works though, that it’s necessary for you to do it the way you’re doing it).

import React from "react";
import { CartProvider } from "./use-cart";
import AddToCart from "./AddToCart";
import CartTotalItems from "./CartTotalItems";

export default function App() {
  return (
      <CartProvider>
        <CartTotalItems />
        <AddToCart />
      </CartProvider>
  );
}

Split out the context, otherwise you have circular dependencies (in yours, App depends on Test which depends on App, which depends on Test etc – it’ll work but you’ll get warnings in the console)

import React from "react";

/**
 * All the logic for carts goes in here. As it gets more complex, you would
 * normally split the state and the functions for updating the state into two
 * contexts (like CartStateContext and CartUpdateContext for example), and
 * possibly start looking at `useReducer` instead of just  `useState`, as there 
 * is stuff you need to think about like price calculations, error handling, maybe
 * API calls _etc_ _etc_.
 */

const CartContext = React.createContext(null);

export const CartProvider = ({ children }) => {
  const [cartTotal, setCartTotal] = React.useState(0);
  const incrementCartTotal = () => setCartTotal(cartTotal + 1);

  return (
    <CartContext.Provider  value={{ cartTotal, incrementCartTotal }}>
      {children}
    <CartContext.Provider>
  );
  // In a more complex situation, the cart Provider would contain both the providers, one of
  // which would just have the state then inside that one would just have the update functions:
  // <CartStateContext.Provider  value={stateObject}>
  //   <CartUpdateContext.Provider value={updateFunctions}>
  //     {children}
  //    <CartUpdateContext.Provider>
  //  <CartStateContext.Provider>
};

// And in more complex situations, you would probably want to have have two hooks, a
// `useCartState` and `useUpdateCartState`
export const useCart = () => {
  const { cartTotal, incrementCartTotal } = React.useContext(CartContext);
  // Here you could put some error handling to prevent this hook being used when it
  // isn't in the scope of the context.
  return { cartTotal, incrementCartTotal };
}
import React from "react";
import { useCart } from "./use-cart";

const AddToCart = () => {
  const { incrementCartTotal } = useCart();

  return <button onClick={incrementCartTotal}>Add to cart</button>
};

export default AddToCart;
import React from "react";
import { useCart } from "./use-cart";

const CartTotalItems = () => {
  const { cartTotal } = useCart();

  return <p>Total items currently in cart: {cartTotal}</p>
};

export default CartTotalItems;
2 Likes

Thank you make sense more now, i was at first confused why did you create useCart function, but after reading notes make sense. For now i have commented this one and just have this in CartTotalItems

export default function CartTotalItems() {
  const { cartTotal } = React.useContext(CartContext);
  return <p>Total items currently in cart: {cartTotal}</p>;
}

Also off topic, why use Arrow function for this, when this function just takes less lines compared to Arrow?

const CartTotalItems = () => {
  const { cartTotal } = useCart();
  return <p>Total items currently in cart: {cartTotal}</p>;
};
export default CartTotalItems;

Sorry! It’s just habit; I should have used the same as your in the code. Practically, makes no difference