An open API service indexing awesome lists of open source software.

https://github.com/frempongdev/mini-android-shop-app

A mini Android Phone Shop that users can Add phones fetched from an API to their cart. Page also has a price and total element that displays phone prices and the total of the prices added to the cart. Built with React, Redux, JavaScript, HTML5 and CSS3.
https://github.com/frempongdev/mini-android-shop-app

css3 html5 javascript react redux-middleware

Last synced: about 2 months ago
JSON representation

A mini Android Phone Shop that users can Add phones fetched from an API to their cart. Page also has a price and total element that displays phone prices and the total of the prices added to the cart. Built with React, Redux, JavaScript, HTML5 and CSS3.

Awesome Lists containing this project

README

        

# Redux Toolkit

#### React Course

[My React Course](https://www.udemy.com/course/react-tutorial-and-projects-course/?referralCode=FEE6A921AF07E2563CEF)

#### Support

Find the App Useful? [You can always buy me a coffee](https://www.buymeacoffee.com/johnsmilga)

#### Docs

[Redux Toolkit Docs](https://redux-toolkit.js.org/introduction/getting-started)

#### Install Template

```sh
npx create-react-app my-app --template redux
```

- @latest

```sh
npx create-react-app@latest my-app --template redux
```

#### Existing App

```sh
npm install @reduxjs/toolkit react-redux
```

#### @reduxjs/toolkit

consists of few libraries

- redux (core library, state management)
- immer (allows to mutate state)
- redux-thunk (handles async actions)
- reselect (simplifies reducer functions)

#### Extras

- redux devtools
- combine reducers

#### react-redux

connects our app to redux

#### Setup Store

- create store.js

```js
import { configureStore } from '@reduxjs/toolkit';

export const store = configureStore({
reducer: {},
});
```

#### Setup Provider

- index.js

```js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// import store and provider
import { store } from './store';
import { Provider } from 'react-redux';

ReactDOM.render(




,
document.getElementById('root')
);
```

#### Setup Cart Slice

- application feature
- create features folder/cart
- create cartSlice.js

```js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
cartItems: [],
amount: 0,
total: 0,
isLoading: true,
};

const cartSlice = createSlice({
name: 'cart',
initialState,
});

console.log(cartSlice);

export default cartSlice.reducer;
```

- store.js

```js
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './features/cart/cartSlice';

export const store = configureStore({
reducer: {
cart: cartReducer,
},
});
```

#### Redux DevTools

- extension

#### Access store value

- create components/Navbar.js

```js
import { CartIcon } from '../icons';
import { useSelector } from 'react-redux';

const Navbar = () => {
const { amount } = useSelector((state) => state.cart);

return (


redux toolkit





{amount}






);
};
export default Navbar;
```

#### Hero Icons

- [Hero Icons](https://heroicons.com/)

```css
nav svg {
width: 40px;
color: var(--clr-white);
}
```

#### Setup Cart

- cartSlice.js

```js
import cartItems from '../../cartItems';

const initialState = {
cartItems: cartItems,
amount: 0,
total: 0,
isLoading: true,
};
```

- create CartContainer.js and CartItem.js
- CartContainer.js

```js
import React from 'react';
import CartItem from './CartItem';
import { useSelector } from 'react-redux';

const CartContainer = () => {
const { cartItems, total, amount } = useSelector((state) => state.cart);

if (amount < 1) {
return (

{/* cart header */}

your bag


is currently empty




);
}
return (

{/* cart header */}

your bag



{/* cart items */}

{cartItems.map((item) => {
return ;
})}

{/* cart footer */}





total ${total}



clear cart


);
};

export default CartContainer;
```

- CartItem.js

```js
import React from 'react';
import { ChevronDown, ChevronUp } from '../icons';

const CartItem = ({ id, img, title, price, amount }) => {
return (

{title}


{title}


${price}


{/* remove button */}
remove


{/* increase amount */}



{/* amount */}

{amount}


{/* decrease amount */}





);
};

export default CartItem;
```

#### First Reducer

- cartSlice.js
- Immer library

```js
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
clearCart: (state) => {
state.cartItems = [];
},
},
});

export const { clearCart } = cartSlice.actions;
```

- create action

```js
const ACTION_TYPE = 'ACTION_TYPE';

const actionCreator = (payload) => {
return { type: ACTION_TYPE, payload: payload };
};
```

- CartContainer.js

```js
import React from 'react';
import CartItem from './CartItem';
import { useDispatch, useSelector } from 'react-redux';

const CartContainer = () => {
const dispatch = useDispatch();

return (
{
dispatch(clearCart());
}}
>
clear cart

);
};

export default CartContainer;
```

#### Remove, Increase, Decrease

- cartSlice.js

```js
import { createSlice } from '@reduxjs/toolkit';
import cartItems from '../../cartItems';

const initialState = {
cartItems: [],
amount: 0,
total: 0,
isLoading: true,
};

const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
clearCart: (state) => {
state.cartItems = [];
},
removeItem: (state, action) => {
const itemId = action.payload;
state.cartItems = state.cartItems.filter((item) => item.id !== itemId);
},
increase: (state, { payload }) => {
const cartItem = state.cartItems.find((item) => item.id === payload.id);
cartItem.amount = cartItem.amount + 1;
},
decrease: (state, { payload }) => {
const cartItem = state.cartItems.find((item) => item.id === payload.id);
cartItem.amount = cartItem.amount - 1;
},
calculateTotals: (state) => {
let amount = 0;
let total = 0;
state.cartItems.forEach((item) => {
amount += item.amount;
total += item.amount * item.price;
});
state.amount = amount;
state.total = total;
},
},
});

export const { clearCart, removeItem, increase, decrease, calculateTotals } =
cartSlice.actions;

export default cartSlice.reducer;
```

- CartItem.js

```js
import React from 'react';
import { ChevronDown, ChevronUp } from '../icons';

import { useDispatch } from 'react-redux';
import { removeItem, increase, decrease } from '../features/cart/cartSlice';

const CartItem = ({ id, img, title, price, amount }) => {
const dispatch = useDispatch();

return (

{title}


{title}


${price}


{/* remove button */}
{
dispatch(removeItem(id));
}}
>
remove



{/* increase amount */}
{
dispatch(increase({ id }));
}}
>


{/* amount */}

{amount}


{/* decrease amount */}
{
if (amount === 1) {
dispatch(removeItem(id));
return;
}
dispatch(decrease({ id }));
}}
>




);
};

export default CartItem;
```

- App.js

```js
import { useEffect } from 'react';
import Navbar from './components/Navbar';
import CartContainer from './components/CartContainer';
import { useSelector, useDispatch } from 'react-redux';
import { calculateTotals } from './features/cart/cartSlice';

function App() {
const { cartItems } = useSelector((state) => state.cart);
const dispatch = useDispatch();
useEffect(() => {
dispatch(calculateTotals());
}, [cartItems]);

return (




);
}

export default App;
```

#### Modal

- create components/Modal.js

```js
const Modal = () => {
return (


Remove all items from your shopping cart?




confirm


cancel




);
};
export default Modal;
```

- App.js

```js
return (





);
```

#### modal slice

- create features/modal/modalSlice.js

```js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
isOpen: false,
};

const modalSlice = createSlice({
name: 'modal',
initialState,
reducers: {
openModal: (state, action) => {
state.isOpen = true;
},
closeModal: (state, action) => {
state.isOpen = false;
},
},
});

export const { openModal, closeModal } = modalSlice.actions;
export default modalSlice.reducer;
```

- App.js

```js
const { isOpen } = useSelector((state) => state.modal);

return (

{isOpen && }



);
```

#### toggle modal

- CartContainer.js

```js
import { openModal } from '../features/modal/modalSlice';

return (
{
dispatch(openModal());
}}
>
clear cart

);
```

- Modal.js

```js
import { closeModal } from '../features/modal/modalSlice';
import { useDispatch } from 'react-redux';
import { clearCart } from '../features/cart/cartSlice';

const Modal = () => {
const dispatch = useDispatch();

return (


Remove all items from your shopping cart?



{
dispatch(clearCart());
dispatch(closeModal());
}}
>
confirm

{
dispatch(closeModal());
}}
>
cancel




);
};
export default Modal;
```

#### async functionality with createAsyncThunk

- [Course API](https://course-api.com/)
- https://course-api.com/react-useReducer-cart-project
- cartSlice.js

- action type
- callback function
- lifecycle actions

```js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

const url = 'https://course-api.com/react-useReducer-cart-project';

export const getCartItems = createAsyncThunk('cart/getCartItems', () => {
return fetch(url)
.then((resp) => resp.json())
.catch((err) => console.log(error));
});

const cartSlice = createSlice({
name: 'cart',
initialState,
extraReducers: {
[getCartItems.pending]: (state) => {
state.isLoading = true;
},
[getCartItems.fulfilled]: (state, action) => {
console.log(action);
state.isLoading = false;
state.cartItems = action.payload;
},
[getCartItems.rejected]: (state) => {
state.isLoading = false;
},
},
});
```

- App.js

```js
import { calculateTotals, getCartItems } from './features/cart/cartSlice';

function App() {
const { cartItems, isLoading } = useSelector((state) => state.cart);

useEffect(() => {
dispatch(getCartItems());
}, []);

if (isLoading) {
return (


Loading...



);
}

return (

{isOpen && }



);
}

export default App;
```

#### Options

```sh
npm install axios
```

- cartSlice.js

```js
export const getCartItems = createAsyncThunk(
'cart/getCartItems',
async (name, thunkAPI) => {
try {
// console.log(name);
// console.log(thunkAPI);
// console.log(thunkAPI.getState());
// thunkAPI.dispatch(openModal());
const resp = await axios(url);

return resp.data;
} catch (error) {
return thunkAPI.rejectWithValue('something went wrong');
}
}
);
```

#### The extraReducers "builder callback" notation

cart/cartSlice

```js
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
// reducers
},
extraReducers: (builder) => {
builder
.addCase(getCartItems.pending, (state) => {
state.isLoading = true;
})
.addCase(getCartItems.fulfilled, (state, action) => {
// console.log(action);
state.isLoading = false;
state.cartItems = action.payload;
})
.addCase(getCartItems.rejected, (state, action) => {
console.log(action);
state.isLoading = false;
});
},
});
```