0

I have a demo here

I have a simple list of products and a cart that I would like to add the products to.

The Products and Cart are separate components in the index file.

I have the function to add the products to the cart in the Products components but how do I pass this to the Cart component that is outside the Products component.

import React, { useState } from "react";
import { render } from "react-dom";
import Cart from "./Cart";
import Products from "./Products";
import "./style.css";

const App = () => {
  return (
    <div>
      <Products />
      <Cart />
    </div>
  );
};

render(<App />, document.getElementById("root"));
ksav
  • 20,015
  • 6
  • 46
  • 66
lomine
  • 873
  • 5
  • 20
  • 36

6 Answers6

2

https://stackblitz.com/edit/react-ts-txpsds

// index.tsx
import React from "react";
import { render } from "react-dom";
import Cart from "./Cart";
import { CartProvider } from "./context";
import Products from "./Products";
import "./style.css";

const App = () => {
  return (
    <CartProvider>
      <div>
        <Products />
        <Cart />
      </div>
    </CartProvider>
  );
};

render(<App />, document.getElementById("root"));

// Products.tsx
import React, { createContext, useCallback, useContext, useState } from "react";
import { AddCartContext } from "./context";
import { IProduct } from "./interface";
const Products = () => {
  const addItems = useContext(AddCartContext);

  const items = [
    {
      id: 1,
      name: "Product One",
      price: 20
    },
    {
      id: 2,
      name: "Product Two",
      price: 56
    },
    {
      id: 3,
      name: "Product Three",
      price: 13
    }
  ];

  const handleClick = (
    e: React.MouseEvent<HTMLInputElement, MouseEvent>,
    item: IProduct
  ) => {
    e.preventDefault();
    addItems(item);
  };

  const listItems = items.map(item => (
    <div key={item.id}>
      {`${item.name}: £${item.price}`}
      <input type="submit" value="+" onClick={e => handleClick(e, item)} />
    </div>
  ));

  return (
    <div>
      <div>
        <h2>Products</h2>
        {listItems}
      </div>
    </div>
  );
};

export default Products;

const Cart = () => {
  const items = useContext(CartContext);
  const cartItems = items.map((item, index) => (
    <div key={index}>{`${item.name}: £${item.price}`}</div>
  ));

  return (
    <div>
      <h2>Cart</h2>
      {cartItems}
    </div>
  );
};

// context.tsx
import React, { createContext, useCallback, useRef, useState } from "react";

export const CartContext = createContext([]);
export const AddCartContext = createContext(item => {});

export function CartProvider(props) {
  const [items, setItems] = useState([]);
  const itemsRef = useRef(items);
  itemsRef.current = items;

  return (
    <AddCartContext.Provider
      value={useCallback(item => {
        setItems([...itemsRef.current, item]);
      }, [])}
    >
      <CartContext.Provider value={items}>
        {props.children}
      </CartContext.Provider>
    </AddCartContext.Provider>
  );
}
1

If you want to share a property or function between multiple components you need to put that property or function in closest parent of those components so you can pass them as props.

In your case try to add your function to your App Component and then pass the function to both Products and Cart Components

Taghi Khavari
  • 6,272
  • 3
  • 15
  • 32
1

There are 2 work-arounds for your problem.

  1. You can make the Cart component as the child component of Products through which you can pass the addToCart() as Props to Cart. [but it is not meaningful]

  2. You can bring the state from Product Component to App i.e make the App as a stateful component and for products and Cart, make them as statelesss. Pass the data and methods as props.

For the second option, check the link.

teddcp
  • 1,514
  • 2
  • 11
  • 25
1

Take a look at the react docs for Lifting state up.

Move your cart state up into the closest common ancestor - App.

From App, pass cart and setCart as props into both Products and Cart as needed.

import React, { useState, Dispatch, SetStateAction } from "react";
import { render } from "react-dom";

interface IProduct {
  id: number;
  name: string;
  price: number;
}

const App = () => {
  const [cart, setCart] = useState<IProduct[]>([]);
  return (
    <div>
      <Products cart={cart} setCart={setCart} />
      <Cart cart={cart} />
    </div>
  );
};

function Cart({ cart = [] }: { cart: IProduct[] }) {
  return (
    <div>
      <h2>Cart</h2>
      {cart.map(item => (
        <div>{`${item.name}: £${item.price}`}</div>
      ))}
    </div>
  );
}

function Products({
  cart,
  setCart
}: {
  cart: IProduct[];
  setCart: Dispatch<SetStateAction<IProduct[]>>;
}) {
    const items: IProduct[] = [{id: 1,name: "Product One",price: 20},{id: 2,name: "Product Two",price: 56},{id: 3,name: "Product Three",price: 13}];

  const handleClick = (
    e: React.MouseEvent<HTMLInputElement, MouseEvent>,
    item: IProduct
  ) => {
    e.preventDefault();
    setCart([...cart, item]);
  };

  return (
    <div>
      <div>
        <h2>Products</h2>
        {items.map(item => (
          <div>
            {`${item.name}: £${item.price}`}
            <input
              type="submit"
              value="+"
              onClick={e => setCart([...cart, item])}
            />
          </div>
        ))}
      </div>
    </div>
  );
}

render(<App />, document.getElementById("root"));

Stackblitz

ksav
  • 20,015
  • 6
  • 46
  • 66
0

Keep a common items variable and a function addItems in App.tsx and you need to pass this function as prop to Product component which when adds a product will call this same function in App.tsx file and update the items list.And this items list can be passed to the Cart component. Here check this live demo:https://stackblitz.com/edit/react-ts-qq5cea?file=index.tsx

Sakshi
  • 1,464
  • 2
  • 8
  • 15
0

I improved your code a little, the main thing that needed to be done was to move the state to a higher level

enter image description here

https://stackblitz.com/edit/react-ts-iicy7v?file=Shop.tsx

const App = () => {
  return (
    <div>
      <Shop Products={Products} Cart={Cart} />
    </div>
  );
};

move logic to Shop:

const Shop = ({ Products, Cart }) => {
  const [cart, setCart] = useState([]);

  const addToCart = (item: IProduct) => {
    setCart([...cart, item]);
  };

  const removeFromCart = (item: IProduct) => {
    const itemWillBeRemoved = cart.find(e => e.id === item.id);
    const index = cart.indexOf(itemWillBeRemoved);
    const newCart = [...cart];
    newCart.splice(index, 1);
    setCart(newCart);
  };

  const items = [
    {
      id: 1,
      name: "Product One",
      price: 20
    },
    {
      id: 2,
      name: "Product Two",
      price: 56
    },
    {
      id: 3,
      name: "Product Three",
      price: 13
    }
  ];

  return (
    <div>
      <Products items={items} addToCart={addToCart} />
      <Cart items={cart} removeFromCart={removeFromCart} />
    </div>
  );
};

But the best way - use State Management

Redux Redix

Daniil Loban
  • 4,165
  • 1
  • 14
  • 20