From 63db4a3785a366aa9161b910e95d521d06467388 Mon Sep 17 00:00:00 2001 From: Godswill Francis Date: Wed, 19 Apr 2023 21:53:46 -0700 Subject: [PATCH] added navigation connecting redux between product page and product details --- App.js | 12 ++- package-lock.json | 153 ++++++++++++++++++++++++++- package.json | 4 +- src/navigation/Navigation.jsx | 23 ++-- src/redux/slice/ItemSlice.js | 24 +++++ src/redux/store.js | 8 ++ src/screens/ProductDetailsScreen.jsx | 22 ++-- src/screens/ProductScreen.jsx | 12 ++- 8 files changed, 228 insertions(+), 30 deletions(-) create mode 100644 src/redux/slice/ItemSlice.js create mode 100644 src/redux/store.js diff --git a/App.js b/App.js index 9a361bc..defb371 100644 --- a/App.js +++ b/App.js @@ -1,12 +1,16 @@ import React from 'react'; import { StatusBar, View } from 'react-native'; import Navigation from './src/navigation/Navigation'; +import { store } from './src/redux/store'; +import { Provider } from 'react-redux'; export default function App() { return ( - - - - + + + + + + ); } diff --git a/package-lock.json b/package-lock.json index d92d423..922bcc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,15 @@ "@expo/vector-icons": "^13.0.0", "@react-navigation/native": "^6.1.6", "@react-navigation/native-stack": "^6.9.12", + "@reduxjs/toolkit": "^1.9.5", "expo": "~48.0.9", "expo-app-loading": "^2.1.1", "expo-status-bar": "~1.4.4", "react": "18.2.0", "react-native": "0.71.6", "react-native-safe-area-context": "4.5.0", - "react-native-screens": "~3.20.0" + "react-native-screens": "~3.20.0", + "react-redux": "^8.0.5" }, "devDependencies": { "@babel/core": "^7.20.0" @@ -4796,6 +4798,29 @@ "nanoid": "^3.1.23" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz", + "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==", + "dependencies": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@segment/loosely-validate-event": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", @@ -4844,6 +4869,15 @@ "@sinonjs/commons": "^2.0.0" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -4870,11 +4904,36 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==" }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "18.0.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.37.tgz", + "integrity": "sha512-4yaZZtkRN3ZIQD3KSEwkfcik8s0SWV+82dlJot1AbGYHCzJkWP3ENBY6wYeDRmKZ6HkrgoGAmR2HqdwYGp6OEw==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", + "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/yargs": { "version": "15.0.15", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.15.tgz", @@ -6249,6 +6308,11 @@ "node": ">=8" } }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, "node_modules/dag-map": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/dag-map/-/dag-map-1.0.2.tgz", @@ -7850,6 +7914,19 @@ "node": ">=8" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hosted-git-info": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", @@ -7961,6 +8038,15 @@ "node": ">=4.0" } }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", @@ -11616,6 +11702,49 @@ "asap": "~2.0.6" } }, + "node_modules/react-redux": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", + "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", @@ -11677,6 +11806,22 @@ "node": ">=0.10.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -11822,9 +11967,9 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/reselect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.7.tgz", - "integrity": "sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==" + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" }, "node_modules/resolve": { "version": "1.22.1", diff --git a/package.json b/package.json index faef6e9..5f69074 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,15 @@ "@expo/vector-icons": "^13.0.0", "@react-navigation/native": "^6.1.6", "@react-navigation/native-stack": "^6.9.12", + "@reduxjs/toolkit": "^1.9.5", "expo": "~48.0.9", "expo-app-loading": "^2.1.1", "expo-status-bar": "~1.4.4", "react": "18.2.0", "react-native": "0.71.6", + "react-native-safe-area-context": "4.5.0", "react-native-screens": "~3.20.0", - "react-native-safe-area-context": "4.5.0" + "react-redux": "^8.0.5" }, "devDependencies": { "@babel/core": "^7.20.0" diff --git a/src/navigation/Navigation.jsx b/src/navigation/Navigation.jsx index e814c51..35fbedf 100644 --- a/src/navigation/Navigation.jsx +++ b/src/navigation/Navigation.jsx @@ -2,8 +2,7 @@ import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { Text, StyleSheet, TouchableOpacity } from 'react-native'; -import { FontAwesome } from '@expo/vector-icons'; - +import { Feather } from '@expo/vector-icons'; import ProductScreen from '../screens/ProductScreen'; import ProductDetailsScreen from '../screens/ProductDetailsScreen'; import CartScreen from '../screens/CartScreen'; @@ -11,9 +10,12 @@ import CartScreen from '../screens/CartScreen'; const Stack = createNativeStackNavigator(); const Navigation = () => { - const renderCartButton = () => ( - - + const renderCartButton = ({ navigation }) => ( + navigation.navigate('Shopping Cart')} + > + 1 ); @@ -24,10 +26,11 @@ const Navigation = () => { ({ headerStyle: styles.navigationHeader, headerTitleAlign: 'center', - }} + headerRight: () => renderCartButton({ navigation }), + })} /> { ({ + headerRight: () => renderCartButton({ navigation }), headerTitleAlign: 'center', - }} + })} /> diff --git a/src/redux/slice/ItemSlice.js b/src/redux/slice/ItemSlice.js new file mode 100644 index 0000000..09bac42 --- /dev/null +++ b/src/redux/slice/ItemSlice.js @@ -0,0 +1,24 @@ +import { createSlice } from '@reduxjs/toolkit'; +import products from '../../data/products'; + +const initialState = { + value: 0, + items: products, + selectedProduct: null, +}; + +export const itemSlice = createSlice({ + name: 'items', + initialState, + reducers: { + setSelectedProduct: (state, action) => { + const id = action.payload; + const itemExists = state.items.find((item) => item.id === id); + state.selectedProduct = itemExists; + }, + }, +}); + +export const { setSelectedProduct } = itemSlice.actions; + +export default itemSlice.reducer; diff --git a/src/redux/store.js b/src/redux/store.js new file mode 100644 index 0000000..065f5c6 --- /dev/null +++ b/src/redux/store.js @@ -0,0 +1,8 @@ +import { configureStore } from '@reduxjs/toolkit'; +import ItemSlice from './slice/ItemSlice'; + +export const store = configureStore({ + reducer: { + products: ItemSlice, + }, +}); diff --git a/src/screens/ProductDetailsScreen.jsx b/src/screens/ProductDetailsScreen.jsx index eb11123..d3a265c 100644 --- a/src/screens/ProductDetailsScreen.jsx +++ b/src/screens/ProductDetailsScreen.jsx @@ -10,11 +10,13 @@ import { TouchableOpacity, View, } from 'react-native'; -import products from '../data/products'; - -const product = products[0]; +import { useSelector } from 'react-redux'; const ProductDetailsScreen = ({ navigation }) => { + const selectedProduct = useSelector( + (state) => state.products.selectedProduct + ); + const renderItem = ({ item }) => { const screenWidth = Dimensions.get('window').width; return ( @@ -34,7 +36,7 @@ const ProductDetailsScreen = ({ navigation }) => { { style={{ backgroundColor: '#f6f6f6' }} /> - {product.name} - ${product.price.toFixed(2)} - {product.description} + {selectedProduct.name} + + ${selectedProduct.price.toFixed(2)} + + {selectedProduct.description} { onPress={() => navigation.navigate('Shopping Cart')} activeOpacity={0.7} > - Add {product.name} to Cart + + Add {selectedProduct.name} to Cart + diff --git a/src/screens/ProductScreen.jsx b/src/screens/ProductScreen.jsx index 028d95e..aff7ba6 100644 --- a/src/screens/ProductScreen.jsx +++ b/src/screens/ProductScreen.jsx @@ -1,9 +1,13 @@ import React from 'react'; import { FlatList, Pressable } from 'react-native'; import { Text, StyleSheet, Image, Dimensions } from 'react-native'; -import products from '../data/products'; +import { useSelector, useDispatch } from 'react-redux'; +import { setSelectedProduct } from '../redux/slice/ItemSlice'; const ProductScreen = ({ navigation }) => { + const products = useSelector((state) => state.products.items); + const dispatch = useDispatch(); + const { width } = Dimensions.get('window'); const itemWidth = (width - 20) / 2; // Subtracting 20 from width to account for margins @@ -12,14 +16,16 @@ const ProductScreen = ({ navigation }) => { data={products} renderItem={({ item }) => ( navigation.navigate('Product Details')} + onPress={() => { + dispatch(setSelectedProduct(item.id)); + navigation.navigate('Product Details'); + }} style={[styles.container, { width: itemWidth }]} > {item.name} )} - // keyExtractor={(item) => item.id.toString()} numColumns={2} contentContainerStyle={styles.list} />