How to pass data inside single product page ??
const store = configureStore({
reducer: {
cart: CartReducer,
product: ProductReducer,
},
});
productSlice
import { createSlice } from "#reduxjs/toolkit";
import { STATUS } from "./StatusSlice";
const ProductSlice = createSlice({
name: "product",
initialState: {
data: [],
status: STATUS.IDLE,
},
reducers: {
setProducts(state, action) {
state.data = action.payload;
},
setStatus(state, action) {
state.status = action.payload;
},
},
});
export const { setProducts, setStatus } = ProductSlice.actions;
export default ProductSlice.reducer;
export function fetchProducts() {
return async function fetchProduct(dispatch) {
dispatch(setStatus(STATUS.LOADING));
try {
const res = await fetch("https://fakestoreapi.com/products#");
const data = await res.json();
dispatch(setProducts(data));
dispatch(setStatus(STATUS.IDLE));
} catch (error) {
console.log(error);
dispatch(setStatus(STATUS.ERROR));
}
};
}
here is product page
const Products = () => {
const dispatch = useDispatch();
const { data: products, status } = useSelector((state) => state.product);
useEffect(() => {
dispatch(fetchProducts());
}, []);
if (status === STATUS.LOADING) {
return <h1>Loading......!!</h1>;
}
if (status === STATUS.ERROR) {
return <h1>Something went wrong</h1>;
}
return (
<>
<div className={style.wrapper}>
{products.map((product) => {
return (
<Link to={`/products/${product.id}`} key={product.id}>
// rest of the codes
</Link>
);
})}
</div>
</>
);
};
i'm not able to understand how get data in single product page
const SingleProduct = () => {
return (
// details
);
};
i had two option,
i can fetch api data in products page, single product page and wherever i want to or
i can fetch api data in one page (productSlice) and use everywhere that's why redux has been made.
Need Solution :
You are passing the product id as part of the URL path:
{products.map((product) => {
return (
<Link to={`/products/${product.id}`} key={product.id}>
// rest of the codes
</Link>
);
})}
Assuming you have a router and a route for path="/products/:productId" rendering the SingleProduct component:
<Routes>
...
<Route path="/products/:productId" element={<SingleProduct />} />
...
</Routes>
Then the SingleProduct component can use the useParams hook to access the productId route path parameter value and use this to filter the products array selected from the redux store.
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
const SingleProduct = () => {
const products = useSelector(state => state.product.data);
const { productId } = useParams();
const product = products.find(product => String(product.id) === productId);
if (!product) {
return "Sorry, no matching product found.";
}
return (
// render product details
);
};
Related
I used this code to show the id of a product.
import { useState } from 'react';
import axios from 'axios';
const BASE_URL = "https://makeup-api.herokuapp.com/api/v1/products";
const useGetProduct = () => {
const [products, setProducts] = useState([]);
const [singleProduct, setSingleProduct] = useState(null);
const getTopProducts = () => {
axios.get(`${BASE_URL}.json`, {
params: {
product_tags: 'Canadian',
},
})
.then(Response => setProducts(Response.data));
};
const getSingleProduct = () => {
axios.get(`${BASE_URL}/1048.jason`)
.then(Response => setSingleProduct(Response.data));
};
return {
products,
getTopProducts,
singleProduct,
getSingleProduct,
}
};
export default useGetProduct;
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import useGetProduct from "../hooks/useGetProduct";
const Product = () => {
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
return (
<div>
<p>Product: {id}</p>
</div>
);
};
export default Product;
app.js
<Route exact path="/product/:id" element={<Product />} />
But when I change <p>Product: {id}</p> to <p>Product: {singleProduct?.name}</p> the product name does not display. Instead, it just shows Product: without the details about the product on localhost and I am not sure why.
const Product = () => {
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
return (
<div>
<p>Product: {singleProduct?.name}</p>
</div>
);
};
export default Product;
I want to show details about a single product via a hook in react js.
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
You are not using the id from useParams. Shouldn't you pass it to getSingleProduct, I assume that singleProduct is undefined.
I have a NextJS application that is using the ShopifyBuy SDK. I have been successfully able to implement a solution where I am able to fetch the products from Store and display them to the User. The user is also able to go to a product page and add the product to the cart.
However, when the user refreshes the page, the cart is reset, and the data does not persist. The code is below:
context/cart.js:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
import Cookies from "js-cookie";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState = {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const savedState = Cookies.get("cartState");
const [state, dispatch] = useReducer(reducer, savedState || initalState);
useEffect(() => {
Cookies.set("cartState", state, { expires: 7 });
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
products/[handle].tsx:
import React, { useState, useEffect } from "react";
import client from "../../lib/client";
import { useCartDispatch, useCartState } from "../../context/cart";
import Link from "next/link";
import cookie from "js-cookie";
export const getStaticPaths = async () => {
const res = await client.product.fetchAll();
const paths = res.map((product: any) => {
return {
params: { handle: product.handle.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context: any) => {
const handle = context.params.handle;
const res = await client.product.fetchByHandle(handle);
const product = JSON.stringify(res);
return {
props: {
product,
},
};
};
function Product({ product }: any) {
const { state } = useCartState();
const { setCart } = useCartDispatch();
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
setCart(res);
};
product = JSON.parse(product);
return (
<div>
<div className=" flex-col text-2xl font-bold m-8 flex items-center justify-center ">
<h1>{product.title}</h1>
<button onClick={addToCart}>Add to Cart</button>
<Link href="/cart">Checkout</Link>
</div>
</div>
);
}
export default Product;
pages/cart/index.tsx:
import React, { useEffect } from "react";
import { useCartState, useCartDispatch } from "../../context/cart";
import client from "../../lib/client";
function Cart() {
const { state } = useCartState();
return (
<div>
<h1>Cart</h1>
{state.lineItems &&
state.lineItems.map((item: any) => {
return (
<div key={item.id}>
<h2>{item.title}</h2>
<p>{item.variant.title}</p>
<p>{item.quantity}</p>
</div>
);
})}
</div>
);
}
export default Cart;
I have tried using a library called js-cookie and also localStorage. I'm not sure where the problem lies or if the solutions that I've tried are wrong.
P.S.: I'm fairly new to NextJS and Typescript so go easy on the syntax. This code is for a personal project. Thanks in advance!
Answering this because I ended up coming up with a solution that works for me, at least.
Here it is:
const getCart = async () => {
try {
const checkoutId = Cookies.get("checkoutId");
let cart;
if (checkoutId) {
cart = await client.checkout.fetch(checkoutId);
} else {
cart = await client.checkout.create();
Cookies.set("checkoutId", cart.id);
}
setCart(cart);
} catch (err) {
console.log(err);
}
};
From my understanding, what this does is the following:
Check the cookies to see if one exists called "checkoutId"
If it exists, fetch the cart using that checkoutId
Otherwise, create a new cart and create a cookie using the cart.id that is returned in the response
Then, inside my individual Product page ([handle].tsx), I'm doing the following:
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
console.log(res);
if (cookie.get("checkoutId") === undefined) {
cookie.set("checkoutId", res.id);
}
setCart(res);
};
Using cookies to store your object cart, as far as I know, is not a good idea. You could use localStorage, like so:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState =
typeof localStorage !== "undefined" && localStorage.getItem("cartState")
? JSON.parse(localStorage.getItem("cartState"))
: {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initalState);
useEffect(() => {
localStorage.set("cartState", JSON.stringify(state));
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>{children}</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
I'm trying to display the response from the API into my react component but it's not working. If I try to use it in the console, I can see the data and its value but not in the react component, it's empty when I try to show the value in a div.
Here is the code where I'm trying to display it in my react component:
const CharacterListing = () => {
const characters = useSelector(getAllCharacters);
console.log("Hello", characters);
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
<div>{value.name}</div>
})
return (
<div>
{renderCharacters}
</div>
);
};
export default CharacterListing;
This is the code for my Character Slice Component
const initialState = {
characters: {},
};
const characterSlice = createSlice({
name: 'characters',
initialState,
reducers: {
addCharacters: (state, { payload }) => {
state.characters = payload;
},
},
});
export const { addCharacters } = characterSlice.actions;
export const getAllCharacters = (state) => state.characters.characters;
export default characterSlice.reducer;
This is the code for my Home Component:
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchCharacters = async () => {
const response = await baseURL.get(`/characters`)
.catch(error => {
console.log("Error", error);
});
dispatch(addCharacters(response.data));
console.log("Success", response);
};
fetchCharacters();
}, [])
return (
<div>
Home
<CharacterListing />
</div>
);
};
export default Home;
Thank you
You forgot to return item into your map func
Try this :
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
return <div key={key}>{value.name}</div>
})
I'm learning react context and while developing a todo application using useContext, I'm facing an issue where on submitting one task, the same task gets added two times to an array. The output component would loop through this array and display the results. While debugging I observed that, although the submit of task add only one entry into the array, not sure why and how, the consumer component gets the array with duplicate entry. Please let me know, what I'm missing.
Here is my code of index file that maintains context
import { createContext, useReducer } from "react";
import ContextReducer, { initialState } from "./ContextReducer";
const taskContext = createContext();
const ContextProvider = (props) => {
const [state, dispatch] = useReducer(ContextReducer, initialState);
const setTaskInput = (taskInput) => {
dispatch({
type: "SET_TASKINPUT",
payload: taskInput,
});
};
const addTask = (task) => {
dispatch({
type: "ADD_TASK",
payload: task,
});
};
const deleteTask = (id) => {
dispatch({
type: "DELETE_TASK",
payload: id,
});
};
const todoContext = {
todo: state.todo,
taskInput: state.taskInput,
setTaskInput,
addTask,
deleteTask,
};
return (
<taskContext.Provider value={todoContext}>
{props.children}
</taskContext.Provider>
);
};
export { taskContext };
export default ContextProvider;
This is the code for reducer
const initialState = {
todo: [],
taskInput: "",
};
const ContextReducer = (state = initialState, action) => {
if (action.type === "SET_TASKINPUT") {
state.taskInput = action.payload;
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "ADD_TASK") {
state.todo = [...state.todo, action.payload];
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
if (action.type === "DELETE_TASK") {
state.todo = state.todo.filter((todo) => todo.id !== action.payload);
return {
todo: state.todo,
taskInput: state.taskInput,
};
}
return state;
};
export { initialState };
export default ContextReducer;
This is the code of output component or say, consumer component
import React, { Fragment, useContext } from "react";
import { taskContext } from "../../Context";
import styles from "./Content.module.css";
const Output = () => {
const { todo, deleteTask } = useContext(taskContext);
const deleteHandler = (e) => {
deleteTask(+e.target.parentElement.parentElement.id);
};
return (
<Fragment>
{todo.length > 0 && (
<div className={styles.outputDiv}>
<ul>
{todo.map((task) => {
return (
<li key={task.id} id={task.id}>
<div className={styles.row1}>{task.task}</div>
<div className={styles.row2}>
<button className={styles.edit}>Edit</button>
<button className={styles.delete} onClick={deleteHandler}>
Delete
</button>
</div>
</li>
);
})}
</ul>
</div>
)}
</Fragment>
);
};
export default Output;
I'm using Redux Toolkit for the first time. Data is successfully available in the console, but when i try to render data in the UI, i'm getting undefined JSON path {${weather[0].description} ${weather[0].main}} Maybe i need to check something with if() statement but i don't know how and where. My own if() solution didn't do the trick in App.js
JSON data
description: "broken clouds"
icon: "04n"
id: 803
main: "Clouds"
[[Prototype]]: Object
length: 1
[[Prototype]]: Array(0)
App.js side
import { useDispatch, useSelector } from 'react-redux';
import { fetchWeatherAction } from './redux/slices/weatherSlices';
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchWeatherAction('Seoul'));
}, []);
const state = useSelector(state => state.weather);
const { loading, weather, error } = state || {};
if(!weather){
return null
}
console.log(weather);
return (
<div className="App">
<header className="App-header">
{weather.map((weather, index) => (
<div key={index}>
<div>{`${weather[0].description} ${weather[0].main}`}</div>
</div>
))}
</header>
</div>
);
}
export default App;```
Redux Toolkit side
``` import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import axios from 'axios';
export const fetchWeatherAction = createAsyncThunk(
'weather/fetch',
async (payload, {rejectWithValue, getState, dispatch})=>{
try{
const {data} = await axios.get(`http://api.openweathermap.org/data/2.5/weather?q=${payload}&appid=7469e38d322111e34a7027db2eee39c3`);
return data;
}catch(error){
if(!error?.response){
throw error
}
return rejectWithValue(error?.response?.data);
}
}
);
const weatherSlice = createSlice({
name: 'weather',
initialState: {},
extraReducers: builder => {
builder.addCase(fetchWeatherAction.pending, (state, action) => {
state.loading = true;
});
builder.addCase(fetchWeatherAction.fulfilled, (state, action) => {
state.weather = action?.payload;
state.loading = false;
state.error = undefined;
});
builder.addCase(fetchWeatherAction.rejected, (state, action) => {
state.loading = false;
state.weather = undefined;
state.error = action?.payload;
})
},
});
export default weatherSlice.reducer;```
It appears that you are mapping weather, which looks like an array of objects, and then trying to index into that object as e.g. weather[0].... If weather inside the map operation is in fact an object and not an array, this will not work. I think what you want is something like the following. Note that I've changed the name of the interior variable to weatherItem for clarity:
{weather.map((weatherItem, index) => (
<div key={index}>
<div>{`${weatherItem.description} ${weatherItem.main}`}</div>
</div>
))}