-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
useEffect loop crashes new demo BottomTabBar component #956
Comments
Thanks for raising the issue @ehenighan - coincidently I've added a fix a couple days ago to solve that regression, are you running the code with or without my fix? Let me know. As we're still iterating on this ability to set a custom tab bar component, we might need to make a few more adjustments to this approach, so I'll consider your proposed fix soon. On this topic:
This should work if you refer to |
Hi @flochtililoch, Looks like I don't have your fix, which is a surprise because I thought I'd pulled the latest W.r.t the first approach I tried, what I specifically wanted to do actually didn't work for me because of other limitations - but they might be interesting to discuss separately because I think there's a possible simple change to the rules on Thanks for the help and the quick response! Ed |
keep me posted
sure thing, please go ahead! |
|
Okay, the alternative fix didn't make any difference but I've made progress - I am using an SVG gradient background for the menu bar so initially had it in a parent view as a sibling of the select-single:
This appears to break something about the props caching for state preservation.
Now the selection works as expected. HOWEVER There's now a flicker of a second nav bar during the first screen transition, and after that it appears permanently on the next screen you swap to but with the previous state selected - so the one at the bottom maintains the current selection but the copy above has the other tab. I'll keep digging. |
N.B I think it's a rendering issue on the first page - the one you're leaving. Sequence of events: |
Okay, making some progress now - a lot of the complexity and need for more effects is driven by the context provider - it causes renders all the time because the function it returns is always a new object. Changing that by (I admit a bit dirtily) using Annoyingly I've now broken it again in mysterious ways after getting it working reliably so I can't raise a PR yet |
Thanks for your investigation, I'm facing the same issue as you described here:
Spent a decent amount of time on this but haven't quite figured what the issue is. I was a bit baffled at first by how this would work fine on the demo app, and I just realize it doesn't, just the styles we apply there obfuscate the issue: the tab bar is absolute-positioned at the bottom of the screen, so that second unexpected instance always shows under the main, navigator driven tab bar. I've tried a few variations on the way to share the element state between the screen and the navigator (caching the state setter with |
So far I've resolved those early issues and been able to simplify the overall rendering as a result, but now I'm stuck on a competing rendering loop when you switch back between tabs that are already loaded. No problem when the tab is loaded for the first time, but when it's already cached I'm getting a race condition between screens. I have a sneaking suspicion it's because with the Code changes so far:
Annotated logs from working the above example from one tab to another (good) and back (bad):
|
The last file changes work because with better behaviour from the context provider the component re-renders as needed based on the context updates anyway, so there's less of a need for explicit local state management - I actually think it's worth experimenting with removing its internal useEffect/useState entirely and clean up the rendering loop by just getting the element directly from the context object every time |
Still not sure if genuinely relevant but here's (possibly) the code snippet I predicted would exist to explain the behaviour differences: hyperview/src/core/components/hv-screen/index.js Lines 197 to 208 in b5a6ada
|
My explorations today, with help from @hgray-instawork, surfaced that this line might be the culprit in the infinite loop we're seeing when navigation is performed. @hgray-instawork we should discuss further on how to accomplish differently what this code currently do. |
Could it just be that Separately, I think the loop I'm seeing in my patched version might be resolvable in a different way - the no-render component registers itself with the context on render but it's only there to make sure we show the UI components currently in the DOM, so it only needs to happen when the screen is focused. I'm going to try replacing the |
Okay, still got a loop but it's now much better defined so I'm hoping I can debug it fairly easily from here.
Code now looks like this:
Now the tab bar display is totally stable and updates like you'd expect, with no unnecessary re-renders. The remaining loop is again when you navigate back to a previously-loaded tab, but the source is clearer now - the However, the loop behaviour is 'improved', if you like - it completes a real navigation between tabs gracefully with no unnecessary re-renders, and it's just flicking properly between them constantly. If you put a I think this is going to be something to do with the cached props from the context in some way - if the 'prevProps' are from the page you just left, they will have tab 1 selected now instead of tab 2, but the current component previously had tab 2 selected. Need to chase the loop around a bit and see where it's coming from. New logs:
|
Yep, confirmed with some additional logging - the I'll have a think about how we could avoid this - it's definitely due to the way we're caching the props and overwriting them, but I'm not quite clear on exactly why this means the new screen components are actually receiving the old props from the previous screen in this case when they aren't when we have to await a server hit.
|
So far I have a solution of sorts but it is absolutely disgusting and there's no way it's tolerable. The problem as I understand it:
I have currently some really hacky code in place which abuses I think my slightly better but still not very nice solution will be something like:
It would be a lot nicer to implement this if instead there was a nice on-completion callback I could hook into for the navigation action, but I can't see one - is there something I'm missing? |
Slightly better solution seems to work stably but is a bit sluggish due to tonnes of logging and some excessive re-renders - I don't think I've got the screen-key setting right but I think I know what I need to change. Hoping to have a working PR tomorrow. Will require people defining a bottom nav bar component to make sure they have a reset-selection event handling behaviour defined but the alternative was assuming that the display component was always going to be a Let me know if there actually is a nice post-navigation callback hook I can use that I've missed, because it'll clean things up even further! Hopefully what I've got working isn't too mad now. Thanks, Ed |
Good news - managed to find a solution that let me ditch most of the evil stuff I was doing! @flochtililoch this branch has the relevant stuff in it: Under normal circumstances this works fine now, without any extra event firing needed etc., and without too many unnecessary renders. It's still definitely possible to get it stuck in an infinite loop of navigation if you click around fast enough for long enough, but that was still the case even with my nastiest but most belt & braces attempts, so I'm going to pause it here if you don't mind and let someone with better React Native skills than me try to solve the remaining issue with clicking around too fast. Hope this is useful to you, and apologies if my branch isn't usable directly - got fed up with the linter shouting at me so there may be bits that aren't quite right. EDIT: Disregard that, think I've sorted the last remaining problem and now I can't make it infinite loop any more. All yours :) |
Thanks Ed, sorry for not getting back to you earlier, I appreciate your effort in helping solve this issue! |
No problem, and thanks for taking an interest! FWIW I suspect the best thing for the long run will be to remove this tension, around the fact that the components are rendered as part of a |
Fix double rendering of the custom bottom bar component by refactoring the following pieces: - context: switch to using the reducer pattern, and expose 3 methods: `getElementProps`, `setElementProps`, `setElement`. - core component: render any child element of the custom "non-render" element, not just the assumed single-child - custom element: set the `registered` attribute on the element upon setting the props in the context, to avoid re-render loops when the screen holding the custom element re-renders. This does not prevent server side updates of the tab bar. - update the demo app to factorize the markup for the tab bar as well as to show case the server updates of the tab bar. Special thanks to @ehenighan for the thorough investigation and help solving #956. https://github.com/user-attachments/assets/756d8585-d563-4a23-b845-3b42a843c4e7 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a notification badge design for the bottom tab bar. - Added a new tab bar component to manage notifications within the second tab. - **Enhancements** - Improved the BottomTabBar's context management with a reducer pattern for better state handling. - Streamlined the BottomTabBar component's logic and rendering process. - Enhanced tab navigation with a modular approach for dynamic rendering. - **Bug Fixes** - Resolved rendering loops in the BottomTabBar component. - **Documentation** - Updated context exports to prioritize the new custom hook for easier access. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: flochtililoch <[email protected]>
@ehenighan Thanks again for your investigation work, it definitely helped crafting a fix for this issue! Closing as resolved for now. |
Hi,
By pure coincidence I was having similar problems relating to the lack of a sticky nav bar at the bottom and tried this recent new component out when my other idea didn't work (show-during-load = existing screen, hide-during-load = existing screen content box -> fails because the hide-during-load isn't applied during navigations, booo)
Immediately ran into a problem where the useEffect in the component gets into a loop, and I think it's because of the context provider:
hyperview/demo/src/Contexts/BottomTabBar.tsx
Line 27 in b5a6ada
'Fixed' it with the following refactoring:
I think the useCallback is probably overkill and it's the early-return condition in the function that's making the difference. Initially I thought this might be a niche case because I was using URL-loaded SVG icons and I considered if they might be causing lots of re-renders when the loading completed, but I took them out and the problem persisted.
I've not raised a PR for this directly because after my fix I'm also getting some other weird behaviour where after a successful initial load, the rest of the screen content just doesn't render when you select an item, leaving the nav bar floating in the centre of the page half-styled. The network request goes off in the background and the full screen content is returned to the app, but it doesn't load properly. If you click back and forth between tabs a bit, sometimes the destination page eventually loads once, but not again after that.
I've either got no rendering because of the useEffect crash, or weird UI behaviour with my fix which I've not yet been able to debug, so I figured I'd raise an issue with what I've found so far and see if anyone has advice before I go further with it.
The text was updated successfully, but these errors were encountered: