ReactJS + Tracker: Update parent state - meteor

I'm having an issue with autorun and setting the state of a parent. For example, I'm having cateogry items which also have subcat items:
Category A
Sub 1
Sub 2
Category B
Sub 3
Sub 4
Now, if I visit Sub 4, the state of Category B and Sub 4 should be go to active.
In my sub component, my code looks like this:
export class CategorySubItem extends Tracker.Component {
constructor(props) {
super(props);
this.state = {
active: false
};
this.autorun(() => {
if(LayoutGlobals.get().categoryId == this.props.doc.id) {
this.setState({active:true});
this.props.parent.setState({active:true});
} else {
this.setState({active:false});
this.props.parent.setState({active:false});
}
});
}
Calling an sub category item:
<CategorySubItem doc={cat} key={cat.id} parent={this}/>
LayoutGlobals is a reactive variable. When I set the state of my parent, I'm ending up in an infinite loops of rerenders. What would be the correct way to handle this?

Never set the state of a parent component. All components should only set their own state instead. Write a function for your Category component do decide whether it's active or not.
I'd try to avoid autorun though. Just pass LayoutGlobals.get().categoryId as a prop down the component tree, and implement your own componentWillReceiveProps function. That way you allow React to minimize re-evaluation.

Related

Use Binded data on the logic part on LIT

I have been looking for information about variable binning. The problem is that I don't want to use those variables inside my HTML render, but in the component logic (i want to fetch some data). How do I declare it?
constructor(){
super();
this.url = "https://....";
}
render() {
return html`
<parent-component .url ="${this.url}"></parent-component>
`;
child component:
constructor() {
super();
fetch(this.url)
.then(response => response.json())
.then(data =>{this.ArrayData = data.results
this.requestUpdate()
})
}
Thank you very much
To bind data, declare a reactive property for properties that can trigger the reactive update cycle when changed, re-rendering the component.
From your example I'm not exactly sure when you want to trigger the fetch in the child component. I'll assume you want to fetch the data once with this.url passed from the parent as a property binding.
The issue I see in the code sample is that this.url will be undefined in the constructor of the child component, as the outer element needs to render and set the property .url on the child. Moving the logic in the constructor to the firstUpdated lifecycle callback should fix the issue, as this.url will now be defined.
An additional change I included in the code sample, is making this.ArrayData a reactive property, allowing the removal of this.requestUpdate.
Minimal example of fix in the Lit Playground.

why getting the focus trigs databinding in angular 5?

Developing a multiculture Angular5 app I've structured a class which contains a dictionary (custom ts class) to hold the translations. When the user changes the culture, anything related to it must change. It works but.... too much.
Since I use to console.log everytime the method which gets the right sentence according to the selected culture is called, I noticied that if I just click on a textbox, Angular updates everything and, incomprehensibly, if I click on nothing soon after just to leave the focus, again angular updates everything! I know there must be something related with ChangeDetectionStrategy but I tried to solve without any success.
When the app grows up, if the browser has to reload everything everytime... what a problem! The app looks like this:
a MainController.ts holds anything can be useful to the components so it is passed in every component constructor.
MainController looks like this:
#Injectable()
export class MainController extends BaseClass {
private currentCulture: string;
private platformLocalizedSentencies: KeyedCollection<LocalizedString>;
the class LocalizedString looks like this:
#Injectable()
export class LocalizedString extends BaseClass {
public DefaultText: string;
public IdTranslationIndex: number;
public Translations: KeyedCollection<string>;
constructor() {
super();
this.Translations = new KeyedCollection<string>();
}
public GetTranslation(culture: string) {
console.log('getting translation for ' + this.DefaultText + ' in culture ' + culture);
if (!this.Translations.ContainsKey(culture)) {
return '*' + this.DefaultText;
} else {
return this.Translations.Item(culture);
}
}
}
Now, there's a component (culture-selector.component.ts) which shows flags (changeDetection: ChangeDetectionStrategy.OnPush) and when the user select a flag this happens:
onCultureClick(menuItem: string) {
console.log(menuItem + ' clicked.');
this.mainController.CurrentCulture = menuItem; // this must be the thing which unleash the databinding on the other components I think and I hope
this.updateSelectedCultureUI(); // doesn't do anything special, just sets the right flag and culture name on the top of the control
this.mainController.trace(TraceType.Info, 'Culture ' + this.cultureName + ' clicked');
}
and the component where I noticied the hell wild by the databinding, the login component:
#Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
})
export class LoginComponent extends BaseClass implements OnInit, AfterViewInit {
constructor(public mainController: MainController) {
super();
}
ngOnInit() {
this.usernamePlaceholder = this.mainController.PlatformLocalizedSentencies.Item('Username');
Now, "usernamePlaceholder" is used into the html like this:
<mat-form-field style="width: 100%;">
<input matInput [placeholder]="usernamePlaceholder.GetTranslation(mainController.CurrentCulture)" [(ngModel)]="loginInput.username" (keypress)="eventHandler($event.keyCode, 'txtUsername')" #txtUsername>
</mat-form-field>
in the following picture you can see into the console-window what happens if I just click into the username textbox.... I cleared the console before clicking. What is going on? I spent two days trying to undestand... sorry it's my first angular App.. I should have started with something easier :)
You can see username translation is get by ClassLibray.ts while the other sentencies by mainController.ts... this is because I tried different ways but same result... in mainController the code is almost the same:
.... and the method to retrieve the correct string
public GetPlatformSentence(key: string) {
console.log('getting translation for ' + key + ' in culture ' + this.CurrentCulture);
if (!this.PlatformLocalizedSentencies.ContainsKey(key)) {
return '[NOTRANSLATION]';
}
if (!this.PlatformLocalizedSentencies.Item(key).Translations.ContainsKey(this.CurrentCulture)) {
return '*' + this.PlatformLocalizedSentencies.Item(key).DefaultText;
} else {
return this.PlatformLocalizedSentencies.Item(key).Translations.Item(this.CurrentCulture);
}
}
Thank you anyway
here is what happens
Ok guys, I've tried anything without any success. Angular binding is... too pedantic :-)
The best result I was able to achieve was the refresh at first user-interaction. So I had to choose between refreshing everything everytime (which was too much) or refreshing as soon as the user clicked somewhere forcing the binding refresh (that was what I've been able to get playing with ChangeDetectionStrategy settings and some additional code).
So I solved (just for multicuture immediate binding) using a messageService. Every component subscribes and asks to a common maicontroller to update anything related on culture when it changes. Of course I needed to write e little bit more code but the result in terms of user experience, responsiveness and CPU load is very very nice. Also the executed code is the same for every component so it is not so bad.
Of course it isn't what I expected but, it works better for me.

dispatch custom event for component doesnt have parent

i have a Group g1 that addElement MyComponent comp1 and a UIComponent c1 add child g1
and a custom Event customEvent1 means:
c1.addChild(g1.addElement(comp1))
so comp1.parent is null
this is some code
MyComponent:
private function mouseUpFunction(e:MouseEvent):void {
//e.stopPropagation();
var event:MouseChangeEvent = new MouseChangeEvent(MouseChangeEvent.Mouse_Up_Objective);
dispatchEvent(event);
}
private function mouseDownFunction(e:MouseEvent):void{
//e.stopPropagation();
var event:MouseChangeEvent = new MouseChangeEvent(MouseChangeEvent.Mouse_Down_Objective);
dispatchEvent(event);
}
Main App:
stage.addEventListener(MouseChangeEvent.Mouse_Down_Objective, mouseDownHandler);
stage.addEventListener(MouseChangeEvent.Mouse_Up_Objective, mouseUpHandler);
the problem is comp1 dispatch the event but it never catch! :(
The "bubbles"-parameter needs to be set to true in you custom event class.
with my tests its not possible to bubble custom event in the component without parent
so you should set parent anyway

Rewiring actions of parent to a child viewmodel

So here's my screnario. I have a toolbar at the top (office style), with buttons. This is hosted in a shell. Some of those buttons are applicable only to certain child view models as they get loaded. Ideally what I would like to happen is have the buttons action.target repositioned to child view model as it gets created (I kind of got this working by settings Action.Target="ActiveItem" on them. This doesn't solve the problem fully though:
a) When the child viewmodel is closed and there is no active item, I want them to reposition to Shell as the target so they can be set to "default" state.
b) I noticed that when child viewmodel is closed and the shell being the conductor has it ActiveItem=null, the hooks from the action are still bound to the living instance of the last viewmodel, so doesn't looks like it got disposed of. Memory leak?
Any suggestions how to implement this scenario?
What about adding a property to your ShellViewModel which points to the action target and updating it when stuff gets activated/deactivated:
e.g.
public class ShellViewModel
{
public object ActionTarget
{
get { return _actionTarget; }
set
{
_actionTarget = value;
NotifyOfPropertyChange(() => ActionTarget);
}
}
// Then when the active item changes just update the target:
public override NotifyOfPropertyChange(string propertyName)
{
if(propertyName == "ActiveItem")
{
if(ActiveItem == null) ActionTarget = this;
else ActionTarget = ActiveItem;
}
}
}
Now bind to that:
<SomeMenu cal:Action.Target="{Binding ActionTarget}" />
Not sure if that will work or not but I'm sure I've done something similar in the past. (You may also have to explicitly call NPC on your actions before they will update after you have changed ActiveItem)

how to stop getting mouse click events in flex

I have made a hierarchy in which there is a main page, using add element i have attached a component mxml of type group. There is a single button on main page when clicked it should add children of type group in that group type mxml component along with two buttons. Now using one of buttons i am attaching another component mxml type group. the problem is even they overlap i can still excess the children groups of first group component mxml. how can i stop this mouse events to happen.
I think those kind of events usually bubble up to parent components.
You can try using the following code in your mouse click event listener to stop further propagation:
private function onMouseClicked(event: MouseEvent): void {
event.stopPropagation();
... do whatever you wanted when smth was clicked ...
}
By setting enabled, mouseChildren, mouseEnabled to false, you will disable the entire component and it's children. example below
private var myPreviousGroupComponent:Group = null;
function addNewGroup():void
{
if(myPreviousGroupComponent != null)
{
myPreviousGroupComponent.enabled = false;
myPreviousGroupComponent.mouseChildren = false;
myPreviousGroupComponent.mouseEnabled = false;
}
var newGroup:Group = new Group();
addElement(newGroup);
myPreviousGroupComponent = newGroup;
}

Resources