Can not render/display the fetched data from an api using, ReactJs, Axios, Redux - reactjs

this is where all the things are happening
const Home = () => {
//FETCHING THE INFORAMATION
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchHeroes());
}, [Home]);
//PULLING THE DATA OR EXTRACTING IT FROM THE STATE
const { heroesInfo, heroName, heroType, attackType, mostPicked } =
useSelector((state) => state.HeroesInfoAll);
console.log(heroesInfo);
return (
<div>
<HeroList>
{heroesInfo.map((heroes) => {
<Heroes />;
})}
</HeroList>
</div>
);
};
I am also learning about Redux. I have use the reducer which has these arrays in which I want to pass the values accordingly for now though I am only passing the value to the "heroesInfo" array
const initState = {
heroesInfo: [],
heroName: [],
heroType: [],
attackType: [],
mostPicked: [],
};
const HInfoReducer = (state = initState, action) => {
switch (action.type) {
case "FETCH_HEROES":
return { ...state, heroesInfo: action.payload.heroesInfo };
default:
return { ...state };
}
};
export default HInfoReducer;
This is the Heroes component Which I want to render out for each data value present in state which you can see in the first code snippet
const Heroes = () => {
return (
<>
<h1>Hero Name</h1>
<div className="hero-image">
<img src="" alt="Hero Image" />
</div>
<h2>Hero Type</h2>
<h2></h2>
</>
);
};
export default Heroes;
I also console logged out some results to confirm the data was present in the state or not. Installed Redux tools to check it out as well here is the result
[this image shows that the data was extracted for sure after the FETCH_HEROES action ran][1]
[Here I console logged out the heroesInfo array which has the data which I want in it however on the left side my screen is completely blank. I expect it to render out my component for each element present inside of the array][2]
[1]: https://i.stack.imgur.com/nRqZr.png
[2]: https://i.stack.imgur.com/CZbHn.png
I hope I don't get banned this time, I really don't know how to ask questions but all I want to know is why is the component not being rendered out even though the data is present in their?

Please check HeroList component, if the component is returning proper data.

I rewrote the code and can get the data now but I have a new problem which I will be asking soon.

Related

React useReducer bug while updating state array

I haven't been in React for a while and now I am revising. Well I faced error and tried debugging it for about 2hours and couldn't find bug. Well, the main logic of program goes like this:
There is one main context with cart object.
Main property is cart array where I store all products
If I add product with same name (I don't compare it with id's right now because it is small project for revising) it should just sum up old amount of that product with new amount
Well, I did all logic for adding but the problem started when I found out that for some reason when I continue adding products, it linearly doubles it up. I will leave github link here if you want to check full aplication. Also, there I will leave only important components. Maybe there is small mistake which I forget to consider. Also I removed logic for summing up amount of same products because that's not neccesary right now. Pushing into state array is important.
Github: https://github.com/AndNijaz/practice-react-
//Context
import React, { useEffect, useReducer, useState } from "react";
const CartContext = React.createContext({
cart: [],
totalAmount: 0,
totalPrice: 0,
addToCart: () => {},
setTotalAmount: () => {},
setTotalPrice: () => {},
});
const cartAction = (state, action) => {
const foodObject = action.value;
const arr = [];
console.log(state.foodArr);
if (action.type === "ADD_TO_CART") {
arr.push(foodObject);
state.foodArr = [...state.foodArr, ...arr];
return { ...state };
}
return { ...state };
};
export const CartContextProvider = (props) => {
const [cartState, setCartState] = useReducer(cartAction, {
foodArr: [],
totalAmount: 0,
totalPrice: 0,
});
const addToCart = (foodObj) => {
setCartState({ type: "ADD_TO_CART", value: foodObj });
};
return (
<CartContext.Provider
value={{
cart: cartState.foodArr,
totalAmount: cartState.totalAmount,
totalPrice: cartState.totalAmount,
addToCart: addToCart,
}}
>
{props.children}
</CartContext.Provider>
);
};
export default CartContext;
//Food.js
import React, { useContext, useState, useRef, useEffect } from "react";
import CartContext from "../../context/cart-context";
import Button from "../ui/Button";
import style from "./Food.module.css";
const Food = (props) => {
const ctx = useContext(CartContext);
const foodObj = props.value;
const amountInput = useRef();
const onClickHandler = () => {
const obj = {
name: foodObj.name,
description: foodObj.description,
price: foodObj.price,
value: +amountInput.current.value,
};
console.log(obj);
ctx.addToCart(obj);
};
return (
<div className={style["food"]}>
<div className={style["food__info"]}>
<p>{foodObj.name}</p>
<p>{foodObj.description}</p>
<p>{foodObj.price}$</p>
</div>
<div className={style["food__form"]}>
<div className={style["food__form-row"]}>
<p>Amount</p>
<input type="number" min="0" ref={amountInput} />
</div>
<Button type="button" onClick={onClickHandler}>
+Add
</Button>
</div>
</div>
);
};
export default Food;
//Button
import style from "./Button.module.css";
const Button = (props) => {
return (
<button
type={props.type}
className={style["button"]}
onClick={props.onClick}
>
{props.children}
</button>
);
};
export default Button;
Issue
The React.StrictMode component is exposing an unintentional side-effect.
See Detecting Unexpected Side Effects
Strict mode can’t automatically detect side effects for you, but it
can help you spot them by making them a little more deterministic.
This is done by intentionally double-invoking the following functions:
Class component constructor, render, and shouldComponentUpdate methods
Class component static getDerivedStateFromProps method
Function component bodies
State updater functions (the first argument to setState)
Functions passed to useState, useMemo, or useReducer <-- here
The function passed to useReducer is double invoked.
const cartAction = (state, action) => {
const foodObject = action.value;
const arr = [];
console.log(state.foodArr);
if (action.type === "ADD_TO_CART") {
arr.push(foodObject); // <-- mutates arr array, pushes duplicates!
state.foodArr = [...state.foodArr, ...arr]; // <-- duplicates copied
return { ...state };
}
return { ...state };
};
Solution
Reducer functions are to be considered pure functions, taking the current state and an action and compute the next state. In the sense of pure functionality, the same next state should result from the same current state and action. The solution is only add the new foodObject object once, based on the current state.
Note also for the default "case" just return the current state object. Shallow copying the state without changing any data will unnecessarily trigger rerenders.
I suggest also renaming the reducer function to cartReducer so its purpose is more clear to future readers of your code.
const cartReducer = (state, action) => {
switch(action.type) {
case "ADD_TO_CART":
const foodObject = action.value;
return {
...state, // shallow copy current state into new state object
foodArr: [
...state.foodArr, // shallow copy current food array
foodObject, // append new food object
],
};
default:
return state;
}
};
...
useReducer(cartReducer, initialState);
Additional Suggestions
When adding an item to the cart, first check if the cart already contains that item, and if so, shallow copy the cart and the matching item and update the item's value property which appears to be the quantity.
Cart/item totals are generally computed values from existing state. As such these are considered derived state and they don't belong in state, these should computed when rendering. See Identify the minimal (but complete) representation of UI state. They can be memoized in the cart context if necessary.

component state doesnt change even after replacing data

My image component displays images with a heart over it every time a user submits a search. The heart changes colors if the image is clicked, and should reset to white (default color) when user submits a new search. For some reason, the clicked-color persists even after a search. What am I not understanding about react states? This isn't simply something that changes on the next render. It just stays like that until I change it manually.
const Image = ({image, toggleFav, initialIcon, initialAlt}) => {
const [fav, setFav] = useState(false);
const [heartIcon, setHeartIcon] = useState(initialIcon)
const [heartAlt, setHeartAlt] = useState(initialAlt)
const handleClick = () => {
setFav(fav => !fav);
toggleFav(image.id, fav);
if (heartIcon == "whiteHeartIcon") {
setHeartIcon("redHeartIcon")
}
else {
setHeartIcon("whiteHeartIcon")
}
if (heartAlt == "white heart icon") {
setHeartAlt("red heart icon")
}
else {
setHeartAlt("white heart icon")
}
};
return (
<Grid item xs={4} key={image.id}>
<div className={`${fav ? "fav" : ""}`} onClick={handleClick}>
<div className="imgBox">
<img src={image.url} className="image"/>
<Heart icon={heartIcon} alt={heartAlt} className="heart"/>
</div>
</div>
</Grid>
);
}
This is the handle submit func for the component:
const searchAllImages = async (keyword) => {
const response = await searchImages(keyword);
const imageObjects = response.data.message.map((link, index) => {
let newImage = {
url: link,
id: link,
fav: false
};
return newImage;
});
dispatch({type: 'SET_IMAGES', payload: imageObjects});
};
I render the images through a redux store where it replaces the image state every time a new search is done. The state resides in Store.js where image is initially set to an empty list. The dispatch method comes from Reducer.js where the method is defined.
case "SET_IMAGES":
return {
...state,
images: action.payload
}
Have you tried setting the initial image to a different variable initially, then use a useEffect that checks the image variable for changes, and if it changes assign the value to the variable mentioned above. Anyways more like using useEffect or useCallback for actions.

How to use React Redux store in component that modifies and renders the state

In Text component, I want to get text_data from DB once and save it to the Redux store.
export default function Text() {
const [document, setDocument] = useState([]);
setDocument(useSelector(currentState))
useEffect(() => {
axios.get(`/api/texts/${docId}`).then((response) => {
dispatch(currentState(response.data));
})
}, [])
return (
<div className="Text">
{document.text && document.text.map((text, index) => (
<div onClick={() => {dispatch(changeColor(index))}}>
{text.word}
</div>
))}
</div>
)
}
Then I'd like to get the text_data from Redux store probably in the same component
setDocument(useSelector(currentState))
But it causes infinite rerender.
Moreover, I'd like to modify the text_data with clicks so that Text component will show text in different colors after click. For that, I'd like to modify Redux state and rerender Text component.
text_data has a structure {word: color, second_word: color, ...}
How to use Redux for that? Is it possible, is my thinking correct that the redux state should be the only one thing that should change?
EDIT: Code snippet added. I am working on this so my code snippet doesn't work.
I think you are not understand react-redux hooks correctly. These two lines does not make sense. I don't know what your currentState variable should be in your snippet. But the usage is definitely wrong.
setDocument(useSelector(currentState))
dispatch(currentState(response.data));
I don't know what your redux store looks like. In next snippets I will assume that it is something like this.
// redux store structure
{
texts: {document: {}, coloredWords: {}}
}
// fetched document will have structure something like this (array of words)
{text: []}
Your (texts) reducer should modified the redux store like this (written just schematically)
// ...
// storing fetched document
case 'SET_DOCUMENT': {
const document = action.payload
return {...state, document: document}
}
// storing color for word at particular index
case 'CHANGE_COLOR': {
const {index, color} = action.payload
return {...state, coloredWords: {...state.coloredWords, [index]: color}}
}
// ...
import { useDispatch, useSelector } from 'react-redux'
import { setDocument, changeColor } from 'path_to_file_with_action_creators'
export default function Text() {
const dispatch = useDispatch()
// get fetched document from your redux store (it will be an empty object in the first render, while fetch in the useEffect hook is called after the first render)
const document = useSelector(state => state.texts.document))
// get colors for document words (they were saved in redux store by your onClick handler, see bellow)
const coloredWords = useSelector(state => state.texts.coloredWords))
useEffect(() => {
// fetch document
axios.get(`/api/texts/${docId}`).then((response) => {
// store fetched document in your redux store
dispatch(setDocument(response.data));
})
}, [])
return (
<div className="Text">
{document && document.text && document.text.map((text, index) => (
<div
style={{color: coloredWords[index] ? coloredWords[index] : 'black' }}
onClick={() => {
// store color for word at particular index in redux store
dispatch(changeColor({index: index, color: 'red'}))
}}
>
{text.word}
</div>
))}
</div>
)
}

React Component is rendering twice

I have no idea why, the first render shows an empty object and the second shows my data:
function RecipeList(props) {
return (
<div>
{console.log(props.recipes)}
{/*{props.recipes.hits.map(r => (*/}
{/* <Recipe initial="lb" title={r.recipe.label} date={'1 Hour Ago'}/>*/}
</div>
)
}
const RECIPES_URL = 'http://cors-anywhere.herokuapp.com/http://test-es.edamam.com/search?i?app_id=426&q=chicken&to=10'
export default function App() {
const classes = useStyles();
const [data, setData] = useState({});
useEffect(() => {
axios.get(RECIPES_URL)
.then(res => {
setData(res.data);
})
.catch(err => {
console.log(err)
})
}, []);
return (
<div className={classes.root}>
<NavBar/>
<RecipeList recipes={data}/>
<Footer/>
</div>
);
}
I don't know why and I have struggled here for over an hour (React newbie), so I must be missing something.
This is the expected behavior. The reason you see two console logs is because, the first time RecipeList is called with no data (empty object), and the second time when the data becomes available. If you would like to render it only when the data is available you could do something like {Object.keys(data).length > 0 && <RecipeList recipes={data}/>}. By the way this is called conditional rendering.
This is perfectly normal, React will render your component first with no data. Then when your axios.get returns and update data, it will be rendered again with the new data

Where to put chained actions in React/Redux

Fairly simple use case: I have actions/events that will cause an ajax request to be executed and then update a list.
The problem is I'm not sure how (specifically, where to kick off a request for a new list when the page is changed.
redux store
const defaultStore = {
list: [],
page: 1
};
Wrapper Component
const wrapper = props => (
<div>
<List {...props}> {/* This list should have page controls */}
<PageControls {...props} />
</List>
<List /> {/* This list should not have page controls */}
</div>
);
List component
const List = props => (
<div>
{props.children} {/* render page controls if present */}
{props.items.map((item, k) => <div key={k}>item</div>
</div>
);
Pager Controls component
const PageControls = props => (
<div>
<span onClick={props.changePage(-1)}>Backward</span>
<span onClick={props.changePage(1)}>Forward</span>
</div>
);
actionCreator
function changePage(delta) {
return {
type: 'CHANGE_PAGE',
delta
};
}
// utilizes react-thunk middleware
function getList(page = 1) {
return dispatch =>
axios.get(`/path/to/api?page=${page}`)
.then(res => dispatch(updateList(res.data));
}
function updateList(newList) {
return {
type: 'UPDATE_LIST',
newList
};
}
reducer
function reducer(state = {}, action) {
switch(action.type) {
case 'CHANGE_PAGE':
return {...state, page: state.page + action.delta};
case 'UPDATE_LIST':
return {...state, list: action.newList};
default:
return state;
}
}
At this point I could do a couple of things -- I could make every actionCreator that should trigger a list update dispatch that action:
function changePage(delta) {
return dispatch => {
dispatch({
type: 'CHANGE_PAGE',
delta
});
return dispatch(getList(store.getState() + delta));
}
}
But this seems messy. Now not only do I have to get my store but I also have to turn every actionCreator that affects the list into a thunk.
The only other thing I can think of is to have my <List> component use store.subscribe somewhere to watch for changes to page and then kick off another getList action, but this also seems like I'm moving the understanding of what does and does not trigger state changes out of Redux and into my React components.
Any ideas?
Well, maybe you should change your approach. I don't see a reason to make two actions for changing page and retrieving the list. You can just dispatch getPage() action on button click, passing next page number. This should retrieve list of items and refresh your page.
In your store you should keep track on current page, so each time page refreshes the value of getPage() argument will also update.
For example (assuming that current page is not retrieved from API):
function getPage(page = 1) {
return dispatch =>
axios.get(`/path/to/api?page=${page}`)
.then(res => dispatch(updatePage(res.data, page));
}
function updatePage(newList, currentPage) {
return {
type: 'UPDATE_PAGE',
newList,
currentPage,
};
}
and connect required components to the store, in your case it would be List and PageControls components.
const PageControls = props => (
<div>
<span onClick={props.getPage(props.currentPage - 1)}>Backward</span>
<span onClick={props.getPage(props.currentPage + 1)}>Forward</span>
</div>
);
This will allow you to maintain simple and clean code. Also you can trigger it from multiple, not related components.

Resources