I’m trying to fire a callback passed down from a parent component. Our old pattern for handling this was to call the function in didInsertElement. In Octane, I see that we can use the did-insert modifier but that seems weird for this use case since we’re not updating the DOM element that we’d use to call did-insert. I've also seen onRender being used in a few cases but I don’t see documentation on that and it’s not firing for me. Any suggestions?
For this specific use case, we have a parent component that can have one of many child components. And for each child component we have specific text that gets displayed in the parent component and we want the child component to be the owner of that text.
Your instinct that did-insert isn't the right solution here is, I think, correct. In general, modifiers should only be used when the element they're going to be attached to is used in some way—that is, for managing interactions with the DOM. In general, we prefer one-way data flow otherwise. However, the scenario you've outlined looks similar to a "registration" pattern, where when a child is instantiated, it tells its parent "Hey, I'm here, here's the info you need about me."
There are a couple reasonable options in this case:
Rethink whether the child component should in fact own that data. I don't doubt you have a good reason for the child component owning the data, but the fact that the parent is already responsible for deciding which child to render may suggest that a mapping which includes both the component to render and the associated text could be a good solution in this space. That would cleanly solve this issue.
Given that per your description you currently do want to avoid having the parent own that data, you could also consider having the child yield the data. However, this usually only works if the DOM relationship for it makes sense. If it does, you could choose to do something like this:
{{yield (hash block='data' text=this.theText)}}
<div class='the-component-body'>
{{yield}}
</div>
<ChildComponent as |child|>
{{#if (eq child.block 'data'}}
<h2>{{child.text}}</h2>
{{/if}}
{{child}}
</Child>
(You can see this strategy working here—in particular, see the resulting DOM!)
While that's very powerful, again, it only works if your DOM layout supports it.
Finally, and perhaps most simply for the use case you have, you can (though not necessarily best, as I think the other options above are usually better when available), you can 'register' the value for the component by calling an action passed into your component during the constructor. Now, using a component's constructor this way for component behavior can be a problem in that it only runs once, when the component is first instantiated, and Glimmer and Ember keep the component instance stable and just change the values passed to them over time as much as possible, so if the value you're passing back up depends on the argument you pass to it, the constructor won't work. If it's always stable and does not depend on the arguments to a component, this does work, and it's often appropriate for a 'registration' pattern, where the child component simply needs to pass a single piece of data to the parent when instantiated.
Related
I'm using ember.js(version 3.7) and I bite confused now. Now, I'm working on optimizing the code part for our app. First I'll try to find What are the things are re-render in components? After getting into this topic I bite confused with it.
In ember they used didRender() method to trigger re-rendering things (jQuery DOM Manipulation, Asnyc function loading,etc...). Right now I don't have much idea about re-render in ember. Can someone explain to me re-render in detail? And, please share if you have any resource about re-render in ember.
Thanks in advance.
didRender hook doesn't about triggering a re-render. It is described as the Guide that you shared the link of:
You can leverage this hook to perform post-processing on the DOM of a component after it's been updated.
You might want to do something about sizes or focuses or scrolls. To achieve that you need to wait till your rendering finishes. Because otherwise you cannot get the exact values and positions of the components. For those cases you can use didRender hook.
For example:
- if you want to focus some parts of the view
- if you want to scroll some parts of the view
- if you want to resize some components
- if you want to call a third-party libraries which tries to access DOM element.
etc. You can use this hook.
For sure, if you do something that affects to component's values, it can trigger a re-render. But this is something that you normally shouldn't do.
Let's have one more clarification of re-render:
As components are rendered, re-rendered and finally removed, Ember provides lifecycle hooks that allow you to run code at specific times in a component's life.
(Ref)
Guide says about 3 main phases (Ref):
Initial Render
Re-render
Component Destroy
In here Re-render means, if an argument or a property of a component changes, it starts to re-render itself. For example, think of a person-card component which displays the properties of a person. Such as {{person-card person=model.person}}. Whenever the person parameter changes, the component will re-render.
This question has two parts:
Why do prop types check fail in my react-only scenario?
Why does a material-ui HoC interfere with the type checking?
When making UI components, I make the children unaware of each other, by passing props through React.cloneElement in a unidirectional flow. In my approach, the root component updates its screen size state, and it's children must accept and pass it on to the next child, and they can adjust the values according to content area dimension left for it. The idea is that the the leaf child itself can decide how to render depending on the space left.
In my simplified code example, the WithSize-enhancer informs the root component the full screen size, while the BridgedContent-enhancer informs the leaf component how/if it should render:
https://codesandbox.io/s/92vop4oyr4
It turns out that the root component (EnhancedPrimaryUI) gets its necessary props, passed from either parent or enhancer. It's child's prop type, on the other hand, will fail on page load. Running devtools only reveals what's going on runtime, and looks totally OK:
I really have no idea why it has to be like that! To me it just appears to be React inner workings. My tentative workaround is to add defaultProps, either in every child, or in App.js see second example.
I know about alternative workarounds like passing context or connecting child components to redux, but don't see how such could be motivated in this case.
I get even more confused because I implemented Material-UI, and found out that every child component that is styled with the WithStyles-enhancer magically causes no failed prop types! see third example
I know material-ui uses context to pass only theme/classes into withStyles.js, and claims to not modify the component passed to it.
So what is happening here? Does it effect it indirectly by the order React do things? Is it a feature or is it a bug?
While I still haven't found an explanation to question 1 (why the prop requirement is not fulfilled in spite of having props to seemingly flow nicely), I found there are several ways to ensure the props get there safely:
Add initial JSX props in App.js: <SecondaryUI height={0} width={0} isMobile={false}> BridgedContent height={0} width={0} isMobile={false}/></...
Use initial state from wrapper component (like in PrimaryUI), where the wrapper can be a context-provider. (This could be a clue to question 2)
Use default props
On a sidenote, the intended mechanism can be accomplished much cleaner now using React-hooks. See example: https://codesandbox.io/s/71r7l9ppvj
Say I have a child component. Let's say I want to change its height based on information that the parent has.
If I get a reference to it. I can change it with
myChild.changeHeight(newHeight);
(see React.js - access to component methods)
or I could change it with
<Child height={newHeight}/>
Both could be changed in the render() method. But which one should I use?
React team gave few scenarios where we should be using refs.
There are a few good use cases for refs:
Managing focus, text selection, or media playback.
Triggering imperative animations.
Integrating with third-party DOM libraries.
Avoid using refs for anything that can be done declaratively.
If there is a possibility to do some functionality even without using refs, then go ahead and do it. In your case, as you said, you have a way to do it by passing a prop. It would be better if we use props itself rather than ref.
I'm writing a recursive tree renderer and suffering this "Avoid mutating props directly" warning.
As I understand it, "props down, events up" means a child can't/shouldn't directly change parent data. I can avoid this error by writing lots of code where one component $emits a change, its parent catches it and re-emits it, and on and on, up to the top level component which finally can change the data, which then renders down through the tree. While I get the use case where a generic component needs to be agnostic of its parent, for a recursive component it seems terribly inefficient.
I noticed an official VueJs 2 tree view example is provided. But looking at that code shows that the child components are passed an object belonging to the parent, yet the child component is allowed to update it directly (e.g. in the addChild or changeType methods).
I made a variation of that jsfiddle as proof. All my variation does is outputs the parent's data as json so you can see it is changed. At the start it looks like this:
{"name":"My Tree","children":[{"name":"hello"},{"name":"wat"}
Then if you click into the first parent, then double click 1. hello to trigger changeType you'll notice the parent's data is now changed:
{"name":"My Tree","children":[{"name":"hello","children":[{"name":"new stuff"}]},{"name":"wat"}...
...yet the dreaded "Avoid mutating a prop directly" warning was not fired.
So I'm obviously missing some nuance. It's frustrating because there's so many questions about this and I'm afraid of adding a duplicate. But I feel this is different because I'm asking "why is the example given not mutating a prop directly"?!
Objects and arrays are passed by reference it's perfectly acceptable to mutate the properties of objects or the contents of arrays that are pased as props because that is not changing the reference. Changes to an object's properties will be reflected everywhere that specific reference is used. This is addressed in the official documentation.
Note that objects and arrays in JavaScript are passed by reference, so
if the prop is an array or object, mutating the object or array itself
inside the child will affect parent state.
Were you to try to set the object passed in as a property to an entirely different object, the warning would be displayed in the console.
Whether or not you choose take advantage of this is a design consideration. In the Vue tree example, the code mutates the object properties and the changes are reflected everywhere. A counter example might be editing a user. If you had a User component and wanted the changes to that object to only be reflected outside the component once the changes were considered valid (to allow canceling the changes), you would make an internal copy of the object, edit the copy, and emit the changes when they were considered acceptable.
I'm new to Ember and have a leaking state problem. I have a carousel widget that displays one item at a time and allows the user to click previous/next to see each item.
Here's the simplified carousel's component:
<button {{action "nextItem"}}>Next</button>
{{carousel-item item=selectedItem}}
Clicking next changes the selectedItem property so the next item is shown.
What I've realized is that the carousel-item component isn't re-initialized every time I move to a previous/next item. The DOM is reused each time, and the component's properties are shared since it's all one instance, which means I can have leaking state.
The alternative I see is to render all the items initially, so each has its own instance:
{{#each items as |item|}}
{{carousel-item item=item}}
{{/each}}
and to hide all but the selected item using CSS. However, this option kind of feels like a jQuery hack -- seems like Ember would have a better way. And I'm only ever showing one item at a time, so I hate to have so many extra DOM nodes when I don't need them.
What is the recommended way to handle this kind of a UI, where you only need one item shown at a time but don't want to share state between items? I'd imagine I should have one instance of the carousel-item component per item, instead of sharing an instance across all of them. But it doesn't feel right to instantiate every single carousel-item at first either. And I can't imagine the Ember way is to worry too much about the DOM details myself (determining which one is shown/hidden based on a class and some CSS).
Firstly, whatever framework or library you are using, jQuery, ember, angular, react, they are just a pack of JS/HTML/CSS right? So you should think in it's way, there is no magic!
So of course 1 component will only create 1 instance. If you just changed it's property(item in your demo), it just changed the property of an instance, other properties of it will remain as it is and triggered re-render. You cannot expect more. You have to manually reset other properties.
And yes, rendering everything by {{each}} looks stupid, but think about it, how could you create a smooth carousel animation by render only one DOM? At least you need to render 3 (current, previous and next) right?
Since carousel is a common UI, I recommend you to check existing ember addons fist before you write by yourself: https://emberobserver.com/?query=carousel
If I understood your problem correctly, the willUpdate hook in Ember.Component class should help you out. I this hook you can clear up the attributes, remove DOM objects, or anything at all. This will be called each time the component is about to re-render itself.
A simple example is of form:
willUpdate() {
Ember.$(this.get('element')).empty();
},
This will clear the DOM on each re-render forcing it to redraw elements.
You can try out other hooks too and see which event will serve your need. All of them are very helpful and serve different purpose.