I'm new in coding and i couldn't get how to fix the issue after i googled many times. The issue is i have a layout component which contains 4 different components. When i call a function in a function component it affects the others and the others re-render. But i don't pass the new props to them. I only pass props to one component which contains click events. I hope I made myself clear , thanks in advance. So here are my code samples :
This is my layout component.
import React, { useState } from "react";
import Header from "./Header";
import MenuTitle from "./MenuTitle";
import MenuList from "./MenuList";
import Cart from "./Cart";
import Footer from "./Footer";
function Layout({
cartData,
menuList,
menuTitles,
callMenuList,
addToCart,
title,
removeFromCart,
currency,
}) {
const [isCartOpened, setIsCartOpened] = useState("closed");
const openCart = () => {
if (isCartOpened == "closed") {
setIsCartOpened("opened");
} else {
setIsCartOpened("closed");
}
};
const closeCart = () => {
setIsCartOpened("closed");
};
return (
<div>
<Header openCart={() => openCart()} cartData={cartData} />
<MenuTitle
menuTitles={menuTitles}
callMenuList={(titleProp) => callMenuList(titleProp)}
/>
<MenuList
title={title}
menuList={menuList}
addToCart={(data) => addToCart(data)}
/>
<Cart
currency={currency}
cartData={cartData}
removeFromCart={(itemId) => removeFromCart(itemId)}
isCartOpened={isCartOpened}
closeCart={() => closeCart()}
/>
<Footer />
</div>
);
}
export default Layout;
And this is my App component
import React, { useState, useEffect } from "react";
import Layout from "./Components/Layout";
function App() {
const [data, setData] = useState([]);
const [menuTitle, setMenuTitle] = useState([]);
const [title, setTitle] = useState("");
const [currency, setCurrency] = useState("");
const [menuList, setMenuList] = useState([]);
const [cart, setCart] = useState([]);
const API = "./db.json";
const callMenuList = React.useCallback((titleProp) => {
setTitle(titleProp);
const filterMenuList = data.filter((title) => title.TYPE == titleProp);
setMenuList(filterMenuList);
});
const addToCart = React.useCallback((data) => {
setCart([...cart, data]);
});
const removeFromCart = React.useCallback((itemId) => {
const cartItems = cart;
cartItems.map((item) => {
if (item.CODE == itemId) {
const filtered = cartItems.filter(
(cartItem) => cartItem.CODE != itemId
);
setCart(filtered);
}
});
});
useEffect(() => {
const titles = [];
const fetchData = async () => {
const response = await fetch(API);
const responseData = await response.json();
setData(responseData);
console.log(responseData);
// Filtering menu types
responseData.map((item) => titles.push(item.TYPE));
const filtered = titles.filter(
(item, index, self) => self.indexOf(item) == index
);
setMenuTitle(filtered);
const initialMenuList = responseData.filter(
(item) => item.TYPE == filtered[0]
);
setTitle(initialMenuList[0].TYPE);
setCurrency(initialMenuList[0].CURRENCY);
setMenuList(initialMenuList);
};
fetchData();
}, []);
return (
<Layout
menuTitles={menuTitle}
menuList={menuList}
data={data}
callMenuList={(titleProp) => callMenuList(titleProp)}
addToCart={(data) => addToCart(data)}
removeFromCart={(itemId) => removeFromCart(itemId)}
cartData={cart}
title={title}
currency={currency}
/>
);
}
export default React.memo(App);
I have to add this as an answer even though it's more of a comment because so many people become overzealous about preventing renders when it doesn't matter.
React is very fast out of the box - it is supposed to be re-rendering components when props don't change. But, just to illustrate, you can design your components (using children) so that not everything re-renders all the time.
Compare these two stackblitz:
with children - C2 does NOT rerender
without children - C2 does rerender
But none of this actually matters, you should only look at preventing unnecessary renders if you see performance issues.
If you see logical issues that are fixed by preventing a re-render, then you've got a bug that you need to fix somewhere else.
If you aren't experiencing any performance or logic issues, then the answer to your question is to stop worrying about it.
You can use React.memo, but memoizing a component could easily end up being a performance penalty, rather than a win. Memoizing something isn't free.
I urge you to forget about this stuff unless you are seeing performance or logical errors.
Stop worrying, everything is functioning normally when your components re-render without props/state changes if their parents have re-rendered
If you set a new state in your layout component, it will re-run and re-render all the components in its JSX.
Don't worry, it is not the problem of React.
If you want your Header, Menu, Cart, Footer not to be re-render, read about React.PureComponent (for class), React.memo, or useMemo, useCallback (for funtional component).
Related
I have this React code:
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [players, setPlayers] = useState([]);
// Get all Players
const getAllPlayersUrl = "http://localhost:5087/api/GetAllPlayers";
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
}, []);
const [playerCount, setPlayerCount] = useState(players.length);
return (
<div>
<p>{`This is how many there are: ${playerCount}`}</p>
</div>
);
}
export default App;
I want to print how many initial players using playerCount variable. However it says it's zero:
This is how many there are: 0
If I instead print players.length, it would output the correct number:
<p>{`This is how many there are: ${players.length}`}</p>
This is how many there are: 9
Even if I remove dependency array to keep rendering, playerCount still wont update:
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
});
I wonder why the useState is not working? Is there something I am missing in my code?
A good rule of thumb with state (and props) is to avoid duplicating state values when a value can be determined entirely by another. Otherwise, you can run into issues like these, where keeping multiple states in sync can be more challenging than it needs to be.
Here, you set the initial value of playerCount when the component mounts:
const [playerCount, setPlayerCount] = useState(players.length);
And the component mounts only once - and at that time, players is the empty array - so playerCount becomes 0, and because you never call setPlayerCount, it always remains 0.
While you could fix it by calling setPlayerCount inside your .then, a better approach would be to either calculate the player count from the players state only when needed:
function App() {
const [players, setPlayers] = useState([]);
const getAllPlayersUrl = "http://localhost:5087/api/GetAllPlayers";
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
}, []);
return (
<div>
<p>{`This is how many there are: ${players.length}`}</p>
</div>
);
}
Or, if you really had to, to memoize the count depending on the players array (without creating additional state).
function App() {
const [players, setPlayers] = useState([]);
const playerCount = useMemo(() => players.length, [players]);
const getAllPlayersUrl = "http://localhost:5087/api/GetAllPlayers";
useEffect(() => {
axios.get(getAllPlayersUrl).then((response) => {
setPlayers(response.data);
});
}, []);
return (
<div>
<p>{`This is how many there are: ${playerCount}`}</p>
</div>
);
}
I really do not understand why this is not working, basically, I have a header component with its own context. On the other hand, I have a popOver component that goes inside the header, and this popOver also has its own context.
Now, there is a list of elements that are rendered inside the popOver, the user picks which elements to render, and such list needs to be rendered simultaneously in the header, for that reason I am trying to keep both contexts synchronized, the problem appears when I try to consume the header context inside the popOver context, the values consumed appear to be undefined.
const HeaderContext = createContext();
export const HeaderProvider = ({ children }) => {
const [headChipList, setHeadChipList] = useState([]);
const [isChipOpen, setIsChipOpen] = useState(false);
useEffect(() => {
if (headChipList.length) setIsChipOpen(true);
}, [headChipList]);
return (
<HeaderContext.Provider value={{ headChipList, setHeadChipList, isChipOpen, setIsChipOpen }}>
{children}
</HeaderContext.Provider>
);
};
export const useHeaderContext = () => {
const context = useContext(HeaderContext);
if (!context) throw new Error('useHeaderContext must be used within a HeaderProvider');
return context;
};
As you can see at the end there's a custom hook that allows an easier consumption of the context and also is a safeguard in case the custom hook is called outside context, the popOver context follows this same pattern:
import React, { useState, useContext, createContext, useEffect } from 'react';
import { useHeaderContext } from '(...)/HeaderProvider';
const PopoverContext = createContext();
export const PopoverProvider = ({ children }) => {
const { setHeadChipList, headChipList } = useHeaderContext; // this guys are undefined
const [menuValue, setMenuValue] = useState('Locations with Work Phases');
const [parentId, setParentId] = useState('');
const [chipList, setChipList] = useState([]);
const [locations, setLocations] = useState([]);
useEffect(() => setChipList([...headChipList]), [headChipList]);
useEffect(() => setHeadChipList([...chipList]), [chipList, setHeadChipList]);
return (
<PopoverContext.Provider
value={{
menuValue,
setMenuValue,
chipList,
setChipList,
parentId,
setParentId,
locations,
setLocations
}}
>
{children}
</PopoverContext.Provider>
);
};
export const usePopover = () => {
const context = useContext(PopoverContext);
if (!context) throw new Error('usePopover must be used within a PopoverProvider');
return context;
};
I would really appreciate any highlight about this error, hopefully, I will be able to learn how to avoid this type of error in the future
You're not calling the useHeaderContext function. In PopoverProvider, change the line to
const { setHeadChipList, headChipList } = useHeaderContext();
I'm working on a new major release for react-xarrows, and I came up with some messy situation.
It's not going to be simple to explain, so let's start with visualization:
consider the next example - 2 draggable boxes with an arrow drawn between them, and a wrapping context around them.
focused code:
<Xwrapper>
<DraggableBox box={box} />
<DraggableBox box={box2} />
<Xarrow start={'box1'} end={'box2'} {...xarrowProps} />
</Xwrapper>
Xwrapper is the context, DraggableBox and Xarrow are, well, you can guess.
My goal
I want to trigger a render on the arrow, and solely on the arrow, whenever one of the connected boxes renders.
My approach
I want to be able to rerender the arrow from the boxes, so I have to consume 'rerender arrow'(let's call it updateXarrow) function on the boxes, we can use a context and a useContext hook on the boxes to get this function.
I will call XelemContext to the boxes context.
also, I need to consume useContext on Xarrow because I want to cause a render on the arrow whenever I decide.
this must be 2 different contexts(so I could render xarrow solely). one on the boxes to consume 'updateXarrow', and a different context consumed on Xarrow to trigger the reredner.
so how can I pass this function from one context to another? well, I can't without making an infinite loop(or maybe I can but could not figure it out), so I used a local top-level object called updateRef.
// define a global object
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
// define updateXarrow here
...
// assign to updateRef.func
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
//now updateRef.func is defined because XelemProvider defined later
const XelemProvider = ({ children }) => {
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
the thing is, that this object is not managed by react, and also, i will need to handle cases where there is multiple instances of Xwrapper, and I'm leaving the realm of React, so i have 2 main questions:
there is a better approach? maybe I can someone achieve my goal without going crazy?
if there is no better option, is this dangerous? I don't want to release a code that will break on edge cases on my lib consumer's apps.
Code
DraggableBox
const DraggableBox = ({ box }) => {
console.log('DraggableBox render', box.id);
const handleDrag = () => {
console.log('onDrag');
updateXarrow();
};
const updateXarrow = useXarrow();
return (
<Draggable onDrag={handleDrag} onStop={handleDrag}>
<div id={box.id} style={{ ...boxStyle, position: 'absolute', left: box.x, top: box.y }}>
{box.id}
</div>
</Draggable>
);
};
useXarrow
import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { XelemContext } from './Xwrapper';
const useXarrow = () => {
const [, setRender] = useState({});
const reRender = () => setRender({});
const updateXarrow = useContext(XelemContext);
useLayoutEffect(() => {
updateXarrow();
});
return reRender;
};
export default useXarrow;
Xwrapper
import React, { useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
console.log('XarrowProvider');
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
const XelemProvider = ({ children }) => {
console.log('XelemProvider');
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('Xwrapper');
return (
<XarrowProvider>
<XelemProvider>{children}</XelemProvider>
</XarrowProvider>
);
};
export default Xwrapper;
const Xarrow: React.FC<xarrowPropsType> = (props: xarrowPropsType) => {
useContext(XarrowContext);
const svgRef = useRef(null);
....(more 1100 lines of code)
logs
I left some logs.
on drag event of a single box you will get:
onDrag
DraggableBox render box2
XarrowProvider
xarrow
Note
currently, this is working as expected.
Update
after many hours of testing, this seems to work perfectly fine. I manage my own object that remember the update function for each Xwrapper instance, and this breaks the dependency between the 2 contexts. I will leave this post in case someone else will also come across this issue.
Update (bad one)
this architecture breaks on react-trees with <React.StrictMode>...</React.StrictMode> :cry:
any idea why? any other ideas ?
just in case someone would need something similar: here's a version that will work even with react strictmode(basically being rellyed of effect which called once and not renders):
import React, { FC, useEffect, useRef, useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
// will hold a object of ids:references to updateXarrow functions of different Xwrapper instances over time
const updateRef = {};
let updateRefCount = 0;
const XarrowProvider: FC<{ instanceCount: React.MutableRefObject<number> }> = ({ children, instanceCount }) => {
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
useEffect(() => {
instanceCount.current = updateRefCount; // so this instance would know what is id
updateRef[instanceCount.current] = updateXarrow;
}, []);
// log('XarrowProvider', updateRefCount);
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
// renders only once and should always provide the right update function
const XelemProvider = ({ children, instanceCount }) => {
return <XelemContext.Provider value={updateRef[instanceCount.current]}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('wrapper here!');
const instanceCount = useRef(updateRefCount);
const [, setRender] = useState({});
useEffect(() => {
updateRefCount++;
setRender({});
return () => {
delete updateRef[instanceCount.current];
};
}, []);
return (
<XelemProvider instanceCount={instanceCount}>
<XarrowProvider instanceCount={instanceCount}>{children}</XarrowProvider>
</XelemProvider>
);
};
export default Xwrapper;
I am building a simple recipe app and I have a problem with fetching my data from the API, because the code seems to run on every render and I do not even understand why it re-runs since I found that if I add the dependency array, it should run only once, right ?
App.js
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={recipesList} getRecipes={setRecipesList} />
</div>
);
}
export default App;
Recipes.js
import React, {useEffect, useState} from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = (props) => {
useEffect( () => {
if (props.recipesList.length === 0) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.getRecipes(result.recipes);
}
)
}
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [props])
const recipeComponent = props.recipesList.map( (item) => {
return <Recipe className="recipe" info={item}/>
})
return(
<div className="recipes">
{recipeComponent}
<h1>Hello</h1>
</div>
)
}
export default Recipes;
Components will re-render every time your the props or state changes inside of the component.
I would recommend keeping the fetching logic inside of the Recipes component, because A: its recipe related data, not app related data. And B: this way you can control the state in Recipes instead of the props. This will give you more control on how the component behaves instead of being dependent on the parent component.
In the useEffect hook, leave the dependency array empty. This will cause the component to render, call useEffect only the first time, load your data and then render the recipes without re-rendering further.
import React, { useEffect, useState } from "react";
import Recipe from "../Recipe/Recipe";
import "./Recipes.css";
const Recipes = () => {
const [recipesList, setRecipesList] = useState([]);
useEffect(() => {
fetch("myapi.com/blablabla")
.then((res) => res.json())
.then((result) => {
setRecipesList(result.recipes);
});
return () => console.log("unmounting");
}, []);
// On the first render recipeComponents will be empty.
const recipeComponents = recipesList.map((item) => <Recipe className="recipe" info={item}/>)
return (
<div className="recipes">
{recipeComponents}
<h1>Hello</h1>
</div>
);
};
export default Recipes;
try this code :
function App() {
const [recipesList, setRecipesList] = useState([]);
let [scroll, setScroll] = useState(0)
const getListPropd = (e) => {
setRecipesList(e)
}
console.log(recipesList,"list");
return (
<div className="App">
<img className="logo" src={logo} alt="Logo"/>
<Recipes recipesList={(e) => getListPropd (e)} getRecipes={setRecipesList} />
</div>
);
}
export default App;
const [checkData , setCheckData ] = useState(true)
useEffect( () => {
if (checkData) {
fetch("myapi.com/blablabla")
.then(res => res.json())
.then(result => {
props.recipesList(result.recipes);
}
if(props.recipesList.length > 0) {
setCheckData(false)
}
)
else {
console.log("Do not fetch");
}
return () => console.log("unmounting");
}, [checkData])
the useEffect hook uses an empty dependency array, [] if it should ONLY run once after component is mounted. This is the equivalent of the old lifecycle method componentDidMount()
If you add a non-empty dependency array, then the component rerenders EVERY time this changes. In this case, every time your component receives new props (i.e. from a parent component, this triggers a reload.
see more info here https://reactjs.org/docs/hooks-effect.html , especially the yellow block at the bottom of the page
Happy coding!
First things first, my code is working and pulling data from Redux like it should. All input boxes receives user input and submits it perfectly after submit (as seen in my console).
The only problem I'm having is figuring out how to send that information to the actual DOM in my useEffect(). Preferably I'd like it to display at the top of the current list (unshift()) instead of the bottom when new data is submitted by user.
I have been staring at this for so long that I'm pretty sure I'm probably over thinking it. Any help though would be greatly appreciated.
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/postActions';
const Posts = (props) => {
const [data, setData] = useState([]);
useEffect(() => {
if(setData(props.fetchPosts())){
props.posts.unshift(...props.post, props.newPost);
}
},[props.newPost])
console.log('new post',props.newPost)
const postItems = props.posts.map((item, index) => (
<div key={index}>
<h3>{item.title}</h3>
<p>{item.body}</p>
</div>
))
return (
<div>
<h1>Posts</h1>
{postItems}
</div>
)
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts })(Posts);
there are two ways to solve this issue.
1. Update state value (props are immutable).
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/postActions';
const Posts = (props) => {
const [data, setData] = useState([]);
useEffect(() => {
if(props.fetchPosts()){
let dupData = [...data]
dupData.unShift(props.newPost)
setData(dupData)
}
},[props.newPost])
console.log('new post',props.newPost)
let updatedPosts = [data, ...props.posts]
const postItems = updatedPosts.map((item, index) => (
<div key={index}>
<h3>{item.title}</h3>
<p>{item.body}</p>
</div>
))
return (
<div>
<h1>Posts</h1>
{postItems}
</div>
)
}
const mapStateToProps = state => ({
posts: state.posts.items,
newPost: state.posts.item
})
export default connect(mapStateToProps, { fetchPosts })(Posts);
Update redux state
If you simply render the postItems based on your Redux state, I think the issue should be solved. Dispatching an action in useEffect will change the Redux state which should automatically trigger a rerender of the component. You can also consider dispatching an action directly upon the submission of the form, this may make everything a bit cleaner.
Also, I would not recommend using the useState hook here as it is not necessary here if you're also using Redux (in this component). A good practice is to use the useState hook only for state that is local to the component and is not helpful to any others in your app.