Thursday, February 4, 2021

Typescript | React | Sample Code | Minimal Shopping Cart Sample

weekly25

(https://simpledoers.com/weekly25/)

npx create-react-app ui --template typescript
cd ui
npm i @material-ui/core @material-ui/icons
npm i react-query
npm i styled-components @types/styled-components
- src/App.tsx
- src/index.tsx
- react-app-env.d.ts
  • index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render( <App />, document.querySelector('#root'));
  • App.tsx
const App = () => {
  return (
    <div className="App">
      Strarting..
    </div>
  );
}
export default App;

styled-components useState useQuery

index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { QueryClient, QueryClientProvider} from 'react-query';

const client = new QueryClient();
ReactDOM.render( 
  <QueryClientProvider client={client} >
    <App />
  </QueryClientProvider>
  , document.querySelector('#root'));

App.tsx

import { useState } from 'react';
import { useQuery } from 'react-query';
// Components
import Drawer from '@material-ui/core/Drawer';
import LinearProgress from '@material-ui/core/LinearProgress';
import Grid from '@material-ui/core/Grid';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';
import Badge from '@material-ui/core/Badge';
// Styles
import { Wrapper } from './App.styles';
// Types
export type CartItemType =  {
  id: number;
  category: string;
  description: string;
  image: string;
  price: number;
  title: string;
  amount: number;
};

const getProducts = async (): Promise<CartItemType[]> => 
  await (await fetch('https://fakestoreapi.com/products')).json();

const App = () => {
  const { data, isLoading, error } = 
    useQuery<CartItemType[]>('products', getProducts);
  console.log(data);
  return (
    <div className="App">
      Strarting..
    </div>
  );
}
export default App;

App.styled.ts

import styled from 'styled-components';
export const Wrapper = styled.div``;
npm run start

Item Cart styled-component Typed

App.tsx

import { useState } from 'react';
import { useQuery } from 'react-query';
// Components
import Item from './Item/Item';
import Drawer from '@material-ui/core/Drawer';
import LinearProgress from '@material-ui/core/LinearProgress';
import Grid from '@material-ui/core/Grid';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';
import Badge from '@material-ui/core/Badge';
// Styles
import { Wrapper } from './App.styles';
// Types
export type CartItemType =  {
  id: number;
  category: string;
  description: string;
  image: string;
  price: number;
  title: string;
  amount: number;
};

const getProducts = async (): Promise<CartItemType[]> => 
  await (await fetch('https://fakestoreapi.com/products')).json();

const App = () => {

  const { data, isLoading, error } = 
    useQuery<CartItemType[]>('products', getProducts);

  console.log(data);

  const getTotalItems =  () => null;

  const handleAddToCart = (clickedItem: CartItemType) => null;

  const removeFromCart = () => null;

  if(isLoading) return <LinearProgress />;
  if(error) return <div>Something went wrong ...</div>;

  return (
    <Wrapper>
      <Grid container spacing={3}>
        {data?.map((item: CartItemType) => (
          <Grid item key={item.id} xs={12} sm={4} >
            <Item item={item} handleAddToCart={handleAddToCart} /> 
          </Grid>
        ))}

      </Grid>
    </Wrapper>
  );
}
export default App;

Item.tsx

import Button from '@material-ui/core/Button';
// Types
import { CartItemType } from '../App';
// Styles
import { Wrapper } from './Item.styles';

type Props = {
  item: CartItemType;
  handleAddToCart: (clickedItem: CartItemType) => void;
}

const Item: React.FC<Props> = ({item, handleAddToCart}) => (
  <Wrapper>
    <img src={item.image} alt={item.title} ></img>
    <div>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
      <h3>$ {item.price}</h3>
    </div>
    <Button onClick={ () => handleAddToCart(item)} > Add to Cart</Button>
  </Wrapper>
);

export default Item;

Item.styles.ts

import styled from 'styled-components';

export  const Wrapper = styled.div`
  display: flex;
  justify-content: space-between;
  flex-direction: column;
  width: 100%;
  border: 2px solid grey;
  border-radius: 20px;
  height: 100%;

  button {
    border-radius: 0 0 20px 20px;
  }

  img {
    max-height: 250px;
    object-fit: cover;
    border-radius: 20px 20px 0 0;

  }

  div {
    font-family: Arial, Helvetica, sans-serif;
    padding: 1rem;
    height: 100%;

  }
`;

Cart open, add, amount

// index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { QueryClient, QueryClientProvider} from 'react-query';

const client = new QueryClient();
ReactDOM.render( 
  <QueryClientProvider client={client} >
    <App />
  </QueryClientProvider>, 
  document.querySelector('#root'));
// App.tsx
import { useState } from 'react';
import { useQuery } from 'react-query';
// Components
import Item from './Item/Item';
import Drawer from '@material-ui/core/Drawer';
import LinearProgress from '@material-ui/core/LinearProgress';
import Grid from '@material-ui/core/Grid';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';
import Badge from '@material-ui/core/Badge';
// Styles
import { Wrapper, StyledButton } from './App.styles';
// Types
export type CartItemType =  {
  id: number;
  category: string;
  description: string;
  image: string;
  price: number;
  title: string;
  amount: number;
};

const getProducts = async (): Promise<CartItemType[]> => 
  await (await fetch('https://fakestoreapi.com/products')).json();

const App = () => {

  const [cartOpen, setCartOpen] = useState(false);
  const [cartItems, setCartItems] = useState([] as CartItemType[]);

  const { data, isLoading, error } = 
    useQuery<CartItemType[]>('products', getProducts);

  console.log(data);

  const getTotalItems =  (items: CartItemType[]) => null;

  const handleAddToCart = (clickedItem: CartItemType) => null;

  const removeFromCart = () => null;

  if(isLoading) return <LinearProgress />;
  if(error) return <div>Something went wrong ...</div>;
  return (
    <Wrapper>
      <Drawer anchor='right' open={cartOpen} onClose={ () => setCartOpen(false) }> 
        Cart goes here
      </Drawer>
      <StyledButton onClick={ ( ) => setCartOpen(true)} >
        <Badge badgeContent={getTotalItems(cartItems)} color='error'>
          <AddShoppingCartIcon />
        </Badge>
      </StyledButton>
      <Grid container spacing={3}>
        {data?.map((item: CartItemType) => (
          <Grid item key={item.id} xs={12} sm={4} >
            <Item item={item} handleAddToCart={handleAddToCart} /> 
          </Grid>
        ))}

      </Grid>
    </Wrapper>
  );
}
export default App;
// App.styles.ts
import styled from 'styled-components';
import IconButton from '@material-ui/core/IconButton';
export const Wrapper = styled.div`
  margin: 40px;
`;

export const StyledButton = styled(IconButton)`
  position: fixed;
  z-index: 100;
  right: 20px;
  top: 20px;
`;
// Item.tsx
import Button from '@material-ui/core/Button';
// Types
import { CartItemType } from '../App';
// Styles
import { Wrapper } from './Item.styles';

type Props = {
  item: CartItemType;
  handleAddToCart: (clickedItem: CartItemType) => void;
}

const Item: React.FC<Props> = ({item, handleAddToCart}) => (
  <Wrapper>
    <img src={item.image} alt={item.title} ></img>
    <div>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
      <h3>$ {item.price}</h3>
    </div>
    <Button onClick={ () => handleAddToCart(item)} > Add to Cart</Button>
  </Wrapper>
);

export default Item;
// item.styled.ts
import styled from 'styled-components';

export  const Wrapper = styled.div`
  display: flex;
  justify-content: space-between;
  flex-direction: column;
  width: 100%;
  border: 1px solid lightred;
  border-radius: 20px;
  height: 100%;

  button {
    border-radius: 0 0 20px 20px;
  }

  img {
    max-height: 250px;
    object-fit: cover;
    border-radius: 20px 20px 0 0;

  }

  div {
    font-family: Arial, Helvetica, sans-serif;
    padding: 1rem;
    height: 100%;

  }
`;

Cart Menu Empty Items

// App.tsx
import { useState } from 'react';
import { useQuery } from 'react-query';
// Components
import Item from './Item/Item';
import Cart from './Cart/Cart';
import Drawer from '@material-ui/core/Drawer';
import LinearProgress from '@material-ui/core/LinearProgress';
import Grid from '@material-ui/core/Grid';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';
import Badge from '@material-ui/core/Badge';
// Styles
import { Wrapper, StyledButton } from './App.styles';
// Types
export type CartItemType =  {
  id: number;
  category: string;
  description: string;
  image: string;
  price: number;
  title: string;
  amount: number;
};

const getProducts = async (): Promise<CartItemType[]> => 
  await (await fetch('https://fakestoreapi.com/products')).json();

const App = () => {

  const [cartOpen, setCartOpen] = useState(false);
  const [cartItems, setCartItems] = useState([] as CartItemType[]);

  const { data, isLoading, error } = 
    useQuery<CartItemType[]>('products', getProducts);

  console.log(data);

  const getTotalItems =  (items: CartItemType[]) => 
    items.reduce( (acc: number, item) => acc + item.amount, 0);

  const handleAddToCart = (clickedItem: CartItemType) => null;

  const handleRemoveFromCart = () => null;

  if(isLoading) return <LinearProgress />;
  if(error) return <div>Something went wrong ...</div>;
  return (
    <Wrapper>
      <Drawer anchor='right' open={cartOpen} onClose={ () => setCartOpen(false) }> 
        <Cart cartItems={cartItems}  
              addToCart={handleAddToCart} 
              removeFromCart={handleRemoveFromCart} />
      </Drawer>
      <StyledButton onClick={ ( ) => setCartOpen(true)} >
        <Badge badgeContent={getTotalItems(cartItems)} color='error'>
          <AddShoppingCartIcon />
        </Badge>
      </StyledButton>
      <Grid container spacing={3}>
        {data?.map((item: CartItemType) => (
          <Grid item key={item.id} xs={12} sm={4} >
            <Item item={item} handleAddToCart={handleAddToCart} /> 
          </Grid>
        ))}

      </Grid>
    </Wrapper>
  );
}
export default App;
// Cart/Cart.tsx
import CartItem from '../CartItem/CartItem';
// Styles
import { Wrapper } from './Cart.styles';
// Types
import { CartItemType } from '../App';

type Props = {
  cartItems: CartItemType[];
  addToCart: (clickedItem: CartItemType) => void;
  removeFromCart: (id: number) => void;

};

const Cart: React.FC<Props> = ({cartItems, addToCart, removeFromCart}) => {
  return (
    <Wrapper>
      <h2>Your Shopping Cart</h2>
      {cartItems.length === 0 ? <p>Empty Cart</p> : null }
      {cartItems.map( (item) => (
        <CartItem />
      ))}
    </Wrapper>
  );
};

export default Cart;
// Cart/Cart.styles.ts
import styled from 'styled-components';

export const Wrapper = styled.aside`
  font-family: Arial, Helvetica, sans-serif;
  width: 500px;
  padding: 20px;

`;
// CartItem/CartItem.tsx
import Button from '@material-ui/core/Button';
// Types
import { CartItemType } from '../App';
// Styles
import { Wrapper } from './CartItem.styles';

const CartItem: React.FC = () => <div>Cart Item</div>;

export default CartItem;
// CartItem/CartItem.styles.ts
import styled from 'styled-components';

export const Wrapper = styled.div`
  
`;

Cart add and remove items

// App.tsx
import { useState } from 'react';
import { useQuery } from 'react-query';
// Components
import Item from './Item/Item';
import Cart from './Cart/Cart';
import Drawer from '@material-ui/core/Drawer';
import LinearProgress from '@material-ui/core/LinearProgress';
import Grid from '@material-ui/core/Grid';
import AddShoppingCartIcon from '@material-ui/icons/AddShoppingCart';
import Badge from '@material-ui/core/Badge';
// Styles
import { Wrapper, StyledButton } from './App.styles';
// Types
export type CartItemType =  {
  id: number;
  category: string;
  description: string;
  image: string;
  price: number;
  title: string;
  amount: number;
};

const getProducts = async (): Promise<CartItemType[]> => 
  await (await fetch('https://fakestoreapi.com/products')).json();

const App = () => {

  const [cartOpen, setCartOpen] = useState(false);
  const [cartItems, setCartItems] = useState([] as CartItemType[]);

  const { data, isLoading, error } = 
    useQuery<CartItemType[]>('products', getProducts);

  console.log(data);

  const getTotalItems =  (items: CartItemType[]) => 
    items.reduce( (acc: number, item) => acc + item.amount, 0);

  const handleAddToCart = (clickedItem: CartItemType) => {
    setCartItems( prev => {
      const isItemInCart = prev.find(item => item.id === clickedItem.id);

      if(isItemInCart){
        return prev.map( item =>
          item.id === clickedItem.id
          ? { ...item, amount: item.amount + 1}
          : item
        );
      }else{
        return [ ...prev, { ...clickedItem, amount: 1 } ];
      }
    });
  };

  const handleRemoveFromCart = (id: number) => {
    setCartItems( prev => 
      prev.reduce( (acc, item) => {
        if(item.id === id){
          if( item.amount === 1) return acc;
          return [ ...acc, { ...item, amount: item.amount - 1 }];
        }else{
          return [ ...acc, item];
        }
      }, [] as CartItemType[] )
    );
  };

  if(isLoading) return <LinearProgress />;
  if(error) return <div>Something went wrong ...</div>;
  return (
    <Wrapper>
      <Drawer anchor='right' open={cartOpen} onClose={ () => setCartOpen(false) }> 
        <Cart cartItems={cartItems}  
              addToCart={handleAddToCart} 
              removeFromCart={handleRemoveFromCart} />
      </Drawer>
      <StyledButton onClick={ ( ) => setCartOpen(true)} >
        <Badge badgeContent={getTotalItems(cartItems)} color='error'>
          <AddShoppingCartIcon />
        </Badge>
      </StyledButton>
      <Grid container spacing={3}>
        {data?.map((item: CartItemType) => (
          <Grid item key={item.id} xs={12} sm={4} >
            <Item item={item} handleAddToCart={handleAddToCart} /> 
          </Grid>
        ))}

      </Grid>
    </Wrapper>
  );
}
export default App;
// Cart/Cart.tsx
import CartItem from '../CartItem/CartItem';
// Styles
import { Wrapper } from './Cart.styles';
// Types
import { CartItemType } from '../App';

type Props = {
  cartItems: CartItemType[];
  addToCart: (clickedItem: CartItemType) => void;
  removeFromCart: (id: number) => void;

};

const Cart: React.FC<Props> = ({cartItems, addToCart, removeFromCart}) => {

  const calculateTotal = (items: CartItemType[]) =>
    items.reduce( (acc: number, item: CartItemType) => acc + item.amount * item.price, 0); 


  return (
    <Wrapper>
      <h2>Your Shopping Cart</h2>
      {cartItems.length === 0 ? <p>Empty Cart</p> : null }
      {cartItems.map( (item) => (
        <CartItem
          key={item.id}
          item={item}
          addToCart={addToCart}
          removeFromCart={removeFromCart}
        />
      ))}
      <p>Total: $ {calculateTotal(cartItems).toFixed(2)}</p>
    </Wrapper>
  );
};

export default Cart;
// CartItem/CartItem.tsx
import Button from '@material-ui/core/Button';
// Types
import { CartItemType } from '../App';
//import Item from '../Item/Item';
// Styles
import { Wrapper } from './CartItem.styles';

type Props = {
  item: CartItemType;
  addToCart: (clickedItem: CartItemType) => void;
  removeFromCart: (id: number) => void;
};

const CartItem: React.FC<Props> = ({item, addToCart, removeFromCart}) => (
  <Wrapper>
    <div>
      <h3>{item.title}</h3>
      <div className='information'>
        <p>Price: $ {item.price}</p>
        <p>Total: $ {(item.amount * item.price).toFixed(2)}</p>
      </div>
      <div className='buttons'>
        <Button 
          size='small'
          disableElevation
          variant='contained'
          onClick={() => removeFromCart(item.id)} 
          >-</Button>
        <p>{item.amount}</p>
        <Button 
          size='small'
          disableElevation
          variant='contained'
          onClick={() => addToCart(item)}
        >+</Button>
      </div>
    </div>
    <img src={item.image} alt={item.title}  />
  </Wrapper>
);

export default CartItem;
// CartItem/CartItem.styles.ts
import styled from 'styled-components';

export const Wrapper = styled.div`
  display: flex;
  justify-content: space-between;
  font-family: Arial, Helvetica, sans-serif;
  border-bottom: 1px solid grey;
  padding-bottom: 20px;

  div {
    flex: 1;
  }

  .buttons, .information {
     display: flex;
     justify-content: space-beetwen;
  }

  img {
    max-width: 80px;
    object-fit: cover;
    margin-left: 40px; 
  }
`;

(https://simpledoers.com/weekly25/)