Feature Request: `useRemount()` hook
I would like to propose a new hook, useRemount which allows the current component to be remounted, causing all state, etc to be reset.
Usage:
function exampleComponent() {
const remount = useRemount();
return <button onClick={remount} />
}
This is a "low-level" hook that would typically be used by higher level wrappers like the following custom hook:
function useKey(key: any) {
const remount = useRemount();
useEffect(() => () => {
remount();
}, [key])
}
function exampleComponentWithIdProp({itemId}, {itemId: string}) {
useKey(itemId);
const [editedValue, setEditedValue] = useState(null);
...
}
This pattern is a much safer way to prevent the "editedValue" from being transferred between items with different IDs, compared to the current solution where the parent component has to remember to pass in the key prop.
A similar use-case is for modals. It is a common pattern for a modal to take an isOpen prop so that the parent can determine when to open the modal. However, this can lead to the modal state not being properly reset between openings.
function modalComponent({isOpen}: {isOpen: boolean}) {
useKey(isOpen);
const [value, setValue] = useState("");
...
}
The remount functionality allows the modal to easily reset its own state as required.
Hi, @Diggsey,
While the proposed useRemount hook offers a convenient way to remount components, I've outlined a potential disadvantage in the context of a complex form component. The provided example below illustrates how implicit side effects and increased complexity in component lifecycles could impact the understandability and maintainability of the code.
import React, { useState, useEffect } from 'react';
import useRemount from './useRemount'; // Assume useRemount is imported from a module
function ComplexForm() {
const remount = useRemount();
const [formData, setFormData] = useState({ username: '', email: '' });
useEffect(() => {
// Simulating an API call to fetch initial form data
const fetchData = async () => {
const result = await fetch('/api/userdata');
const data = await result.json();
setFormData(data);
};
fetchData();
}, [remount]);
return (
<form>
<label>
Username:
<input
type="text"
value={formData.username}
onChange={(e) => setFormData({ ...formData, username: e.target.value })}
/>
</label>
<br />
<label>
Email:
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
</label>
<br />
<button onClick={remount}>Remount Form</button>
</form>
);
}
export default ComplexForm;
In this example, the useRemount hook is utilized to remount the ComplexForm component. The useEffect fetches initial form data, and when the form is remounted, it refetches the data, essentially resetting the form.
Disadvantages Illustrated:
-
Implicit Side Effects:
- The remounting mechanism introduces implicit side effects by triggering a refetch of form data. Developers might find it challenging to anticipate when and how the component will remount, leading to potential confusion.
-
Complex Logic:
- The logic becomes more intricate due to the need for resetting the form on remount. Understanding and maintaining such complex logic could become challenging as the component evolves.
-
Unpredictable Behavior:
- Developers, especially those not familiar with the
useRemounthook, may find it difficult to predict the behavior of the component, as it deviates from the standard React lifecycle.
- Developers, especially those not familiar with the
While the useRemount hook may address certain use cases, its application in scenarios like the one above could lead to code that is harder to understand and maintain. Developers might benefit from alternative approaches that align more closely with React's conventional patterns.
Your thoughts on addressing these concerns and potential alternatives would be greatly appreciated.
Thank you!
@Piyush-Kumar-Ghosh Thanks for your feedback.
Implicit Side Effects: The remounting mechanism introduces implicit side effects by triggering a refetch of form data. Developers might find it challenging to anticipate when and how the component will remount, leading to potential confusion.
I'm not sure I understand what you're saying here? The remount is entirely explicit in your example. Where do you see the implicitness? Contrast this with the implicit remounting that can occur if eg. an ancestor component's key prop changes.
Complex Logic: The logic becomes more intricate due to the need for resetting the form on remount. Understanding and maintaining such complex logic could become challenging as the component evolves.
Again I'm a little confused. remount() avoids the complex logic that would otherwise be needed to reset the form. If there was no remount() you'd need to:
- Explicitly call
setStateon every piece of local component state. - Duplicate the initial value of that state, violating DRY principle.
With remount() you avoid all this, resulting in simpler, more correct code.
Unpredictable Behavior: Developers, especially those not familiar with the useRemount hook, may find it difficult to predict the behavior of the component, as it deviates from the standard React lifecycle.
All changes have a learning cost. However, I wouldn't say this deviates from the React lifecycle: "mount" and "unmount" are existing lifecycle events which don't need to change. "remounting" is already a thing that can happen when a parent component changes the value of the key prop on a child. The only change here is allowing a component to request itself to be remounted.
@Diggsey Thank you for engaging in the discussion.
Regarding "Implicit Side Effects": I appreciate your observation. My intention was to highlight that, while the remount itself is explicit in the provided example, the side effect of triggering a refetch of form data might be considered implicit. In comparison to scenarios where remounting occurs due to changes in an ancestor component's key prop, where it might not be immediately obvious why a remount happened.
Concerning "Complex Logic":
I see your point, and you rightly mention that remount() simplifies the logic of resetting the form, eliminating the need for explicit state updates and avoiding redundancy.
Regarding "Unpredictable Behavior": Your perspective on the learning curve and the alignment with existing React lifecycle events is valid.
Thanks for contributing to the conversation!
I don't think remounting is the right idea here, resetting sounds better with a hook like useResetState that uses the initial state from multiple calls of useState(or resetting only some state). It would be even better than using keys or replicating its behavior that requires remounting children. Remounting sounds too expensive and forcing it is most likely a mistake.
But it's a good idea to make some things cleaner. I only worry about when you want some state to reset, but you need to keep only one state "stale" like fetched data, which also applies to what to do about the children's state, should it reset too?
@renancleyson-dev that is not right b/c of the state management rule, For cases where you need to manage complex state interactions, integrating state management tools like Redux can provide more flexibility and control over state updates. However, it's important to weigh the benefits and complexity of removing a single state can create big state changes
If props are changed multiple times due to some factor, huge costs are expected due to multiple re-renderings. To prevent this, you must design a perfect component structure and, if necessary, perform memoizing. I think the hook you suggested could lead to over-engineering and I don't think it's a good suggestion. Forcing a component to re-render is a method that has been widely used since ancient times, but think about why a hook that performs this task still does not exist. This goes against React's rendering laws, which are designed to perform re-rendering as props change.
Forcing a component to re-render is a method that has been widely used since ancient times, but think about why a hook that performs this task still does not exist.
Not sure where you got that idea? useState is exactly a hook that causes re-renderings...
In addition, this hook does not simply re-render the component (or else I'd just use useState) it causes the component to be remounted, an entirely different thing.
This goes against React's rendering laws, which are designed to perform re-rendering as props change.
Again, this is not about re-rendering. Furthermore, react already re-renders in more cases that just when props change.
This proposal is about providing a component with the ability to remount itself. Remounting is an existing part of the react component lifecycle, so this is not inventing anything new. The only change is that at the moment only the parent component can cause a child to be remounted (eg. by passing a different key value) whereas this new hook allows a component to request itself to be re-mounted.
If props are changed multiple times due to some factor, huge costs are expected due to multiple re-renderings.
... Yes? So don't do that. Bad performance is always possible to achieve.
I don't understand why have to ignore React's comparison method and force a remount. Even though React Fiber performs Dom comparison and is automatically optimized. why do you want to remount the component except for optimization operations? This suggestion degrades the user experience and deviates from correct usage of React. Please share your proposed Hook implementation to see and check urself aren't there any performance issues.
...Yes? So don't do that. Bad performance is always possible to achieve.
I think the hook you suggested is a shortcut to a bad result.
if i was wrong, guide me. thank you.
Even though React Fiber performs Dom comparison and is automatically optimized. why do you want to remount the component except for optimization operations?
Not sure why you think this has anything to do with optimisation. I gave two concrete use-cases in the issue body, both of which are about correctness.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!