Prevent a component rendering inside formik - javascript

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
}

Related

ReactJS: Warning: Cannot update a component (`x`) while rendering a different component (`y`) with global popup notifier

i've portal which basically notifies a user with a popup if there are some new data.
Now my problem is that on first render (when i reload the page or first render of UseToastComponent with toasts.length>0) i get
Warning: Cannot update a component (`UseToastComponent`) while rendering a different component (`Layout`). To locate the bad setState() call inside `Layout`...
Now i've tried diffrent tecniques but couldn't solve it.
I've UseToastComponent
imported in _app.js like this :
<QueryClientProvider client={queryClient}>
<UseToastComponent settings={{ autoClose: false }} />
{getLayout(<Component {...pageProps} />)}
</QueryClientProvider>
Let's look at my UseToastComponent
return (
<>
{loaded &&
ReactDOM.createPortal(
<div className="mb-6 mr-6">
{toasts.map((toast) => (
<PopupNotification
key={toast.id}
...
...
/>
))}
</div>,
document.getElementById(portalId)
)}
</>
);
Now toasts is global state that is beign updated every x sec in Layout component as it's global
How i update toast (global state) in layout comp
data.data.documents.forEach((doc) => {
addToast({
...
...
});
});
For any more information ask me, thanks
EDIT:
can it be because i update a state in layout?
if (dayjs(data.data.documents[0].createdAt).isAfter(firstTimeCreated)) {
setFirstTimeCreated(data.data.documents[0].createdAt);
}
data.data.documents.forEach((doc) => {
addToast({
...
...
});
});
EDIT 1 : working example https://codesandbox.io/p/sandbox/reverent-monad-76jwg5
In the layout, add the if inside an useEffect:
useEffect(() => {
if (data && data.status === 200 && !isLoading) {
if (data.data.results.length > 0) {
data.data.results.forEach((v) => {
addToast({ circle: v.gender, text: v.name.title });
});
}
}
})
Don't know the exact reason, but with the useEffect, next will wait for the Layout to render, to add a toast, becausing adding a toast makes the useToastComponent rerender, and they cannot rerender at the same time, or you will that error

How can I make a component render onClick in a React functional component?

I'm a bit surprised I'm having trouble finding this online, but I can't seem to find an example of how to do this in a React functional component. I have a React component that I would like to render when I click a button. Right now the function fires and I can see my console.log firing, however the component isn't rendering. My first guess was that it won't render because React doesn't know to update the view, however I added boolean via useState and it still won't render. What am I doing wrong?
Below is the relevant code. How can I get the component in addSection to render?
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
return additionalSection && (
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
span className="button--small">{form.button}</span>
</LinkButton>
</FormAdd>
);
You should change your state (or a prop in your useEffect dependency array in case you had one) in order to force a rerender. In this case:
setAdditionalSection(prevState=>!prevState);
A state change like the one you are calling, will trigger a re-render.
But all html to be rendered must be included in the functional components return statement.
The elements you want to render can be conditionally rendered like this:
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
<span className="button--small">{form.button}</span>
</LinkButton>
{additionalSection &&
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
}
</FormAdd>
);

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

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

Adding conditional to react, redux, and API

I was wondering how I can make updates to a form using React, Redux, and API. I want to add a conditional to my component. As in if, the page is in edit mode, I would like to change the page to edit mode and if it is not, render the page as it does normally.
I want the user to be able to make updates and save those changes to the backend.
class SingleCampus extends React.Component {
componentDidMount() {
this.props.getUser(this.props.match.params.id);
}
render() {
const { User } = this.props;
const hasTest = user.tests && user.tests.length;
return (
<div>
<div className="single-user">
<h1>{user.name}</h1>
<im`enter code here`g src={user.imageUrl} alt={user.name} />
<h3>
<b>Address:</b>
{user.address}
</h3>
<b>Description:</b>
<p> {user.description}</p>
</div>
<hr />
{hasTest ? (
<React.Fragment>
<div>
{user.tests.map((test) => {
return (
<span key={test.id}>
<Link to={`/test/${test.id}`}>
<h3>
{test.grade}
</h3>
</Link>
</span>
);
})}
</div>
</React.Fragment>
) : (
<h2>There are no test for this user!</h2>
)}
</div>
);
}
}
const mapState = (state) => ({
user: state.user,
});
const mapDispatch = (dispatch) => ({
getUser: (id) => {
dispatch(fetchSingleUser(id));
},
});
`
a simple way to do so is to add a variable to your state, (no matter local state or Redux state)
e.g.
// I suppose this is your Redux state used to map state to props?
const mapState = (state) => ({
user: state.user,
isDisabled: state.isDisabled,
});
change your text value in the component to text box, you may need to use some UI package such as Material UI.
Then, make it disabled depends on your isDisabled state value
also, on value change, you need to implement to dispatch the updated value.
import TextField from '#material-ui/core/TextField';
......
<b>Description:</b>
<p> {user.description}</p>
<TextField
value={user.description}
onChange={handleChange} // update description value and dispatch the user object
InputProps={{
readOnly: props.isDisabled,
}}
/>
finally, add a button to change the isDisabled value if in read only mode, process save in edit mode
import Button from '#material-ui/core/Button';
......
<Button
variant="contained"
onClick={handleEditSave} // handle edit/save logic
>{props.isDisabled ? `Edit` : `Save`}
</Button>

Cannot update during an existing state transition in stateless component

I have the following warning :
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor).
with React-redux-router that I understand, but do not know how to fix.
This is the component that is generating the warning.
const Lobby = props => {
console.log("props", props)
if (!props.currentGame)
return (
<div>
<input type="text" ref={input => (roomName = input)} />
<button
className="button"
onClick={() => {
props.createRoom(roomName.value)
}}
>
Create a room
</button>
</div>
)
else
return (
<div>
{props.history.push(`/${props.currentGame}[${props.username}]`)}
</div>
)
}
export default Lobby
What I'm doing here is that my component receives the currentGame property from the Redux store. This property is initialized as null.
When the user creates a game, I want to redirect him on a new URL generated by the server that I assign inside the property currentGame with a socket.io action event that is already listening when the container of the component Lobby is initialized.
However, since the currentGame property changes, the component is re-rendered, and therefore the line
{props.history.push(`/${props.currentGame}[${props.username}]`)}
generates a warning since the property currentGame now has a value, and the history property should not get modified during the re-render.
Any idea on how to fix it ?
Thanks!
You should not write props.history.push in render, instead use Redirect
const Lobby = props => {
console.log("props", props)
if (!props.currentGame)
return (
<div>
<input type="text" ref={input => (roomName = input)} />
<button
className="button"
onClick={() => {
props.createRoom(roomName.value)
}}
>
Create a room
</button>
</div>
)
else
return (
<div>
<Redirect to={`/${props.currentGame}[${props.username}]`} />
</div>
)
}
Do one thing, instead of writing the condition and pushing with history.push(), just put the code inside componentDidMount() if you are trying to do in the beginning.
componentDidMount(){
if(condition){
history.push('/my-url');
}
}

Categories