React-Native: cannot update a component while rendering a different component - javascript

I've got this simple component Login:
function Login() {
const [isFormValidState, setIsFormValidState] = React.useState(false);
const [credentialState, setCredentialState] = React.useState();
function getFormErrors(errors: any, dirty: boolean) {
setIsFormValidState(!Object.keys(errors).length && dirty);
}
function getFormValues(values: any) {
setCredentialState(values);
}
function doAction() {
//credentialState rest call...
}
return (
<View>
<Text>Login</Text>
<UserCredentialForm getFormValues={getFormValues} getFormErrors={getFormErrors}/>
<Button title='Entra' disabled={!isFormValidState} onPress={doAction}/>
</View>
);
}
Which calls UserCredentialForm:
export default function UserCredentialForm({ getFormValues, getFormErrors }) {
[...]
return (
<Formik innerRef={formRef} validationSchema={formSchema} initialValues={state.form} onSubmit={() => { }}>
{({ handleChange, values, touched, errors, dirty }) => {
getFormValues(values);
getFormErrors(errors, dirty);
return <React.Fragment>
// <TextInput/>....
</React.Fragment>
}}
</Formik>
);
[...]
}
While navigating in my app I've got this error:
react native cannot update a component Login while rendering a
different component Formik.
Then it points me to the error in the setCredentialState inside getFormValues handler in Login component.
I've resolved this using a ref instead of a state, but the problem itself is unsolved to me.
What if I need to update my parent component view after a child event?

The reason for that error is because you call setState inside render(). The call getFormValues(values), which set the state of credentialState is called inside the render.
When the state is set, the Login component get rerendered, thus recreating a new function of getFormValues. As this is used as the prop of UserCredentialForm, it also causes that component to rerender, which causes the render prop inside Formik to calls again, which calls getFormValues causing the state change, causing an infinite loop.
One solution you can try is to add useCallback to the two functions, which prevent them to have new identities after the state changes and consequently change the props, thus creating infinite rerender.
function Login() {
const [isFormValidState, setIsFormValidState] = React.useState(false);
const [credentialState, setCredentialState] = React.useState();
const getFormErrors = useCallback(function getFormErrors(errors: any, dirty: boolean) {
setIsFormValidState(!Object.keys(errors).length && dirty);
}, []);
const getFormValues = useCallback(function getFormValues(values: any) {
setCredentialState(values);
}, []);
function doAction() {
//credentialState rest call...
}
return (
<View>
<Text>Login</Text>
<UserCredentialForm getFormValues={getFormValues} getFormErrors={getFormErrors}/>
<Button title='Entra' disabled={!isFormValidState} onPress={doAction}/>
</View>
);
}
However, there is still an issue and that is the identity of values may not be stable and by setting it to state, it will keep causing rerender. What you want to do is to tell UserCredentialForm not to rerender even when that state changes, and since the state is not used as a prop in UserCredentialForm, you can do that with React.memo.
export default React.memo(function UserCredentialForm({ getFormValues, getFormErrors }) {
[...]
return (
<Formik innerRef={formRef} validationSchema={formSchema} initialValues={state.form} onSubmit={() => { }}>
{({ handleChange, values, touched, errors, dirty }) => {
getFormValues(values);
getFormErrors(errors, dirty);
return <React.Fragment>
// <TextInput/>....
</React.Fragment>
}}
</Formik>
);
[...]
})

I think you got an unlimited loop of rendering,
you setState by getFormValues and the Login component re-render make UserCredentialForm re-render too, so it call getFormValues again and again
You can call getFormValues(values) in a useEffect hook after values of formik update

You are calling getFormValues and getFormErrors inside a callback provided by Formik, that means you cannot wrap them inside an effect hook to suppress this warning or it will violate rules of hooks.
I faced the same issue in React JS and got rid of it by using the following: approach.
I used useFormik hook as an alternate.
and afterwards I refactored Formik form into a new component from where I made state changes to parent component.
This way I neither violated rules of hooks nor got this warning.
Also in the that newly refactored component you might need useFormikContext and useField

Simple example can be like
UserCredentialForm:
// Formik x React Native example
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { Formik } from 'formik';
export const MyReactNativeForm = ({onSubmit}) => (
<Formik
initialValues={{ email: '' }}
onSubmit={values => onSubmit(values)}
>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View>
<TextInput
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
<Button onPress={handleSubmit} title="Submit" />
</View>
)}
</Formik>
);
Usage like
function Login() {
function doAction(values) {
console.log(values);
//credentialState rest call...
}
return (
<View>
....
<UserCredentialForm onSubmit={doAction} />
....
</View>
);
}

Related

SonarQube "Do not define components during render" with MUI/TS but can't send component as prop

I am getting the following error during sonarqube scan:
Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “SectionTab” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.
I understand that it says that I should send the component as a prop from the parent, but I don't want to send the icon everytime that I want to use this component, is there another way to get this fixed?
import Select from "#mui/material/Select";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faAngleDown } from "#fortawesome/pro-solid-svg-icons/faAngleDown";
const AngleIcon = ({ props }: { props: any }) => {
return (
<FontAwesomeIcon
{...props}
sx={{ marginRight: "10px" }}
icon={faAngleDown}
size="xs"
/>
);
};
const SectionTab = () => {
return (
<Select
id="course_type"
readOnly={true}
IconComponent={(props) => <AngleIcon props={props} />}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
export default SectionTab;
What can you do:
Send the component as the prop:
IconComponent={AngleIcon}
If you need to pass anything to the component on the fly, you can wrap it with useCallback:
const SectionTab = () => {
const IconComponent = useCallback(props => <AngleIcon props={props} />, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
This would generate a stable component, but it's pretty redundant unless you need to pass anything else, and not via the props. In that case, a new component would be generated every time that external value changes, which would make it unstable again. You can use refs to pass values without generating a new component, but the component's tree won't be re-rendered to reflect the change in the ref.
const SectionTab = () => {
const [value, setValue] = useState(0);
const IconComponent = useCallback(
props => <AngleIcon props={props} value={value} />
, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};

React's Context API re-renders all components that are wrapped inside the Context

This is a very common performance problem while using the Context API. Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown.
If I have a the wrapper as this:
<CounterProvider>
<SayHello />
<ShowResult />
<IncrementCounter />
<DecrementCounter />
</CounterProvider>
And the value props as:
<CounterContext.Provider value={{increment, decrement, counter, hello }} >
{children}
</CounterContext.Provider>
Everytime I increment the count value from the IncrementCounter component, the entire set of wrapped components re-renders as it is how the Context API is supposed to work.
I did a bit of research and came across these solutions:
Split the Context into N number of Context according to the use-case : This solution works as expected.
Wrap the value provider using React.Memo: I saw a lot of articles suggesting to the React.Memo API as follows:
<CounterContext.Provider
value={useMemo(
() => ({ increment, decrement, counter, hello }),
[increment, decrement, counter, hello]
)}
>
{children}
</CounterContext.Provider>
This however doesn't work as expected. I still can see all the components getting re-rendered. What I'm doing wrong while using the Memo API? Dan Abramov does recommend to go by this approach in an open React issue
If anyone can help me out on this one. Thanks for reading.
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The above statement is true if a context is used like in the below example where components are directly nested in the provider. All of them re-render when count changes, no matter wether they are called useContext(counterContext) or not.
const counterContext = React.createContext();
const CounterContextProvider = () => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
<ComponentOne/>
<ComponentTwo />
</counterContext.Provider>
);
};
const ComponentOne = () => {
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider/>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
"Essentially whenever a state value in the context changes, the entire components that are wrapped between the provider re-renders and causes performance slowdown."
The statement is false if you are consuming nested components with children. This time when count changes CounterContextProvider renders, but since it's rendering because its state has changed and not because of its parent rendering, and because a component cannot mutate its props, React won't render children. That's it if it was a normal component.
But since there is a context involved here, React will find all components that contain useContext(counterContext) and render them.
const counterContext = React.createContext();
const CounterContextProvider = ({ children }) => {
const [count, setCount] = React.useState(0);
return (
<counterContext.Provider value={{ count, setCount }}>
<button onClick={() => setCount((prev) => prev + 1)}>Change state</button>
{children}
</counterContext.Provider>
);
};
const ComponentOne = () => {
const { count } = React.useContext(counterContext);
console.log("ComponentOne renders");
return <div></div>;
};
const ComponentTwo = () => {
console.log("ComponentTwo renders ");
return <div></div>;
};
function App() {
return (
<CounterContextProvider>
<ComponentOne />
<ComponentTwo />
</CounterContextProvider>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In the above example only ComponentOne renders when count changes, which is normal cause he is consuming it. Every component that calls useContext(counterContext) renders if one value of the context changes.
Even with useMemo wrapping the context object as you did, that's the behavior you get as soon as one variable in its dependency array changes.

Prevent a component rendering inside formik

I want to disable the rendering of a component inside react.js formik library
here is an example of code structure I have currently
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
{values?.show ?
(
<Text>Hello</Text>
) :
null}
<Rerenderedcomponent /> //no prop passed here
)
</formik>
And here is an example of my Rerendered component file
function Rerenderedcomponent()
{
const callingAPI = useCallback(()=>response,[])
}
export default React.memo(Rerenderedcomponent)
Now as I am clicking on the button(name showbtn) formik "show" field value is getting updated but my component(Rerenderedcomponent) is also getting rerendered & hence the api in it is getting called again
I tried by setting enableReinitialize={false} but nothing works
Is it possible to prevent this rerendering of the component(Rerenderedcomponent) on formik field update
PS:- The component should remain inside formik tag only
I prevent the component rerendering inside formik using the below workaround:
Created a new component say (Hello.js) & included the conditonal rendering(that was inside formik tag previously) inside it, like an example shown below
function Hello({show})
{
return(
<>
{show && <Text>Hello</Text>}
</>
)
}
export default React.memo(Hello);
Now I just imported & use the Hello.js component inside formik as shown below
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
<Hello show={values?.show}/> // Hello.js component
<Rerenderedcomponent /> //this will not rerender now
)
</formik>
Now since the component is already mounted into the DOM the rerendering will not occur on show value change
Also there is one another workaround to resolve this issue just by changing the order of components inside formik tag
<formik
initialValue={{
"show":false
}}>
return (
<button name="showbtn" onclick={setFieldValue("show",true)}/>
<Rerenderedcomponent /> //placed above conditional rendering
{ values?.show ?
(
<Text>Hello</Text>
) :
null
}
)
I moved the rerendered component above the conditional rendering & it resolved the issue
To prevent RerenderedComponent from contacting the api every time. You must define a state in the parent component and pass it to child component:
const [apiData, setApiData] = useState(); // <===
return (
<Formik
initialValues={{ show: false }}
onSubmit={(values) => {}}
>
{({ setValues, values }) => (
<Form>
<button
type="button"
onClick={() => setValues({ show: !values.show })}
>
{values.show ? "hide" : "show"}
</button>
{values.show && (
<Rerenderedcomponent apiData={apiData} setApiData={setApiData} /> // <===
)}
</Form>
)}
</Formik>
);
And in the child component, you can check the existence of apiData and communicate with the api if needed:
function Rerenderedcomponent({ apiData, setApiData }) {
useEffect(() => {
if (!apiData) {
// fetch data here ...
setApiData('<response>');
}
}, []);
return null; // A Redact component must return a value
}

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]);

Connect Material-ui ToggleButtonGroup to redux-form

I'm trying to connect material-ui ToggleButtonGroup with redux form and getting issues with this.
Here is my code:
<Field
name='operator'
component={FormToggleButtonGroup}
>
<ToggleButton value='equals'>Equal</ToggleButton>
<ToggleButton value='not_equals'>Not equal</ToggleButton>
</Field>
.. and my component, passed to Field:
const FormToggleButtonGroup = (props) => {
const {
input,
meta,
children
} = props;
return (
<ToggleButtonGroup
{...input}
touched={meta.touched.toString()}
>
{children}
</ToggleButtonGroup>
);
};
export default FormToggleButtonGroup;
the problem is, when I select value (toggle option), selected value is not passed to redux store, it passed only after loosing focus and then throws error 'newValue.splice is not a function'
Please help to deal with this issue
Sandbox with sample code
Playing with the component I finally found the solution.
I need manually assign new value got from ToggleButtonGroup component and put this value to redux store. Here is how working code looks:
const FormToggleButtonGroup = (props) => {
const {
input,
meta,
children,
...custom
} = props;
const { value, onChange } = input;
return (
<ToggleButtonGroup
{...custom}
value={value}
onChange={(_, newValue) => {
onChange(newValue);
}}
touched={meta.touched.toString()}
>
{children}
</ToggleButtonGroup>
);
};
Main change is getting redux's function onChange and call it with new value, selected when value toggled. There is onChange related to ToggleButtonGroup component and another onChange related to Redux. You need to call latter when ToggleButtonGroup's onChange occurs.

Categories