Component is rendering one step behind - javascript

I have to press sort twice for the items to be rendered, and they are rendered with what should've been rendered on the first press. So it's one step behind. Any idea why it is not being rendered on the first press and why it's one step behind?
I tried making it asynchronous, I tried making a loading state and then render, but everything to no effect.
Here is my code:
import "./productlist.scss";
import ProductCard from "../../../../Main Page Components/Trending Now/Product/ProductCard";
import { Link } from "react-router-dom";
import { useSelector } from "react-redux";
import { useEffect } from "react";
import { useState } from "react";
const ProductList = (props) => {
const sortType = useSelector((state) => state.sort).sortType;
const [sortedProducts, setSortedProducts] = useState(props.products);
// const [isSorted, setIsSorted] = useState(false);
useEffect(() => {
setSortedProducts(sortProducts(sortedProducts, sortType));
}, [sortType, sortedProducts]);
const sortProducts = (products, sortMode) => {
console.log("sortMode", sortMode);
switch (sortMode) {
case "A-Z":
return products.sort((a, b) => a.name.localeCompare(b.name));
case "Z-A":
return products.sort((a, b) => a.name.localeCompare(b.name)).reverse();
case "default":
return products;
}
};
return (
<div className="product-list padding">
<div className="product-list-contents">
{sortedProducts.map((singleProduct) => {
console.log("singleProduct", singleProduct.name);
// if(product.categories[0].name.slice(0,1).toLowerCase() === props.category){
return (
<Link
to={`/product/${singleProduct.sku}`}
className="product-list-link"
key={singleProduct.sku}
>
<ProductCard product={singleProduct} />
</Link>
);
// )}
// }
})}
</div>
</div>
);
};
export default ProductList;

It seems that the useEffect is both setting and listening to sortedProducts which might be causing error.
Assuming that sortedProducts is based on products from props, and sortType is updated by useSelector, perhaps this component can sort props.products without keeping a state and useEffect. For a rough example:
const ProductList = (props) => {
const { sortType } = useSelector((state) => state.sort);
const sortProducts = (products, sortMode) => {
console.log("sortMode", sortMode);
switch (sortMode) {
case "A-Z":
return products.sort((a, b) => a.name.localeCompare(b.name));
case "Z-A":
return products.sort((a, b) => a.name.localeCompare(b.name)).reverse();
case "default":
return products;
}
};
const sortedProducts = sortProducts(props.products, sortType);
return (
<div className="product-list padding">
<div className="product-list-contents">
{sortedProducts.map((singleProduct) => {
console.log("singleProduct", singleProduct.name);
// if(product.categories[0].name.slice(0,1).toLowerCase() === props.category){
return (
<Link
to={`/product/${singleProduct.sku}`}
className="product-list-link"
key={singleProduct.sku}
>
<ProductCard product={singleProduct} />
</Link>
);
// )}
// }
})}
</div>
</div>
);
};
export default ProductList;

Related

Cart quantity, Item Price gives NaN with context api(description of problem in the post below)

I'm trying to make the cart with react context. But the problem here is when ever I do an increment on it it gives a NaN or on the price and no item quantity at the start when I view the cart. After I click increase quantity it gives the quantity as NaN as well. but after i refresh the page the quantity and price changes from NaN to a number. Please help me fix this.
The pictures of what the results are like are in this link :(https://imgur.com/a/QkktrZp)
And the codes for what I did are below:
Cart Context Code
`
import { createContext, useReducer, useEffect } from "react";
export const cartContext = createContext({});
export const CartContextProvider = ({ children }) => {
const reducer = (state, action) => {
switch (action.type) {
case "ADD":
const temporaryCart = state.filter(
(items) => action.payload.id === items.id
);
if (temporaryCart.length > 0) {
return state;
} else {
return [...state, action.payload];
}
case "INCREASE":
const increment = state.map((items) => {
if (items.id === action.payload.id) {
return {
...items,
quantity: items.quantity + 1,
};
} else {
return items;
}
});
return increment;
case "DECREASE":
const decrement = state.map((items) => {
if (items.id === action.payload.id) {
return {
...items,
quantity: items.quantity - 1,
};
} else {
return items;
}
});
return decrement;
case "REMOVECART":
const removeCart = state.filter(
(items) => items.id !== action.payload.id
);
return removeCart;
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, [], () => {
const localCart = localStorage.getItem("Cart");
return localCart ? JSON.parse(localCart) : [];
});
useEffect(() => {
localStorage.setItem("Cart", JSON.stringify(state));
}, [state]);
const cart = { state, dispatch };
return <cartContext.Provider value={cart}>{children}</cartContext.Provider>;
};
Cart Code:
import React, { useContext, useEffect, useState } from "react";
import { Button, Container, Stack } from "react-bootstrap";
import { cartContext } from "../Context/CartContext";
import { useAuthContext } from "../Context/useAuthContext";
const Cart = () => {
const { user } = useAuthContext();
const Cart = useContext(cartContext);
const state = Cart.state;
const dispatch = Cart.dispatch;
return (
<div>
{state.map((items, idx) => {
return (
<Container className="p-5">
<Stack gap={3}>
{state.map((items) => {
return <Container className="border d-flex justify-content-evenly align-items-center">
<div>
<img src={items.images[0].imageName} alt="/" width={"80px"} height={"80px"} />
</div>
<div>{items.title}</div>
<div className="d-flex justify-content-evenly align-items-center">
<Button onClick={()=>dispatch({type:"DECREASE", payload:items})}>-</Button>
<div>{items.quantity}</div>
<Button onClick={()=>dispatch({type:"INCREASE", payload:items})}>+</Button>
</div>
<div>
<Button onClick={()=>dispatch({type:"REMOVE", payload:items})}>Remove</Button>
</div>
<div>
{items.quantity*items.unitPrice[0].sellingPrice}
</div>
</Container>;
})}
</Stack>
</Container>
);
})}
</div>
);
};
export default Cart;
`
Any help would be appreciated. Thank you in advance!
Used usereducer hook to create and store items in local storage and fetched it in the cart page

Adding multiple elements to state with map function

I have 2 buttons, Single Component and Multiple Component.
When I click on Multiple Component, I expect it to add 3 components, but it adds only 1.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { observer } from "mobx-react-lite";
function App() {
const [component, setComponent] = useState([]);
useEffect(() => {});
const newArray = [1, 2, 3];
const Test = observer(() => {
return (
<div>
<p>Test</p>
</div>
);
});
const Test2 = observer(() => {
return (
<div>
<p>Test2</p>
</div>
);
});
const Test3 = observer(() => {
return (
<div>
<p>Test3</p>
</div>
);
});
async function MultipleComponent() {
newArray.map(async (x) => {
if (x === 1) {
await setComponent([...component, Test]);
} else if (x === 2) {
await setComponent([...component, Test2]);
} else {
await setComponent([...component, Test3]);
}
console.log(x);
});
}
return (
<div>
{component.map((Input, index) => (
<Input components={component} key={index} />
))}
<button onClick={() => setComponent([...component, Test])}>
Single Component
</button>
<button onClick={() => MultipleComponent()}>Multiple Component</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
codensadbox: https://codesandbox.io/s/react-hooks-useeffect-forked-edmgb5
There is no point in using await on setState, nowhere the docs say it is a good idea.
On the other hand you need to use version of setState which accepts an updater function, there you can get previous state.
setComponent(ps=>[...ps, Test2])
Also, I don't have link to official docs, but I am not sure storing components inside state is good idea either. You could store some identifier in state which indicates which component it is and then render that one when time comes. Here is what I mean by this:
let Test1 = (props) => {
return <div>1</div>;
};
let Test2 = (props) => {
return <div>2</div>;
};
let GeneralComponent = (props) => {
if (props.comp === '1') return <Test1 />;
if (props.comp === '2') return <Test2 />;
return null;
};
export default function App() {
let [comp, setComp] = React.useState('1');
return (
<div onClick={() => setComp(comp === '1' ? '2' : '1')}>
<GeneralComponent comp={comp} />
</div>
);
}
The GeneralComp accepts an identifier of which component to render, which is stored in state in parent.

Getting multiple requests in useEffect

I have a simple voting system (Yes or No) if you click Yes the state needs to be a string up and if you click No the state needs to be down, the vote works fine in the voteHandler method, but the problem is that i have to refresh the page everytime i vote so i can see the vote changes! i have added the useEffect so the vote changes directly but when i did i got alot of requests and errors, for example, i have 5 questions and each question might have multiple answers and each answer has the vote component (Yes or No), the requests = the number of questions * the number of answers!!
Here is the component without useEffect :
import React, { useState, useContext } from 'react';
import {AnswerContext} from './AnswerWrapper';
const AnswerItem = (props) => {
let indexPlus;
const indexCount = (index) => {
indexPlus = index;
return indexPlus;
}
const { active, setActive } = useContext(AnswerContext)
const [vote, setVote] = useState();
const voteHandler = (e, index, value) => {
e.preventDefault();
setActive(index);
setVote(value)
// I am sending this to a parent component
props.onVote ({
vote : value,
answerID : index
})
}
return (
<div>
<button onClick={(e) => voteHandler(e, props.index, 'up')}>Yes <span>({props.ups !== null ? props.ups : 0 })</span></button>
<button onClick={(e) => voteHandler(e, props.index, 'down')}>No <span>({props.downs !== null ? props.downs : 0 })</span></button>
</div>
)
}
export default AnswerItem;
And here is the component with useEffect :
import React, { useState, useEffect, useContext } from 'react';
import {AnswerContext} from './AnswerWrapper';
const AnswerItem = (props) => {
let indexPlus;
const indexCount = (index) => {
indexPlus = index;
return indexPlus;
}
const { active, setActive } = useContext(AnswerContext)
const [vote, setVote] = useState();
useEffect(() => {
props.onVote ({
vote : vote,
answerID : active
})
}, [vote, active])
const voteHandler = (e, index, value) => {
e.preventDefault();
setActive(index);
setVote(value)
}
return (
<div>
<button onClick={(e) => voteHandler(e, props.index, 'up')}>Yes <span>({props.ups !== null ? props.ups : 0 })</span></button>
<button onClick={(e) => voteHandler(e, props.index, 'down')}>No <span>({props.downs !== null ? props.downs : 0 })</span></button>
</div>
)
}
export default AnswerItem;
And to make it a bit clear, here is the parent component of AnswerItem
import React, { useContext, useCallback } from 'react';
import AnswerItem from './AnswerItem';
const AccordionItem = (props) => {
const handleVote = (dataV) => {
props.voteChanged(dataV)
}
return (
<div>
{props.answers.map((data, index) => (
<AnswerItem key={index} index={data.id} ups={data.ups} downs={data.downs} onVote={handleVote} />
))}
</div>
)
}
export default AccordionItem;
And lastly here is my App.js code, i didn't mention above that i post the votes in my API Api.postVote :
import React, {Component} from "react";
import AccordionItem from './components/AccordionItem';
class App extends Component {
constructor(props) {
super(props);
this.state = {
questions: [],
};
}
handleVoteData = (dataV) => {
this.sendVoteUpsData(dataV)
}
sendVoteUpsData = (dataV) => {
let newData = {...this.state};
newData.params = {
client_id : this.props.client,
the_vote : dataV.vote,
the_answerID : dataV.answerID
}
Api.postVote(newData.params)
.then( response => {
if(response.data.status === 'success'){
return response.data.data;
}
})
}
render() {
return (
<React.Fragment>
<div>
{
this.state.questions.map((data, index) => (
<AccordionItem answers={data.answers} voteChanged={this.handleVoteData}/>
))
}
</div>
</React.Fragment>
);
}
}
export default App;
First, you do not need to use useEffect hook to trigger props.onVote method, the first code should work.
Second, you don't see vote changes because your component has to rerender after the vote happened, and in order to make your component AnswerItem rerender, the object that contains {ups & downs} should be a state of the parent component for example.
see below code snippet and you will get the idea
import React, { useState } from 'react'
import AnswerItem from './AnswerItem';
const AnswerWrapper = () => {
const [state, setState] = useState({ ups: { '1': 3 }, downs: { '1': 0 } })
const onVote = ({ vote, answerID }) => {
if (vote == 'up') {
setState({ ...state, ups: { ...state.ups, [answerID]: state.ups[answerID] + 1 } })
} else if (vote == 'down') {
setState({ ...state, downs: { ...state.downs, [answerID]: state.downs[answerID] + 1 } })
}
}
return (
<AnswerItem onVote={onVote} index={'1'} ups={state.ups['1']} downs={state.downs['1']} />
)
}
export default AnswerWrapper;
I assumed the above component is the parent, and since the ups and downs in the state, so any changes will rerender the component, then the AnswerItem component will re-render accordingly.
The idea is your component has to be re rendered.

Reducer/Context Api

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

Which useEffect will be called after every Render?

I'm a beginner in React and stuck with some problem. I have several queries regarding this code.
Which UseEffect will be called after every render?
Why and How console.log() is called 13 times ?(Please find the screenshot below)
Why the fetched data is not shown in browser until I type something in the search bar?
App.js
import React, { useEffect } from "react";
import { useState } from "react";
import axios from "axios";
function App() {
const [monster, setMonster] = useState([]);
const [searchName, setName] = useState("");
const [filteredMonster, setFilter] = useState([]);
useEffect(() => {
async function fetchData() {
await axios.get(
"https://jsonplaceholder.typicode.com/users"
).then((resp)=>{
setMonster(resp.data);
})
console.log(monster);
}
fetchData();
}, []);
useEffect(()=>{
const mons = monster;
setFilter(mons.filter(mon =>
mon.name.toLowerCase().includes(searchName.toLowerCase())
));
}, [searchName]);
function changeName(event) {
setName(event.target.value);
}
console.log(monster);
const cunter = useRef(0);
return (
<div className="App">
<form>
<input
type="search"
name="searchName"
value={searchName}
onChange={changeName}
/>
</form>
{cunter.current++}
{filteredMonster&&filteredMonster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{monster&&!filteredMonster&&monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
</div>
);
}
export default App;
try this please. fetchData() will run only 1, searchName will run as many times you type on the screen.
TIP: To prevent this. add a timeoutdelay after user finishes typing to only render once instead of N times user presses a keyboard key.
import React, { useEffect } from "react";
import { useState } from "react";
import axios from "axios";
const URL = "https://jsonplaceholder.typicode.com/users"
function App() {
const [monster, setMonster] = useState([]);
const [searchName, setName] = useState("");
const [filteredMonster, setFilter] = useState([]);
useEffect(() => {
async function fetchData() {
await axios.get(URL).then((resp) => {
setMonster(resp.data);
})
console.log(monster);
}
fetchData();
}, []);
useEffect(() => {
if (monster.length > 0) {
const filter = mons.filter(({name}) =>
name.toLowerCase().includes(searchName.toLowerCase()));
setFilter(filter);
}
}, [searchName]);
function changeName(event) {
setName(event.target.value);
}
console.log(JSON.stringify(monster));
return (
<div className="App">
<form>
<input
type="search"
name="searchName"
value={searchName}
onKeyUp={(e) => changeName(e)}
/>
</form>
{monster.length > 0 &&
<div>{JSON.stringify(monster)}</div>
}
{filteredMonster && filteredMonster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{monster && !filteredMonster && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
</div>
);
}
export default App;
This is using Reducer, removes the use of state.
import React, { useEffect, useReducer } from "react";
import axios from "axios";
const URL = "https://jsonplaceholder.typicode.com/users"
const reducer = (state, action) => {
switch(action.type){
case 'FETCH_DATA':
return {
...state,
monster: action.monster,
name: "",
}
case 'SEARCH_MONSTER':
return {
...state,
name: action.name,
}
case 'FILTER_MONSTER':
const filter = state.monster.filter(({name}) =>
name.toLowerCase().includes(searchName.toLowerCase()));
return {
...state,
filteredMonster: filter,
name: state.name,
}
}
};
function App() {
const [state, dispatch] = useReducer(reducer, {
monster: [],
filteredMonster: [],
name: '',
});
useEffect(() => {
async function fetchData() {
await axios.get(URL).then((resp) => {
dispatch({ type: 'FETCH_DATA', monster: resp.data});
})
console.log(monster);
}
fetchData();
}, []);
useEffect(() => {
if (monster.length > 0) dispatch({ type: 'FILTER_MONSTER'});
}, [stat.name]);
console.log(JSON.stringify(monster));
return (
<div className="App">
<form>
<input
type="search"
name="searchName"
value={state.name}
onKeyUp={(e) => dispatch({ type: 'SEARCH_MONSTER', name: e.target.value })}
/>
</form>
{state.monster.length > 0 &&
<div>{JSON.stringify(monster)}</div>
}
{state.filteredMonster && state.filteredMonster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{state.monster && !state.filteredMonster && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
</div>
);
}
export default App;
1. Which UseEffect will be called after every render?
Ans: According to the react official doc useEffect does care about 3 lifecycle method namely componentDidMount componentDidUpdate and componentWillUnmount. So no matter what how many useEffect you have, all the effect hooks will execute when componentMount for the first time. But useEffect will execute further, only when it's dependency get updates else it will ignore
2. Why and How console.log() is called 13 times?
Ans: I tried to reproduce 13 times rerendering but I am not able to do so. But yes it's rerendering multiple times because in the 2nd useEffect on every Keystore the state is updating and because of that component is rerendering several times.
its happening something like this
changeName() → setName() → useEffect() → setFilter() → (on every keystore repeating same step) → ...loop
you can try debounce or throttling which can help you to avoid continuous Keystore hit by which no of rerendering can drastically reduce
Instead of using console.log, there is a hack to know the number of rerendering
declare the below code in the component
const cunter = useRef(0);
and then in the return block add {cunter.current++} by this you can see how many times your component is actually rerendering
3. Why the fetched data is not shown in the browser until I type something in the search bar?
This is because in your condition your checking !filteredMonster where filteredMonster is an array and !filteredMonster will return always false instead try Array length properties
filteredMonster.length === 0
{monster && !filteredMonster && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{(monster && filteredMonster.length === 0) && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}

Categories