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>
);
}
Related
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'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).
I have tried many things and can't seem to understand why setTypes won't update the 'types' array??
import { useState, useEffect } from 'react';
import { PostList } from './post-list';
import * as api from '../utils/api';
export const PostSelector = (props) => {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [type, setType] = useState('post');
const [types, setTypes] = useState([]);
const fetchTypes = async () => {
setLoading(true);
const response = await api.getPostTypes();
delete response.data.attachment;
delete response.data.wp_block;
const postTypes = response.data;
console.log(response.data); // {post: {…}, page: {…}, case: {…}}
setTypes(postTypes);
console.log(types); // []
// Why types remain empty??
}
const loadPosts = async (args = {}) => {
const defaultArgs = { per_page: 10, type };
const requestArgs = { ...defaultArgs, ...args };
requestArgs.restBase = types[requestArgs.type].rest_base; // Cannot read property 'rest_base' of undefined
const response = await api.getPosts(requestArgs);
console.log(response.data);
}
useEffect(() => {
fetchTypes();
loadPosts();
}, []);
return (
<div className="filter">
<label htmlFor="options">Post Type: </label>
<select name="options" id="options">
{ types.length < 1 ? (<option value="">loading</option>) : Object.keys(types).map((key, index) => <option key={ index } value={ key }>{ types[key].name }</option> ) }
</select>
</div>
);
}
Please, take a look at the console.log and notice the different responses.
What I am trying to do is to load list of types, in this case 'post', 'page' and 'case' and then render a list of posts based on the current 'type'. The default type is 'post'.
If I add [types] to useEffect. I finally get the values but the component renders nonstop.
Thanks to everyone for your comments. Multiple people have pointed out the problem, being that, the fact that we set the state doesn't mean it will set right away because it it asynchronous.
How do we solve this problem then? Regardless of the reasons, how do we get it done? How do we work with our state at any point in time and perform calculations based on our state if we don't know when it will become available? How do we make sure we wait whatever we need to and then use the values we expect?
For any one coming here and not being able to set/update a useState array you need to use a spread operator (...) and not just the array e.g. "[...initState]" instead of "initState" ... in Typescript
//initialise
const initState: boolean[] = new Array(data.length).fill(false);
const [showTable, setShowTable] = useState<boolean[]>([...initState]);
// called from an onclick to update
const updateArray = (index: number) => {
showTable[index] = !showTable[index];
setShowTable([...showTable]);
};
It seems like useState is asynchronous and does not update the value instantly after calling it.
Review this same case here
useState's setTypes is an asynchronous function so the changes do not take effect immediately. You can use useEffect to check if anything changes
useEffect(()=>{
const defaultArgs = { per_page: 10, type };
const requestArgs = { ...defaultArgs, ...args };
requestArgs.restBase = types;
console.log("types updated",types)
},[types])
You can remove loadPosts because now useEffect will run whenever types change
You have declared your types to be an array, yet you are passing a dictionary of dictionaries through to it.
Try this:
const [types, setTypes] = useState({});
You also do not need to call
loadPosts()
becuase the useState hook will re-render your component, only updating what is needed.
Ok, The short answer is due to Closures
It not due to asynchronous as other answers said !!!
Solution (☞゚ヮ゚)☞
You can check the changes by console.log at return function like this.
return (
<div> Hello World!
{
console.log(value) // this will reference every re-render
}
</div>
);
or create a new one useEffect with value as a dependency like below
React.useEffect(() => {
console.log(value); // this will reference every value is changed
}, [value]);
function App() {
const [value, Setvalue] = React.useState([]);
React.useEffect(() => {
Setvalue([1, 2, 3]);
console.log(value); // this will reference to value at first time
}, []);
return (
<div> Hello World!
{
console.log(value) // this will reference every re-render
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Read here in more detail: useState set method not reflecting change immediately
I export a JS object called Products to this file, just to replace a real API call initially while I am building/testing. I want to set the function's state to the object, but mapped. I have the component looking like this:
function App() {
const [rooms, setRooms] = useState([]);
const [days, setDays] = useState([]);
const roomsMapped = products.data.map(room => ({
id: room.id,
title: room.title
}))
useEffect(() => {
setRooms(roomsMapped);
})
return ( etc )
This returns the following error: Error: Maximum update depth exceeded.
I feel like I'm missing something really obvious here, but am pretty new to React and Hooks. How can I set this data before the component renders?
Just declare it as initial value of rooms
const Component = () =>{
const [rooms, setRooms] = useState(products.data.map(room => ({
id: room.id,
title: room.title
})))
}
You can also use lazy initial state to avoid reprocessing the initial value on each render
const Component = () =>{
const [rooms, setRooms] = useState(() => products.data.map(room => ({
id: room.id,
title: room.title
})))
}
Change useEffect to this
useEffect(() => {
setRooms(roomsMapped);
},[])
With Lazy initialisation with function as a parameter of useState
import React, { useState } from "react";
function App() {
const [rooms, setRooms] = useState(() => {
// May be a long computation initialization
const data = products.data || [];
return data.map(({ id, title }) => ({ id, title }));
});
return (
// JSX stuffs
)
}
You can use default props for this.set initial value with empty list .
You are getting 'Error: Maximum update depth exceeded', because your useEffect function doesn't have dependency array. Best way to fix this is to pass empty array as the second argument to useEffect like this:
useEffect(() => {
setRooms(roomsMapped);
},[]) <= pass empty array here
this will prevent component to re render, it you want your component to re render on props change you can pass the props in the array like this:
useEffect(() => {
setRooms(roomsMapped);
},[props.props1,props.props2])
here you can pass as many props as you want...
I was wondering how it is possible to run a function after you use the useEffect to fetch data, where the function is manipulating the data after its been pulled?
import React, { useState, useEffect } from 'react';
const Result = (props) => {
const [ playerName, setPlayerName ] = useState('');
const [ playerChoice, setPlayerChoice ] = useState(null);
const [ computerChoice, setComputerChoice ] = useState(null);
const [ result, setResult ] = useState(null);
useEffect(() => {
setPlayerName(props.location.state.playerName);
setPlayerChoice(props.location.state.playerChoice);
setComputerChoice(generateComputerChoice);
setResult(getResult())
}, []);
const getResult = () => {
// code that runs after the setting of the playerName and playerChoice. Will return "Win", "Lose", or "Draw"
};
const generateComputerChoice = () => {
const outcomes = [ 'Rock', 'Paper', 'Scissors' ];
return outcomes[Math.floor(Math.random() * outcomes.length)];
};
return (
<div className="app-container">
<strong>YOU {result}</strong>
<br />
<strong>{playerName}</strong> chose <strong>{playerChoice}</strong>
<br />
<strong>Computer</strong> chose <strong>{computerChoice}</strong>
</div>
);
};
export default Result;
So here in this example I grab the playerName and playerChoice from a previous page, and then add that to my useState on page load.
After that I randomly generate the computerChoice.
However, after that I want to use the playerChoice amd computerChoice that has been added to the state and use that to see if the game is a win, lose or draw.
result ends up being null because I assume that by the time the getResult function is called, the states haven't been set yet.
Do you guys know what should be done in this situation? Seems like this could be a common thing considering you might wanna grab data from an API and then do something with that data before wanting to render it.
That first effect is unneccessary. Just do
const [playerName, setPlayerName] = useState(props.location.state.playerName);
Use the useMemo hook and add the state variables to its dependency array. It will memoize the result for each render cycle so it is only ever computed when playerName or playerChoice update.
const getResult = useMemo(() => {
// code that runs after the setting of the playerName and playerChoice. Will return "Win", "Lose", or "Draw"
}, [playerName, playerChoice]);
Oops, I see now you're trying to save this to the result state variable, so you can either use a second useEffect with the same dependencies instead of the useMemo I suggested, or in your original snippet instead of calling a getResult() function you change the signature to getResult(name, choice) and call setResult with the current render cycle values (right from the props).
useEffect(() => {
const { playerName, playerChoice } = props.location.state;
setPlayerName(playerName);
setPlayerChoice(playerChoice);
setComputerChoice(generateComputerChoice);
setResult(getResult(playerName, playerChoice));
}, []);
const getResult = (name, choice) => {
// Will return "Win", "Lose", or "Draw"
};
setState is asynchronous and you'll be able to use the state only in the next render. Unlike in class components, hooks doesn't allow a callback after setting the state.
But looking at your component and assuming that is what the functionality of that would be, there's no reason for you to add playerName and playerChoice to the state of the component. You can use the data from the props itself.
import React, { useState, useEffect } from 'react';
const Result = (props) => {
const {playerName, playerChoice} = props.location.state;
const [ result, setResult ] = useState(null);
const [computerChoice, setComputerChoice] = useState(null);
useEffect(() => {
setComputerChoice(generateComputerChoice());
getResult();
}, []);
const getResult = () => {
// You can get the playerName, playerChoice from the props.
// You can also setResult here.
};
const generateComputerChoice = () => {
const outcomes = [ 'Rock', 'Paper', 'Scissors' ];
return outcomes[Math.floor(Math.random() * outcomes.length)];
};
return (
<div className="app-container">
<strong>YOU {result}</strong>
<br />
<strong>{playerName}</strong> chose <strong>{playerChoice}</strong>
<br />
<strong>Computer</strong> chose <strong>{computerChoice}</strong>
</div>
);
};
export default Result;
Hope this helps.