Let me start off by saying I understand you should never use refs.
With that said, I think in my scenario it is very valid. Specifically I am using a window.onscroll handler and cannot simply change props in the onscroll handler. Changing state or props in an onscroll handler rerenders child componentsfar too often, which causes way too much lag.
Hence since my scenario only requires setting the style of an element on scroll, (i.e. I make something sticky to the top of page when it is scrolled out of view), I get 0 lag by simply setting this.refs.myelement.style.position = 'fixed'
My problem is that I am currently doing:
this.refs.childelement.refs.childschildelement.refs.childschildschildselement.refs.style.position = 'fixed'
Basically I have my onscroll handler 3 parents abvoe on the actual element I want to style.
My page consists of a List component. This consists of ReadOnlyOrEditable components. Editable components are RichTextEditors which have a Toolbar Component. It is this Toolbar that I need to set the position to fixed when a window scroll event occurs.
Hence on a page I'll have some 10 ReadOnlyOrEditable components, of which maybe 5 are RichTextEditors, each having a Toolbar.
The toolbar has buttons like italics, bold, etc.
When the user scrolls down, we don't want the user to have to scroll back up if the toolbar goes out of view. I.e. so we want to set the position to be fixed if the toolbar goes out of view so that the user can hit italics / bold / etc. immediately.
So how do I make this nicer without having to pass refs from the top down? The way I currently have it is very ugly...
I prefer to keep the onscroll handler close to the location of the list because having window.onscroll handlers in each Toolbar component implies I am setting multiple window.scroll handlers which is unnecesary work on the browser.
Any suggestions is gladly welcome.
Like everything, I believe there are many ways to solve one problem and here's how I would tackle it.
I would abstract the responsibility of registering/unregistering to a node scroll events and firing custom events at predetermined positions to a "service".
The scrollable component or the app container (List component?) would be the glue between the scroll notification service and its children
The scrollable component would pass in a custom registration function to its children via props. This custom registration function would receive the node (Toolbar) to monitor for positions and a callback to set/unset position fixed
The children (RichTextEditor component) would call the function (e.g. onRegisterPinableToolbar 😁) on componentDidMount with the node from ReactDOM.findDOMNode and a callback that the child can use to modify it's node style.
Notes:
Of course you would also need to to cleanup on componentWillUnmount.
Don't know much about your application but I would try to create a HOC for the Editor, e.g. PinableToolbarRichTextEditor
By abstracting the register to scroll events to a service you can optimize using requestAnimationFrame, and provide fallbacks for older browsers.
The function passed to the children would look something like this (node:Node, setPinability:Function) => unregister:Function
I know the naming pinability is not the best 😶
Instead of trying to get a ref to a deeply nested component, pass down the 'fixed' state as a prop, and let the great-great-(great?)-grandchild check the property and set its own style.
Related
I want to create a generic component that should detect required component View outside click (hide the View/dropdown, etc. when pressed outside). Basically it's for the Autocomplete Search Dropdown but I guess such functionality could be used in the future for other things.
The content should be dynamic and could be passed from any level of the app because such functionality is a common UX thing.
It shouldn't be modal that always render content at the screen center because a dropdown should be opened below its trigger, etc. So it should respect the initial component position. (In think to use onLayout/measure API of the initial position when passing the content into the Backdrop)
I researched a lot of resources and found that the one possible way is:
Create a TouchableWithoutFeedback layer (Backdrop) at the top level of the app.
make it fullscreen with/height
When dropdown is shown: render the Backdrop and onPress on it - close the dropdown & backdrop.
The main question is: How to pass a generic content into this Layer and save its relationship with the parent/props, re-render when needed, etc.?
Please, keep in mind, that the component which should pass content for this layer could be deep inside the app.
I guess it could be done via Context API, etc. but I'm not sure if it's the best possible way.
P.S. in the case of web it's a trivial task: create a fullscreen layer -> use a portal -> move content at the top level of the app -> render it at the required X\Y position and listen for the outside press.
I didn't expect that such functionality will be a problem in the case of RN.
Thanks for any help.
I have a large array that I am using in a component (component A) with *ngFor with a nested *ngFor.
Component B initialises a jquery plugin which registers a document mousemove event handler, I am using this.zone.runOutsideAngular to init the plugin and I am calling this.ref.detectChanges() in the callback as I need to update the UI on mousemove inside the component B.
Component A is not a child of component B.
As soon as the component A is rendered change detection becomes very slow. the array does not change and I am using the ChangeDetectionStrategy.OnPush strategy for component A but when I fire ref.detectChanges() inside component B, ngDoCheck gets called on component A and I can see a noticeable jank on mousemove.
Is there a way to tell angular to completely ignore the large array of items in component A and allow me to handle when the UI should be updated? I thought that using ChangeDetectionStrategy.OnPush would give me what I need but I have tried removing all #Input()s from component A and anytime I call this.ref.detectChanges() inside component B it is still firing ngDoCheck and it is obvious that this is very slow.
I can scroll through the list of items no issue, but it is when I am triggering the detectChanges inside the mousemove on component B that is causing the issue. I know I could manually update the DOM but I think this would just be a workaround as it would only address the jank on mousemove and not the issue around the change detection being slow.
I have got to the bottom of this issue.
The problem was that inside component A for the nested *ngFor I was using a child component to render each sub item which meant that although I was using the ChangeDetectionStrategy.OnPush strategy, it still required a ref check for each item.
I have now moved the html from the child component into component A directly and this has had a huge impact on performance.
this.ref.detach() to remove the detector from from the tree completely, that should stop the checking. Then you can still call detectChanges to do it manually, and reattach to bring it back online.
Maybe also debouncing the mousemoves (rxjs debounceTime()) might help, unless you really need to track every mousemove?
One more optimization if you already didn't, add trackBy: yourTrackByFn to the ngFor(s).
Is there a React event that triggers when user is looking at the component?
Like ComponentEnteredShowArea
Examples are like facebook post videos automatically play when I'm looking at it.
It would be nice if it has nothing to do with scroll.
You could set an event listener for window scrolling in componentDidMount then use vanilla javascript or React refs to determine if specific elements have come into view.
https://gomakethings.com/how-to-test-if-an-element-is-in-the-viewport-with-vanilla-javascript/
I've an HTML table generated by React render() which is tied to a websocket for realtime updates.
What I'm looking to do is attach a React event handler "onClick" to the cell which replaces the value in the cell with a custom piece of DOM such as some Bootstraps dropdown HTML so the user can update that cell.
I can use .getDOMNode() in the click event handler and then manipulate the DOM manually and insert the code, however if render() on this component was called due to a new updated state from a websocket event it would be overridden. If this race condition occurs, I need to inform the user instead of the DOM simply being replaced. I've just seen componentWillUpdate() though this still feels like I'm not using React correctly.
Is there a better approach ? It feels dirty to be manipulating the DOM and incorrect to change the state.
Updating internal component state is there for exactly this case. This component should be in charge of determining whether it is active or not. If you update state on the component when the click handler is triggered with something like active: true, then you trigger a re-render. In your render function add the additional DOM elements if active is true. Then you have three different possibilities that should be accounted for:
onBlur
onUpdate which can
be triggered in two ways. either way you'll likely want to set
active: false on state.
websockets - you will need to inform your
user here probably regardless of if the component is active or not
user - which will just update normally
Doing it this way means that React is always in full control of the DOM, which is a really good way to avoid issues with React.
I have a Panel where items are List and two Toolbars are in dockedItems. One Toolbar is hidden by default other is visible. When I click one Toolbar it gets hidden and other visible ( using setVisible function ) The problem is that I can't see the active one until I resize a window. Maybe some one knows how could I repaint it or refresh? ( tried Ext.repaint(), didn't help )
Thanks
Try calling doComponentLayout() and doLayout() on the panel object.
Most likely you will want to use the doLayout() since doComponentLayout() will also try doing all of the child components and in some applications especially where there are a lot of components, weird things would happen with doComponentLayout() i.e. information going missing, bottom toolbars going to the top, extra whitespace around edges. This was experienced in Sencha Touch 1 hence i would definitely use the doLayout() on the component or you can add a listener to the toolbar and when show event is fired you can call doLayout on it and if that does not work call it on the parent of the toolbar and that should work.