Cannot update during an existing state transition in stateless component - javascript

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

Related

react component return problem when props are null

export default function RenderPages({storage, setStorage, state, setState}){
let elRefs= useRef()
if(!storage) return
if(!state.currentFileId || !state.currentFolderId) return
const content = storage[state.currentFolderId][state.currentFileId].content
return (
<div className="writing">
<input ref={elRefs}/>
{content.map((page, index)=>
<div className='textarea'>
<textarea placeholder='write here' value={page} id={"page"+index} onChange={(e)=>onChange(e, index)} rows={rows} cols={cols}></textarea>
</div>)}
</div>
)
}
There are some props(state, storage) and they are sometimes null value. What I am doing now is checking the values of state and storage, returning blank early if those values are null. If I don't return in advance, the variable "content" get error because it needs state and storage value. Now this is the problem. I want to use "useRef", and if the component return early "elRefs" is assigned null value, so I can't get DOM element. What should I do?
I put your booleans into single function and seperated the renderable part of component. If bool is false, that component is simply not rendered. Of course you can put in there other component which shows error data or loading gif or something like that. Hope this works!
export default function RenderPages({ storage, setStorage, state, setState }) {
const elRefs = useRef()
const content = storage[state.currentFolderId][state.currentFileId].content
// content to render in seperate component
const Pages = () => (
<div className="writing">
<input ref={elRefs} />
{
content.map((page, index) =>
<div className='textarea'>
<textarea
placeholder='write here'
value={page} id={"page" + index}
onChange={(e) => onChange(e, index)}
rows={rows}
cols={cols}
/>
</div>
)
}
</div>
)
// decide to or not to render component
const renderable = storage &&
state.currentFileId &&
state.currentFolderId
return (
<>
{
renderable
? <Pages />
: <></> // returns empty component
}
</>
)
}

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
}

How can I return a React Component from a function and render it onClick?

I have an array of objects that I am looping through in my higher order component. On click I want to pass one of those objects to my component and render that component. The problem I am having is it's unclear how to return the component and have React update the render to show that component. I've read several Stackoverflow posts on this and the problem of putting the component in the html is it passes all the items in my loop to my component instead of just the one I need onClick.
The warning I'm seeing in my console is react_devtools_backend.js:3973 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> Which makes sense however, I'm not sure what the proper syntax is to solve this problem. Below is the relevant code.
const FormGroup = ({index}) => {
const renderSection = form => ( // I want to render this
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
);
const addSection = form => {
setAdditionalSection(prevState => !prevState);
console.log('form', form);
renderSection(form);
};
return (
<section>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
{myForm.controls.map(form => {
if (form.type === 'section') {
return (
<FormSection>
<div className="section__label">
<h4>{form.label}</h4>
</div>
...
{form.button
&& (
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)} // called here and passes my form object
>
<span className="button--small">{form.button}</span>
</LinkButton>
</FormAdd>
)}
{renderFormType(form)}
</FormSection>
);
}
})}
// renderSection should render here outside of the loop
{additionalSection && renderSection}
<input type="submit" />
</form>
</FormProvider>
</section>
);
Put it in state and just render.
const { extraSection, setExtraSection } = useState(null);
const addSection = form => {
setExtraSection(form);
};
return (
...
// renderSection should render here outside of the loop
{extraSection}
);
The problem with your original approach is that you were not saving that element anywhere. You called renderSection upon click, but that just called the function without storing or displaying that code anywhere.
Then in you render method, you again referenced rederSection. This is just a copy of the function, now without a parameter. It doesn't have any element as parameter, and it's not even called, so React is complaining that you're trying to render a function, instead of an element.
try it
const renderSection = form => (
return(
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
)
);
Just in case anyone else may be struggling with this. Thanks to szaman and this article. Turns out I need to pass the section in the loop, but just pass through the data that was clicked.
const addSection = form => {
console.log('form', form);
setAdditionalSection(form);
};
return (
<section>
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
{myForm.controls.map(form => {
if (form.type === 'section') {
return (
<FormSection>
<div className="section__label">
<h4>{form.label}</h4>
</div>
...
{form.button
&& (
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)} // called here and passes my form object
>
<span className="button--small">{form.button}</span>
</LinkButton>
</FormAdd>
)}
{additionalSection && additionalSection.position === ind && (
<AdditiveSection
form={additionalSection}
register={register}
errors={errors}
/>
)}
{renderFormType(form)}
</FormSection>
);
}
})}
<input type="submit" />
</form>
</FormProvider>
</section>
);

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

Remove dynamic rendered element from dom in ReactJS

Currently I've got a react component that looks like this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
return (
<div key={i} className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div>
);
});
};
Based on the cards array, it renders something like this:
Rendered Component
Whenever I click the trash button, I make a request to my backend, edit the list on my database and rerender the component based on the now updated "cards". The problem is that this takes sometime to happen, so i wanted a way to remove it from the dom instantly while my backend does it's job.
somehting like
{show ? renderCompoennt : null}
I've tried using vanilla javascript to grab the parent from the trash can, which would be the card i want to remove, but the results are unpredictable and it's quite slow as well.
My latest try was this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
const [show, setShow] = useState(true);
return (
<div key={i}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
});
};
but react won't let me do this. Even tho its fast, everytime one item gets deleted, react complains that "less hooks were rendered" and crashes the app.
You are attempting to do some Optimistic UI, in which you assume that your action will succeed, and reflect the expected/assumed state instantly, before the request to the backend completes. This would be in lieu of showing some progress/busy indicator, like a spinner, until the action completes with the server.
The first problem and immediate problem in your code-- it violates the rules of hooks, which state that hooks may only be used at the top-level (never inside loops, conditionals, etc).
The second problem is that you are leveraging vanilla JS to manipulate the DOM directly; this generally an antipattern in MV* frameworks, and very much so here. Instead, I would suggest doing managing it in your data model; something like this:
Rewrite your .map handler to return null if the card has a deleted property.
When the user clicks the trash button, do two things:
Make the request to the backend to delete it
Use a setState to add a deleted: true property to the clicked card
Now you will get a rerender that will omit the deleted card, and also make the request to the backend, all inside the React data model. Make sure that you handle complexity for:
How to handle the response
How to handle an error if the deletion fails at the backend
How to manage if a user quickly clicks many cards for deletion before any of the requests can complete.
The problem is that in the first render you have {cards.length} calls to hook "useState" within GeraCard, but after deletion of one card, you will have {cards.length-1} calls to hook "useState". As the React docs state:
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function. By
following this rule, you ensure that Hooks are called in the same
order each time a component renders. That’s what allows React to
correctly preserve the state of Hooks between multiple useState and
useEffect calls.
You should extract the content of map callback into separate a component.
const GeraCards = (cards, cart = false) => {
return cards.map((v, i) =>
<GeraCard card={v} index={i} cart={cart} />
);
};
const GeraCard = ({ card, index, cart }) => {
const [show, setShow] = useState(true);
const v = card;
return (
<div key={index}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
}

Categories