Closed janczizikow closed 2 years ago
Hey @janczizikow , thanks for the deep dive into the code! It's a valid problem for reducing unnecessary renderings.
Option 1 looks good to me. It provides opportunity for a developer to optimize the rendering. From immutability perspective a modal handler depends on some state(args) it uses seems reasonable. So we can just optimize the methods on the handler.
Option 2 will break the functionality because the args
from modal handler is used in the NiceModal.create
method in HOC. So we can't remove the args
property from the return value of useModal
.
However, this problem is similar with which is described at: https://stackoverflow.com/questions/55724642/react-useeffect-hook-when-only-one-of-the-effects-deps-changes-but-not-the-oth . Actually we only want to do something when part of dependences changed. So manual control is a suggested approach (check if counter
is actually changed).
Hey @supnate thanks for your answer! I didn't realize args
are used in NiceModal.create
! Totally makes sense to have it as a return value in the hook. Now I get it π
The problem with counter is slightly made up. Perhaps a better example (more real) would be to show a modal depending on URL query. Often it's also the case that we re-use existing callback and use it in multiple places (onClick + useEffect):
const { show } = useModal(UserInfoModal);
const [users, setUsers] = useState<User[]>(mockData);
const handleNewUser = useCallback(() => {
show({ title: 'New User' }).then((newUser) => {
setUsers([newUser, ...users]);
// navigate to a new page without ?action=show in the URL
});
}, [show, users]);
useEffect(() => {
if (window.location.search.includes("action=show")) {
handleNewUser();
}
}, [handleNewUser]);
return (
// ...
<Button type="primary" onClick={handleNewUser}>
+ New User
</Button>
// ...
)
Ofc we could skip passing [handleNewUser]
in useEffect, but it will trigger a warning from eslint if using react-hooks and it's generally not recommended in react docs. Somehow I think it feels more natural to pass the dependency.
It could still be a nice improvement to memoize just the callbacks as described in option 1. I'll open a PR with the changes for your consideration π
EDIT: another "fix" to prevent constant re-rendering could be to also to check if the modal is not open. Which should also work with the current useMemo
useEffect(() => {
if (!modal.visible && window.location.search.includes("action=show")) {
handleNewUser();
}
}, [modal.visible, handleNewUser]);
Hey I realized that issue described in #53 is still present when calling the
show
handler with arguments πThe following works as desired after the fix:
The effect doesn't cause re-rendering loop as it was before. But if we call show with a non-primitive type it would again cause an infinite re-rendering loop π’
It's the same reason for it as described in #54. This time though the problem is
modalInfo.args
- it's an object and it will change on every render. I think there are 2 possible ways of solving this:Option 1: Don't memoize
args
returned fromuseModal
instead memoize just callbacks:Now we can rely on
show
, because they will only change if id of modal changes, which I don't think it's a common case. All is well and if we desctructure show from the return value it works in our example:But if someone passes the whole object returned returned from the hook it will again trigger an re-rendering loop
Option 2: Don't return
args
inuseModal
This eliminates the problem of comparing the modalInfo.args with === and we can pass return values from
useModal
hook however we like in useEffect or other hooks. I think it's probably a better approach in my opinion, but it also means a change in the API.I would be curious to hear your opinion and discuss whether removing
args
from the return value would be something you would consider? Would be happy to open PR for it as well if we decide which approach we would like to take π