Why does using setState in React makes function repeat? - javascript

There is a button that toggles dark and light mode so I am trying to save the state of what mode it is on in localStorage. However, when I try to change the state to anything inside the React function it calls the function infinitely and gives this error:
Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
How do I change the state without re-rendering the function infinitely?
let localDark = localStorage.getItem("dark");
function Mode() {
const [dark, setDark] = useState(false);
const onClick = () => {
if (dark) {
setDark(false);
document.querySelector("body").classList.remove("dark");
} else {
setDark(true);
document.querySelector("body").classList.add("dark");
}
localStorage.setItem("dark", dark);
};
if (localDark !== null) {
localDark = JSON.parse(localStorage.getItem("dark"));
setDark(localDark); // this is what causes the error
// onClick();
}
return (
<div onClick={onClick} className="mode">
{dark ? <Light /> : <Dark />}
</div>
);
}

Related

Getting a warning while using firebase login system [duplicate]

I am getting this warning in react:
index.js:1 Warning: Cannot update a component (`ConnectFunction`)
while rendering a different component (`Register`). To locate the
bad setState() call inside `Register`
I went to the locations indicated in the stack trace and removed all setstates but the warning still persists. Is it possible this could occur from redux dispatch?
my code:
register.js
class Register extends Component {
render() {
if( this.props.registerStatus === SUCCESS) {
// Reset register status to allow return to register page
this.props.dispatch( resetRegisterStatus()) # THIS IS THE LINE THAT CAUSES THE ERROR ACCORDING TO THE STACK TRACE
return <Redirect push to = {HOME}/>
}
return (
<div style = {{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
<RegistrationForm/>
</div>
);
}
}
function mapStateToProps( state ) {
return {
registerStatus: state.userReducer.registerStatus
}
}
export default connect ( mapStateToProps ) ( Register );
function which triggers the warning in my registerForm component called by register.js
handleSubmit = async () => {
if( this.isValidForm() ) {
const details = {
"username": this.state.username,
"password": this.state.password,
"email": this.state.email,
"clearance": this.state.clearance
}
await this.props.dispatch( register(details) )
if( this.props.registerStatus !== SUCCESS && this.mounted ) {
this.setState( {errorMsg: this.props.registerError})
this.handleShowError()
}
}
else {
if( this.mounted ) {
this.setState( {errorMsg: "Error - registration credentials are invalid!"} )
this.handleShowError()
}
}
}
Stacktrace:
This warning was introduced since React V16.3.0.
If you are using functional components you could wrap the setState call into useEffect.
Code that does not work:
const HomePage = (props) => {
props.setAuthenticated(true);
const handleChange = (e) => {
props.setSearchTerm(e.target.value.toLowerCase());
};
return (
<div key={props.restInfo.storeId} className="container-fluid">
<ProductList searchResults={props.searchResults} />
</div>
);
};
Now you can change it to:
const HomePage = (props) => {
// trigger on component mount
useEffect(() => {
props.setAuthenticated(true);
}, []);
const handleChange = (e) => {
props.setSearchTerm(e.target.value.toLowerCase());
};
return (
<div key={props.restInfo.storeId} className="container-fluid">
<ProductList searchResults={props.searchResults} />
</div>
);
};
I just had this issue and it took me a bit of digging around before I realised what I'd done wrong – I just wasn't paying attention to how I was writing my functional component.
I was doing this:
const LiveMatches = (props: LiveMatchesProps) => {
const {
dateMatches,
draftingConfig,
sportId,
getDateMatches,
} = props;
if (!dateMatches) {
const date = new Date();
getDateMatches({ sportId, date });
};
return (<div>{component stuff here..}</div>);
};
I had just forgotten to use useEffect before dispatching my redux call of getDateMatches()
So it should have been:
const LiveMatches = (props: LiveMatchesProps) => {
const {
dateMatches,
draftingConfig,
sportId,
getDateMatches,
} = props;
useEffect(() => {
if (!dateMatches) {
const date = new Date();
getDateMatches({ sportId, date });
}
}, [dateMatches, getDateMatches, sportId]);
return (<div>{component stuff here..}</div>);
};
please read the error message thoroughly, mine was pointing to SignIn Component that had a bad setState. which when i examined, I had an onpress that was not an Arrow function.
it was like this:
onPress={navigation.navigate("Home", { screen: "HomeScreen" })}
I changed it to this:
onPress={() => navigation.navigate("Home", { screen: "HomeScreen" }) }
My error message was:
Warning: Cannot update a component
(ForwardRef(BaseNavigationContainer)) while rendering a different
component (SignIn). To locate the bad setState() call inside
SignIn, follow the stack trace as described in
https://reactjs.org/link/setstate-in-render
in SignIn (at SignInScreen.tsx:20)
I fixed this issue by removing the dispatch from the register components render method to the componentwillunmount method. This is because I wanted this logic to occur right before redirecting to the login page. In general it's best practice to put all your logic outside the render method so my code was just poorly written before. Hope this helps anyone else in future :)
My refactored register component:
class Register extends Component {
componentWillUnmount() {
// Reset register status to allow return to register page
if ( this.props.registerStatus !== "" ) this.props.dispatch( resetRegisterStatus() )
}
render() {
if( this.props.registerStatus === SUCCESS ) {
return <Redirect push to = {LOGIN}/>
}
return (
<div style = {{paddingTop: "180px", background: 'radial-gradient(circle, rgba(106,103,103,1) 0%, rgba(36,36,36,1) 100%)', height: "100vh"}}>
<RegistrationForm/>
</div>
);
}
}
I think that this is important.
It's from this post that #Red-Baron pointed out:
#machineghost : I think you're misunderstanding what the message is warning about.
There's nothing wrong with passing callbacks to children that update state in parents. That's always been fine.
The problem is when one component queues an update in another component, while the first component is rendering.
In other words, don't do this:
function SomeChildComponent(props) {
props.updateSomething();
return <div />
}
But this is fine:
function SomeChildComponent(props) {
// or make a callback click handler and call it in there
return <button onClick={props.updateSomething}>Click Me</button>
}
And, as Dan has pointed out various times, queuing an update in the same component while rendering is fine too:
function SomeChildComponent(props) {
const [number, setNumber] = useState(0);
if(props.someValue > 10 && number < 5) {
// queue an update while rendering, equivalent to getDerivedStateFromProps
setNumber(42);
}
return <div>{number}</div>
}
If useEffect cannot be used in your case or if the error is NOT because of Redux
I used setTimeout to redirect one of the two useState variables to the callback queue.
I have one parent and one child component with useState variable in each of them. The solution is to wrap useState variable using setTimeout:
setTimeout(() => SetFilterData(data), 0);
Example below
Parent Component
import ExpenseFilter from '../ExpensesFilter'
function ExpensesView(props) {
const [filterData, SetFilterData] = useState('')
const GetFilterData = (data) => {
// SetFilterData(data);
//*****WRAP useState VARIABLE INSIDE setTimeout WITH 0 TIME AS BELOW.*****
setTimeout(() => SetFilterData(data), 0);
}
const filteredArray = props.expense.filter(expenseFiltered =>
expenseFiltered.dateSpent.getFullYear().toString() === filterData);
return (
<Window>
<div>
<ExpenseFilter FilterYear = {GetFilterData}></ExpenseFilter>
Child Component
const ExpensesFilter = (props) => {
const [filterYear, SetFilterYear] = useState('2022')
const FilterYearListener = (event) => {
event.preventDefault()
SetFilterYear(event.target.value)
}
props.FilterYear(filterYear)
return (
Using React and Material UI (MUI)
I changed my code from:
<IconButton onClick={setOpenDeleteDialog(false)}>
<Close />
</IconButton>
To:
<IconButton onClick={() => setOpenDeleteDialog(false)}>
<Close />
</IconButton>
Simple fix
If you use React Navigation and you are using the setParams or setOptions you must put these inside method componentDidMount() of class components or in useEffects() hook of functional components.
Minimal reproducing example
I was a bit confused as to what exactly triggers the problem, having a minimal immediately runnable example helped me grasp it a little better:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/#babel/standalone#7.14.7/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function NotMain(props) {
props.setN(1)
return <div>NotMain</div>
}
function Main(props) {
const [n, setN] = React.useState(0)
return <>
<NotMain setN={setN} />
<div>Main {n}</div>
</>
}
ReactDOM.render(
<Main/>,
document.getElementById('root')
);
</script>
</body>
</html>
fails with error:
react-dom.development.js:61 Warning: Cannot update a component (`Main`) while rendering a different component (`NotMain`). To locate the bad setState() call inside `NotMain`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
followed by a stack trace:
at NotMain (<anonymous>:16:9)
at Main (<anonymous>:21:31)
Presumably 16:9 would be the exact line where props.setN(1) is being called from, but the line numbers are a bit messed up because of the Babel JSX translation.
The solution like many other answers said is to do instead:
function NotMain(props) {
React.useEffect(() => { props.setN(1) }, [])
return <div>NotMain</div>
}
Intuitively, I think that the general idea of why this error happens is that:
You are not supposed to updat state from render methods, otherwise it could lead to different results depending on internal the ordering of how React renders things.
and when using functional components, the way to do that is to use hooks. In our case, useEffect will run after rendering is done, so we are fine doing that from there.
When using classes this becomes slightly more clear and had been asked for example at:
Calling setState in render is not avoidable
Calling setState() in React from render method
When using functional components however, things are conceptually a bit more mixed, as the component function is both the render, and the code that sets up the callbacks.
I was facing same issue, The fix worked for me was if u are doing
setParams/setOptions
outside of useEffect then this issue is occurring. So try to do such things inside useEffect. It'll work like charm
TL;DR;
For my case, what I did to fix the warning was to change from useState to useRef
react_devtools_backend.js:2574 Warning: Cannot update a component (`Index`) while rendering a different component (`Router.Consumer`). To locate the bad setState() call inside `Router.Consumer`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at Route (http://localhost:3000/main.bundle.js:126692:29)
at Index (http://localhost:3000/main.bundle.js:144246:25)
at Switch (http://localhost:3000/main.bundle.js:126894:29)
at Suspense
at App
at AuthProvider (http://localhost:3000/main.bundle.js:144525:23)
at ErrorBoundary (http://localhost:3000/main.bundle.js:21030:87)
at Router (http://localhost:3000/main.bundle.js:126327:30)
at BrowserRouter (http://localhost:3000/main.bundle.js:125948:35)
at QueryClientProvider (http://localhost:3000/main.bundle.js:124450:21)
The full code for the context of what I did (changed from the lines with // OLD: to the line above them). However this doesn't matter, just try changing from useState to useRef!!
import { HOME_PATH, LOGIN_PATH } from '#/constants';
import { NotFoundComponent } from '#/routes';
import React from 'react';
import { Redirect, Route, RouteProps } from 'react-router-dom';
import { useAccess } from '#/access';
import { useAuthContext } from '#/contexts/AuthContext';
import { AccessLevel } from '#/models';
type Props = RouteProps & {
component: Exclude<RouteProps['component'], undefined>;
requireAccess: AccessLevel | undefined;
};
export const Index: React.FC<Props> = (props) => {
const { component: Component, requireAccess, ...rest } = props;
const { isLoading, isAuth } = useAuthContext();
const access = useAccess();
const mounted = React.useRef(false);
// OLD: const [mounted, setMounted] = React.useState(false);
return (
<Route
{...rest}
render={(props) => {
// If in indentifying authentication state as the page initially loads, render a blank page
if (!mounted.current && isLoading) return null;
// OLD: if (!mounted && isLoading) return null;
// 1. Check Authentication is one step
if (!isAuth && window.location.pathname !== LOGIN_PATH)
return <Redirect to={LOGIN_PATH} />;
if (isAuth && window.location.pathname === LOGIN_PATH)
return <Redirect to={HOME_PATH} />;
// 2. Authorization is another
if (requireAccess && !access[requireAccess])
return <NotFoundComponent />;
mounted.current = true;
// OLD: setMounted(true);
return <Component {...props} />;
}}
/>
);
};
export default Index;
My example.
Code with that error:
<Form
initialValues={{ ...kgFormValues, dataflow: dataflows.length > 0 ? dataflows[0].df_tpl_key : "" }}
onSubmit={() => {}}
render={({values, dirtyFields }: any) => {
const kgFormValuesUpdated = {
proj_key: projectKey,
name: values.name,
description: values.description,
public: values.public,
dataflow: values.dataflow,
flavours: flavoursSelected,
skipOCR: values.skipOCR
};
if (!_.isEqual(kgFormValues, kgFormValuesUpdated)) {
setNewKgFormValues(kgFormValuesUpdated);
}
Working Code:
<Form
initialValues={{ ...kgFormValues, dataflow: dataflows.length > 0 ? dataflows[0].df_tpl_key : "" }}
onSubmit={() => {}}
render={({ values, dirtyFields }: any) => {
useEffect(() => {
const kgFormValuesUpdated = {
proj_key: projectKey,
name: values.name,
description: values.description,
public: values.public,
dataflow: values.dataflow,
flavours: flavoursSelected,
skipOCR: values.skipOCR
};
if (!_.isEqual(kgFormValues, kgFormValuesUpdated)) {
setNewKgFormValues(kgFormValuesUpdated);
}
}, [values]);
return (
I had the same problem. I was setting some state that was storing a function like so:
// my state definition
const [onConfirm, setOnConfirm] = useState<() => void>();
// then I used this piece of code to update the state
function show(onConfirm: () => void) {
setOnConfirm(onConfirm);
}
The problem was from setOnConfirm. In React, setState can take the new value OR a function that returns the new value. In this case React wanted to get the new state from calling onConfirm which is not correct.
changing to this resolved my issue:
setOnConfirm(() => onConfirm);
I was able to solve this after coming across a similar question in GitHub which led me to this comment showing how to pinpoint the exact line within your file causing the error. I wasn't aware that the stack trace was there. Hopefully this helps someone!
See below for my fix. I simply converted the function to use callback.
Old code
function TopMenuItems() {
const dispatch = useDispatch();
function mountProjectListToReduxStore(projects) {
const projectDropdown = projects.map((project) => ({
id: project.id,
name: project.name,
organizationId: project.organizationId,
createdOn: project.createdOn,
lastModifiedOn: project.lastModifiedOn,
isComplete: project.isComplete,
}));
projectDropdown.sort((a, b) => a.name.localeCompare(b.name));
dispatch(loadProjectsList(projectDropdown));
dispatch(setCurrentOrganizationId(projectDropdown[0].organizationId));
}
};
New code
function TopMenuItems() {
const dispatch = useDispatch();
const mountProjectListToReduxStore = useCallback((projects) => {
const projectDropdown = projects.map((project) => ({
id: project.id,
name: project.name,
organizationId: project.organizationId,
createdOn: project.createdOn,
lastModifiedOn: project.lastModifiedOn,
isComplete: project.isComplete,
}));
projectDropdown.sort((a, b) => a.name.localeCompare(b.name));
dispatch(loadProjectsList(projectDropdown));
dispatch(setCurrentOrganizationId(projectDropdown[0].organizationId));
}, [dispatch]);
};
My case was using setState callback, instead of setState + useEffect
BAD ❌
const closePopover = useCallback(
() =>
setOpen((prevOpen) => {
prevOpen && onOpenChange(false);
return false;
}),
[onOpenChange]
);
GOOD ✅
const closePopover = useCallback(() => setOpen(false), []);
useEffect(() => onOpenChange(isOpen), [isOpen, onOpenChange]);
I got this when I was foolishly invoking a function that called dispatch instead of passing a reference to it for onClick on a button.
const quantityChangeHandler = (direction) => {
dispatch(cartActions.changeItemQuantity({title, quantityChange: direction}));
}
...
<button onClick={() => quantityChangeHandler(-1)}>-</button>
<button onClick={() => quantityChangeHandler(1)}>+</button>
Initially, I was directly calling without the fat arrow wrapper.
Cannot update a component while rendering a different component warning
I have the same problem but when I dispatch an action inside a component rendered. You should dispatch the action inside useEffect hook to fix that problem
//dispatch action to inform user that 'Marked days already have hours!'
React.useEffect(() => {
if (btn_class == 'redButton') {
dispatch({ type: ActionType.ADD_NOTIFICATION, payload: 'Marked days already have hours!' });
} else {
dispatch({ type: ActionType.ADD_NOTIFICATION, payload: '' });
}
}, [btn_class, dispatch]);
also use union type for btn-class variable
*`
type ButtonState = 'btnAddDay' | 'redButton' | 'btnAddDayBlue' | 'btnAddDayGreen';
`*
Using some of the answers above, i got rid of the error with the following:
from
if (value === "newest") {
dispatch(sortArticlesNewest());
} else {
dispatch(sortArticlesOldest());
}
this code was on my component top-level
to
const SelectSorting = () => {
const dispatch = useAppDispatch();
const {value, onChange} = useSelect();
useEffect(() => {
if (value === "newest") {
dispatch(sortArticlesNewest());
} else {
dispatch(sortArticlesOldest());
}
}, [dispatch, value]);

React array.length returning 0 even though array has items

I'm pulling stuff from my database using Firestore. When I log inside the function that pulls the data, the array has the data. When I log in my main component, it also has. But for some reason, .map doesn't work, and when I try array.length it returns 0. I was using a map, but then I changed it to use a function to try to get the error.
export default function Search() {
const [searchedData, setSearchedData] = useState([]);
const [loading, setLoading] = useState(true);
const [noBook, setNoBook] = useState(false);
const [showBooks, setShowBooks] = useState(false);
const link = useLocation();
useEffect(() => {
const srch = link.pathname.substring(8);
loadSearchBooks(srch);
}, [link]);
async function loadSearchBooks(srch) {
try {
const bookArray = await getSearchedBooks(srch);
bookArray ? setShowBooks(true) : setNoBook(true);
setSearchedData(bookArray);
} catch (e) {
setSearchedData(null);
} finally {
setLoading(false);
}
}
function renderBooks() {
console.log(searchedData);
const l = searchedData.length;
return l;
}
return (
<div>
<Navbar />
<div className={searchBookWrapper}>
{loading && 'Carregando'}
{showBooks && renderBooks()}
{noBook && <BookCardItem />}
</div>
</div>
);
}
When doing this, console.log(searchedData) returns the array, but const l = searchedData.length shows just a 0. When I search again, the number changes to 12 for a moment right when it's about to change. This is the previous code:
return (
<div>
<Navbar />
<div className={searchBookWrapper}>
{loading && 'Carregando'}
{showBooks &&
searchedData.map(({ afn, aln, notes, quant, title }, index) => {
return (
<BookCardItem
key={title}
firstName={afn}
lastName={aln}
notes={notes}
quant={quant}
title={title}
bookNumber={index}
/>
);
})}
{noBook && <BookCardItem />}
</div>
</div>
);
}
The same thing happened. The bookInfo appeared just for a moment when I searched again.
From the first code in this question, this is the console:
Console - one empty array, then two filled ones
if useLocation is an API call or other async function, react prefers those to be in a useEffect. If they are not, it can give inconsistent results. Try putting useLocation inside a useEffect. If you only plan on useLocation firing once (and thus loadSearchedBooks only firing once) you can even put them in the same useEffect, just don't make them rerender based on the thing they update.
useEffect(() => {
useLocation().then(link => {
const srch = link.pathname.substring(8);
loadSearchBooks(srch);
}
}, []);
Hopefully, this will fix your problem.

How to delegate responsibilities to each component correctly in React?

I have a hook, which basically renders a Loading Indicator at the right side of my Stack Header.
Here is how it looks like:
export default function useHeaderRightLoadingIndicator(loading = false) {
const navigation = useNavigation();
const renderLoading = useCallback(() => {
if (!loading) return null;
return (
<Loading
size={20}
style={styles.loadingIndicator}
/>
);
}, [loading]);
useEffect(() => {
navigation.setOptions({
headerRight: renderLoading,
});
}, [navigation, renderLoading]);
}
Now, I want to use it during a delete operation. Basically, I have a screen with a lot of erasable items rendered in a FlatList.
The screen contains the stateful array of data... and pass it to the FlatList component.
In order to avoid repeating my self each time I wanna delete the same item (but in a different screen), I have decided to move the responsibility of the deletion to the item itself (I mean, to the FlatList's item). For this, I have been forced to use a context in order to update the graphical interface without having to pass the "setState" node by node.
So... I have the following:
function MyScreen() {
const statefulData = useData(); // consuming context...
return <CustomFlatList data={statefulData} />
}
...
function ErasableItem({ id }) {
const { isLoading, error, deleteItem } = useDeleteItem(id);
return <Text onPress={deleteItem}>Delete!</Text>;
}
Inside the logic of useDeleteItem, I just make an api call and update my context in order to update the UI (delete the item from the list).
This has sense to me... I don't have a super screen which does everything, instead, each node does its own stuff.
But... what if I wanna use the useHeaderRightLoadingIndicator() I described at the beginning? As I have delegated the deletion responsibility to an unmountable component, I will not be able to stop loading it. I mean, if I do:
function ErasableItem({ id }) {
const { isLoading, error, deleteItem } = useDeleteItem(id);
useHeaderRightLoadingIndicator(isLoading);
return <Text onPress={deleteItem}>Delete!</Text>;
}
As the item will be unmounted from the screen, the useEffect of the useHeaderRightLoadingIndicator will not be called the last time, when isLoading toggles from true to false.
In order to fix that, I will have to run a cleanup function, like this:
export default function useHeaderRightLoadingIndicator(loading = false) {
const navigation = useNavigation();
const renderLoading = useCallback(() => {
if (!loading) return null;
return (
<Loading
size={20}
style={styles.loadingIndicator}
/>
);
}, [loading]);
useEffect(() => {
navigation.setOptions({
headerRight: renderLoading,
});
return () => {
// cleanup
navigation.setOptions({ headerRight: null });
};
}, [navigation, renderLoading]);
}
Something that makes me doubt, since if I had delegated the delete operation to the screen, which is not unmountable, I would not have any problem and I would not have to execute that cleanup function.
What is the logic to follow when delegating responsibilities? Is my delegation incorrect in React?

Child components get updated properly only after the second click in react functional component

I have the following component, where review assignments (props.peerReviewAssignmentIds) are loaded for a student's own work (related event is onClick_Submission) or a peer's work to review (related event is onClick_PeerReview ). These events work fine and the related data is loaded successfully. However, there is a problem with updating the content of the child components based on the value of the props.peerReviewAssignmentIds, which I elaborate below.
const AssignmentItem = (props) => {
const assignment = props.assignments[props.currentAssignmentId];
const onClick_Submission = (e) => {
e.preventDefault();
if (!st_showSubmission) {
props.fetchPeerReviewAssignmentForStudent(currentUserId, assignment.activeReviewRoundId);
}
set_showSubmission(!st_showSubmission);
set_isPeerReview(false);
}
const onClick_PeerReview = (e) => {
e.preventDefault();
if (!st_showPeerReviews) {
if (st_submissionContiues === false)
props.fetchPeerReviewAssignmentForReviewer(currentUserId, assignment.activeReviewRoundId);
}
set_showPeerReviews(!st_showPeerReviews);
set_isPeerReview(true);
}
return (
<>
{
st_showSubmission === true && props.peerReviewAssignmentIds.length > 0 &&
<ReviewPhaseInfoForSubmission isPeerReview={false} />
}
</>
)
}
const mapStateToProps = state => ({
peerReviewAssignmentIds: state.peerReviewAssignmentReducer.peerReviewAssignmentIds,
loading_pra: state.peerReviewAssignmentReducer.loading,
error_pra: state.peerReviewAssignmentReducer.error,
})
I will try to explain the problem with an example. When the first time onClick_Submission is triggered, props.peerReviewAssignmentIds[0] is set to 2, and all the sub components are loaded properly. Next, when onClick_PeerReview is triggered, props.peerReviewAssignmentIds[0] is set to 1, which is correct. But, the child components get updated according to the previous value of props.peerReviewAssignmentIds[0], which was 2. If the onClick_PeerReview event is triggered second time, then the child components get updated correctly according to the current value of props.peerReviewAssignmentIds[0], which is 1. Any ideas why this might be happening?
I further explain my code below.
Below is the ReviewPhaseInfoForSubmission component. In this component, based on props.peerReviewAssignmentIds[0] value (which gets updated in the parent component above) etherpadLaterSubmission variable is created with props.createSession_laterSubmission method.
const ReviewPhaseInfoForSubmission = (props) => {
const [st_is_discussFeedback, set_is_discussFeedback] = useState(false);
useEffect(() => {
set_is_discussFeedback(true);
if (props.etherpadLaterSubmission === null) {
props.createSession_laterSubmission(props.peerReviewAssignmentIds[0], discussFeedback.dueDate);
}
}, [])
return (
<div className="p-1">
{
st_is_discussFeedback === true &&
<ProvideOrDiscussFeedback provide0discuss1revise2={1} isPeerReview={props.isPeerReview} />
}
</div>
);
}
const mapStateToProps = (state) => ({
etherpadLaterSubmission: state.etherpadReducer.etherpadLaterSubmission,
loading_ep: state.etherpadReducer.loading,
})
Then, in a child component, ProvideOrDiscussFeedback (see below), the props.etherpadLaterSubmission value is used for display purposes.
const ProvideOrDiscussFeedback = (props) => {
return <div className="p-3 shadow">
{
props.etherpadLaterSubmission &&
<div>
<DisplayingEtherpad etherpadSession={props.etherpadLaterSubmission } />
</div>
}
</div>
}

React component is rendering old state before getting new data

When the component first renders it gets from the switchcase (enters ALL case because its the default value) on the usePositions hook, the value for positions and I set them there, and return them to the Positions controller.
The problem comes when the selectedClient changes from ALL to TODAY (its a context value, I change it in a Sidebar component somewhere else) and before entering the switch case in TODAY value to get the positions of today, I noticed the Positions component already rendered the old state of ALL positions again! Then a second later it renders correctly the todays positions.
I noticed this because my browser on my network tab shows some calls to the server that IndividualPosition makes it means that it rendered
This is my component where it calls the usePositions hook
export const Positions = () => {
const { selectedClient } = useSelectedClientValue();
const { loading, setLoading } = useLoadingValue();
const { positions } = usePositions(selectedClient);
const clientName =
typeof selectedClient === "string" ? selectedClient : selectedClient.name;
useEffect(() => {
setLoading(false);
}, [positions]);
let positionsView = (
<ul className="positions__list">
{positions.map((position) => (
<IndividualPosition position={position} key={position.positionId} />
))}
</ul>
);
return (
<div className="positions" data-testid="positions">
{!loading ? (
<>
<div className="positions__header">
<h2 data-testid="client-name">{clientName}</h2>
</div>
{positionsView}
</>
) : (
<div className="loading-main-window">
<Spinner />
</div>
)}
</div>
);
};
This is the hook where I fetch the data
export const usePositions = selectedClient => {
const [positions, setPositions] = useState([]);
const {setLoading} = useLoadingValue()
useEffect(() => {
setLoading(true);
switch (selectedClient) {
case 'ALL':
getPositions().then(pos => {
setPositions(pos);
});
break;
case 'TODAY':
getTodayPositions().then(pos => {
setPositions(pos);
});
break;
default:
break;
}
}, [selectedClient]);
return {positions, setPositions};
};
The useEffect runs each time the selectedClient change
It looks like the component renders again before getting the todays data and thats why it shows the old state before getting the new data, but I thought that could be avoided with the loading flag
Basically:
-Positions renders, the hook fetches allPositions, its fine
-If I change in the sidebar the value of the selectedClient context value, the Positions components renders again, rendering the IndividualComponent but with the state of allPositions.
- Instead it should wait till todaysPositions fetches to show the new state (loading should do that)
I already tried having a loading local state (my loading is a context value)
Moving the loading in the useEffects on my local component instead of my hook
Setting loading to false inside the hook after fetching the data
Any ideas?

Categories