As soon as my app loads the 'FETCH_PRODUCTS' action payload is dispatched, resulting in items sourced from the data.json file, being added to state in my products reducer. I am able to access this state
via console.log(action.payload) in both my actions and reducers files. I need to be able to iterate through the state object so I can render products from state in my Products component. However, I am unable to iterate thru the object. Ive tried with no luck, to convert it to an iterable array with Object.keys(), .values() and .entries().
This is what I get when console.log() action.payload or products in Products.js
Products.js
import React, { useState, useEffect } from "react";
import { Fade, Zoom } from "react-reveal";
import Modal from "react-modal";
import { connect, useDispatch } from "react-redux";
import fetchProducts from "../actions/productActions";
const Products = ({ products, add }) => {
const [product, setProduct] = useState(null);
const openModal = (product) => {
setProduct(product);
};
const closeModal = () => {
setProduct(null);
};
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
console.log(products)
const renderProducts = () => {
return products.map((product) => {
return (
<li key={product._id}>
<a href={"#" + product._id} onClick={() => openModal(product)}>
<img src={product.image} />
</a>
<p>
{product.title}
</p>
<p>
<strong>${product.price}.00</strong>
</p>
<button onClick={() => add(product)}>ADD TO CART</button>
</li>
);
});
};
return (
<div>
<Fade bottom cascade>
<div>Loading...</div>
<ul>{!products ? <div>Loading...</div> : renderProducts()}</ul>
</Fade>
{product && (
<Modal isOpen={true}>
<Zoom clear cascade>
<div>
<p onClick={() => closeModal()}>X CLOSE</p>
<img src={product.image} />
</div>
<div>
<p>{product.title}</p>
<p>${product.price}.00</p>
Sizes
<p>
Available Sizes
{product.availableSizes.map((size) => {
return (
<>
<br />
<span> {size} </span>
</>
);
})}
</p>
<button
onClick={() => {
add(product);
closeModal();
}}
>
ADD TO CART
</button>
</div>
</Zoom>
</Modal>
)}
</div>
);
};
export default connect((state) => ({ products: state.products.items }), {
fetchProducts,
})(Products);
productActions.js
import { FETCH_PRODUCTS } from "../components/types";
const fetchProducts = () => async(dispatch) => {
const result = await fetch('data/data.json');
const data = await result.json();
console.log(data);
dispatch({
type: FETCH_PRODUCTS,
payload: data
})
}
export default fetchProducts
productReducers.js
const { FETCH_PRODUCTS } = require("../components/types");
const data = require('../data.json')
const productsReducer = (state = {}, action) =>{
switch(action.type){
case FETCH_PRODUCTS:
console.log(action.payload)
return {items: action.payload}
default:
return state;
}
}
export default productsReducer;
store.js
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from "redux-thunk";
import productsReducer from "./reducers/productReducers";
const initialState = {};
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
combineReducers({ products: productsReducer }),
initialState,
composeEnhancer(applyMiddleware(thunk))
);
export default store;
You aren't accessing the data, because you didn't get the property from the object
Your redux store for products says this whenever you console.log(products)
{products : Array(6)}
So as a result you have to say products.products to properly map the array
Addressing undefined value
The problem is you are instantly returning the component when there is no data
const renderProducts = () => {
return products.map((product) => {
return (
<li key={product._id}>
<a href={"#" + product._id} onClick={() => openModal(product)}>
<img src={product.image} />
</a>
<p>
{product.title}
</p>
<p>
<strong>${product.price}.00</strong>
</p>
<button onClick={() => add(product)}>ADD TO CART</button>
</li>
);
});
};
Keep in mind that getting data takes time so you have to check if your products state is empty. This can be solve with a ternary operator:
return (
<div>
{doSomething ? something : null}
</div>
);
So in your case check if the products array is empty. If yes map through the array. If no then return "no products".
return (
<div>
{products.products.length !== 0 ? <your regular routine as above) : <p>No products</p>}
</div>
);
Finally figured it out!!! Needed to change payload: data to payload: data.products in productActions.js
Related
-Hello guys , I've encountered a problem which taken a lot time in research but I did not get a solution:
->here I have 2 separate components header.jsx(contains booking.com homepage clone) and hotel.jsx and I want to send a state containing search data from header to hotel using context and use reducer . I've declared a separate context file searchcontext.js ,exported the context and its provider , wrapped the app using the provider in index.js , and I've called the dispatch function in header.js in order to send the needed state , when I call useContext in hotel.js to get the state the console return undefined ,any help please :)
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { SearchContextProvider } from './context/searchcontext';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<SearchContextProvider>
<App />
</SearchContextProvider>
</React.StrictMode>
);
searchcontext.js
import { createContext, useReducer } from "react";
const INITIAL_STATE = {
city: undefined,
dates: [],
options: {
adult: undefined,
children: undefined,
room: undefined,
},
};
export const SearchContext = createContext(INITIAL_STATE);
const searchReducer = (action, state) => {
switch (action.type) {
case "NEW_SEARCH":
return action.payload;
case "RESET_SEARCH":
return INITIAL_STATE;
default:
return state;
}
};
export const SearchContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(searchReducer, INITIAL_STATE);
return (
<SearchContext.Provider
value={{
city: state.city,
dates: state.dates,
options: state.options,
dispatch,
}}
>
{children}
</SearchContext.Provider>
);
};
header.jsx file
import "./header.css";
import { DateRange } from "react-date-range";
import { useContext, useState } from "react";
import "react-date-range/dist/styles.css"; // main css file
import "react-date-range/dist/theme/default.css"; // theme css file
import { format } from "date-fns";
import { useNavigate } from "react-router-dom";
import {SearchContext} from "../../context/searchcontext";
const Header = ({ type }) => {
const [destination, setDestination] = useState("");
const [openDate, setOpenDate] = useState(false);
const [dates, setDates] = useState([
{
startDate: new Date(),
endDate: new Date(),
key: "selection",
},
]);
const [openOptions, setOpenOptions] = useState(false);
const [options, setOptions] = useState({
adult: 1,
children: 0,
room: 1,
});
const navigate = useNavigate();
const handleOption = (name, operation) => {
setOptions((prev) => {
return {
...prev,
[name]: operation === "i" ? options[name] + 1 : options[name] - 1,
};
});
};
const{dispatch}= useContext(SearchContext);
const handleSearch = () => {
dispatch({type:"NEW_SEARCH",payload:{destination,dates,options}});
console.log(true)
navigate("/hotels", { state: { destination, dates, options } });
};
return (
<div className="header">
<div className="headerContainer" >
<div className="headerSearch">
<div className="headerSearchItem">
<input
type="text"
placeholder="where are you going?"
className="headerSearchInput"
onChange={(e) => setDestination(e.target.value)}
/>
</div>
<div className="headerSearchItem">
<span
onClick={() => setOpenDate(!openDate)}
className="headerSearchText"
>
{`${format(dates[0].startDate, "dd/MM/yyyy")} to ${format(
dates[0].endDate,
"dd/MM/yyyy"
)}`}
</span>
{openDate && (
<DateRange
editableDateInputs={true}
onChange={(item) => setDates([item.selection])}
moveRangeOnFirstSelection={false}
ranges={dates}
minDate={new Date()}
className="date"
/>
)}
</div>
<div className="headerSearchItem">
<span
onClick={() => setOpenOptions(!openOptions)}
className="headerSearchText">
{`${options.adult}`} adult {`${options.children}`} children
{`${options.room}`} room
</span>
{openOptions && (
<div className="options">
<div className="option">
<span className="optionTitle">Adults</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("adult", "d");
}}
disabled={options.adult <= 1}
>
-
</button>
<span>{options.adult}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("adult", "i");
}}
>
+
</button>
</div>
</div>
<div className="option">
<span className="optionTitle">Children</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("children", "d");
}}
disabled={options.children <= 0}
>
-
</button>
<span>{options.children}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("children", "i");
}}
>
+
</button>
</div>
</div>
<div className="option">
<span className="optionTitle">Rooms</span>
<div className="optionCounter">
<button
className="optionCounterButton"
onClick={() => {
handleOption("room", "d");
}}
disabled={options.room <= 1}
>
-
</button>
<span>{options.room}</span>
<button
className="optionCounterButton"
onClick={() => {
handleOption("room", "i");
}}
>
+
</button>
</div>
</div>
</div>
)}
</div>
<div className="headerSearchItem">
<button className="headerBtn" onClick={handleSearch}>
Search
</button>
</div>
</div>
</>
)}
</div>
</div>
);
};
export default Header;
hotel.jsx file
import "./hotel.css";
import { useState } from "react";
import { useContext } from "react";
import { SearchContext } from "../../context/searchcontext";
const Hotel = () => {
const [sliderNumber, setSliderNumber] = useState(0);
const [openSlider, setOpenSlider] = useState(false);
//here where the problem encouters
const {state}= useContext(SearchContext);
console.log(state);//returns undefined
return (
<div style={{ margin: -8 }}>
<div className="hotelItemContainer">
<div className="hotelItemWrapper">
<button className="hotelBookBtn"> Reserve or Book Now!</button>
<h1 className="hotelTitle">lorem</h1>
<span className="hotelSpecFeatures">
You're eligible for a Genius discount! , to save at this property
, all you have to do is sign in.
</span>
</div>
</div>
</div>
);
};
export default Hotel;
In your hotel.js you are expecting a state property. However you have not been exposing one in your search context
searchcontext.js
Your context was not exposing a state
export const SearchContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(searchReducer, INITIAL_STATE);
return (
<SearchContext.Provider
value={{
city: state.city,
dates: state.dates,
options: state.options,
state, // you forgot to add this
dispatch,
}}
>
{children}
</SearchContext.Provider>
);
};
alternatively don't use the state property in hotel.js instead use the destructure values that are exposed in your context.
I'm doing project on React.js. I'm mapping the array and the error saying that the array is undefine even if it exists
<ul>
{details.extendedIngredients.map(ingredient => (
<li id={ingredient.id}>{ingredient.original}</li>
))}
</ul>
Full code:
import { useEffect, useState } from "react";
import styled from "styled-components";
import { useParams } from "react-router-dom";
function Recipe() {
let params = useParams();
const [details, setDetails] = useState({});
const [activeTab, setActiveTab] = useState("instructions");
const fetchDetails = async () => {
const data = await fetch(
`https://api.spoonacular.com/recipes/${params.name}/information?apiKey=${process.env.REACT_APP_API_KEY}`
);
const detailData = await data.json();
setDetails(detailData);
};
useEffect(() => {
fetchDetails();
}, [params.name]);
console.log(details.extendedIngredients);
return (
<DetailWrapper>
<div>
<h2>{details.title}</h2>
<img src={details.image} alt="" />
</div>
<Info>
<Button
className={activeTab === "instructions" ? "active" : ""}
onClick={() => setActiveTab("instructions")}
>
Instructions
</Button>
<Button
className={activeTab === "ingredients" ? "active" : ""}
onClick={() => setActiveTab("ingredients")}
>
Ingredients
</Button>
<div>
<h3 dangerouslySetInnerHTML={{ __html: details.summary }}></h3>
<h3 dangerouslySetInnerHTML={{ __html: details.instructions }}></h3>
</div>
<ul>
{details.extendedIngredients.map(ingredient => (
<li id={ingredient.id}>{ingredient.original}</li>
))}
</ul>
</Info>
</DetailWrapper>
)}
export default Recipe;
As setDetails supposed to save the details received from your API in an array, I guess that it must be initialised as an empty array
const [details, setDetails] = useState({});
As it will be an empty array, there will be no render when the component will be mounted from react.
Should be:
const [details, setDetails] = useState({});
edit this three parts:
first Part:
useEffect(() => {
fetchDetails().then(res=>
{setDetails(res.data); console.log(res)}
);
}, []);
second Part:
<ul>
{details?.extendedIngredients?.map(ingredient => (
<li id={ingredient.id}>{ingredient.original}</li>
))}
</ul>
third Part:
const fetchDetails = async (params) => {
const data = await fetch(
`https://api.spoonacular.com/recipes/${params.name}/information?
apiKey=${process.env.REACT_APP_API_KEY}`
return data;
);
I am creating a basic shopping cart app with Reactjs. I created a useContext file to make the states globally available.
Unfortunately, the objects in the useReducer state are not responding to action, except the array of products called 'cart'. The 'amount' and 'total' are not rendering.
Though, the actions can be seen to be updated when I checked the console log. That means I am not returning the right variables.
The action I want to achieve is that when <MdKeyboardArrowUp> is clicked, the 'amount' variable should increase by 1. It increases on console log but not rendered on the page.
The product list:
export default [
{
id: 1,
title: 'Samsung Galaxy S7',
price: 599.99,
img:
'https://res.cloudinary.com/diqqf3eq2/image/upload/v1583368215/phone-2_ohtt5s.png',
amount: 1,
},
useContext file
import React, {useState, useContext, useReducer, useEffect} from 'react';
import cartData from '../component/data'; //this is the source file for the product list//
import customReducer from './reducer'; //the file that handles the useReducer
const Appcontext = React.createContext();
const initialState = {
loading: false,
cart: cartData,
total: 0,
amount: 0,
}
const AppProvider = ({children}) =>{
const [state, dispatch] = useReducer(customReducer, initialState);
const increaseProduct = (id) =>{
dispatch({type: "INCREASE_PRODUCT", payload: id})
}
const decreaseProduct = (id) =>{
dispatch({type: "DECREASE_PRODUCT", payload: id})
}
return(
<Appcontext.Provider value={{...
state,
clearShopCart,
clearShopCart,
removeProduct,
decreaseProduct,
increaseProduct,
}}>
{children}
</Appcontext.Provider>
)
}
export const useGlobalContext = () =>{
return useContext(Appcontext)
}
export {Appcontext, AppProvider}
useReducer file
const customReducer = (state, action) => {
if(action.type === 'CLEAR_SHOPP_CART'){
return{...state, cart: []}
}
if(action.type === 'REMOVE_ITEM'){
const newProducts = state.cart.filter((singleProduct) => singleProduct.id !==
action.payload)
return{...state, cart: newProducts}
}
**if(action.type === "INCREASE_PRODUCT"){
let newValue = state.cart.map((singleProduct) => {
if(singleProduct.id === action.payload){
return {... singleProduct, amount: singleProduct.amount + 1}
}
return singleProduct
});
console.log(newValue)
return {...state, cart: newValue}** //these codes on bold format are the codes that
increases by 1 each time the button is clicked//
}
return state
}
export default customReducer;
The home file where the codes are rendered
import React, { useState, useEffect } from 'react';
import {HiShoppingCart} from 'react-icons/hi';
import {MdKeyboardArrowUp} from 'react-icons/md';
import {RiArrowDownSLine} from 'react-icons/ri';
import { useGlobalContext } from '../component/context';
export default function Home() {
const {cart, amount, total, clearShopCart, removeProduct, decreaseProduct, increaseProduct} =
useGlobalContext();
{cart.map((singleData) => {
const {id, title, price, img} = singleData;
return(
<>
<div key={id} className='product-container'>
<div className='img-container'>
<img src={img} alt={title} />
<div className='product-text-container'>
<h4>{title}</h4>
< h4>${price}</h4>
<h5 className='btn1' onClick={() => removeProduct(id)}>Remove</h5>
</div>
</div>
<div className='item-control'>
<MdKeyboardArrowUp className='iconUp' onClick={() =>
increaseProduct(id)}/> **//when clicked, should inrease 'amount' by
1**//
<p>{amount}</p>
{console.log(amount)}
<RiArrowDownSLine className='iconDown' onClick={() =>
decreaseProduct(id)}/>
</div>
</div>
</>
)
})}
I eventually found the solution. I needed to destructure the 'amount' object coming from the product array. That way, I was able to increase the individual products' amount. It should be like this while destructuring the array:
{cart.map((singleData) => {
const {id, title, price, img, amount} = singleData;
So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example
I have this project for pagination of json data received through an API. The problem is that my code somehow gives me a 'slice' error (it is not the case when using other API's, e.g. https://corona.lmao.ninja/v2/countries) <--- Works fine
Items.js:
import React from 'react';
import { ITEMS_PER_PAGE } from '../utils/constants';
import Data from './Data';
const Items = ({ items, page }) => {
const startIndex = (page - 1) * ITEMS_PER_PAGE;
const selectedItems = items.slice(startIndex, startIndex + ITEMS_PER_PAGE);
return (
<React.Fragment>
{selectedItems.map(item => (
<Data key={item.country} {...item} />
))}
</React.Fragment>
);
};
export default Items;
Data.js:
import React from 'react';
const Data = ({ Data }) => {
const { high, low } = Data;
return (
<div class="data">
<p>
<strong>Test:</strong> {high} {low}
</p>
<hr />
</div>
);
};
export default Data;
Pagination.js:
import React from 'react';
const Pagination = ({ totalPages, handleClick, page }) => {
const pages = [...Array(totalPages).keys()].map(number => number + 1);
return (
<div className="numbers">
{pages.map(number => (
<a
key={number}
href="/#"
onClick={() => handleClick(number)}
className={`${page === number && 'active'}`}
>
{number}
</a>
))}
</div>
);
};
export default Pagination;
App.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Pagination from './components/Pagination';
import Items from './components/Items';
import { ITEMS_PER_PAGE } from './utils/constants';
const App = () => {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(0);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
axios
.get('https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=10')
.then(response => {
const result = response.data;
setItems(result);
setTotalPages(Math.ceil(result.length / ITEMS_PER_PAGE));
setIsLoading(false);
});
}, []);
const handleClick = number => {
setPage(number);
};
return (
<div>
<h1>Pagination Demo</h1>
{isLoading ? (
<div className="loading">Loading...</div>
) : (
<React.Fragment>
<Items items={items} page={page} />
<Pagination
totalPages={totalPages}
handleClick={handleClick}
page={page}
/>
</React.Fragment>
)}
</div>
);
};
export default App;
My problem seems to be something that am I missing with this other API: https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=10
error: TypeError: items.slice is not a function in Items.js
Any help would be appreciated!
The response from the API has 2 nested Data keys, so it has to be like this:
const result = response.data;
setItems(result.Data.Data);
Data.js
import React from 'react';
const Data = ({ high, low }) => {
return (
<div class="data">
<p>
<strong>Test:</strong> {high} {low}
</p>
<hr />
</div>
);
};
export default Data;
demo: https://stackblitz.com/edit/react-arqaxj