React JS: data flow between grandparents/grandchildren - javascript

I intend to set up the following React Component
<tab-box>
<title>Example</title>
<butons>
<button id="actionID1"></button>
<button id="actionID2"></button>
<button id="actionID3"></button>
</butons>
<content>
<tabList>
<tab id="tab1" label="label1"></tab>
<tab id="tab2" label="label2"></tab>
</tabList>
</content>
</tab-box>
Where I would like to pull out each label attribute for tab to set up a nav bar on top of the actual content.
The question is, how do I pull out the attributes from nested children? Or how can I restructure the component so that I don't have this issue?
Thought1: use a global Store service, children like tab populate the Store and parents may retrieve them when being mounted
UPDATE, Thought2: make labels a prop on tab-box, but I still don't feel quite right..

There are ways to do it, but i doubt it is what you ultimately want. You want the parent to obtain information about the child? This doesn't make sense as you are trying to create "components" and not some code specific to one task.
Think of it this way. The tabs should be using tab component to display its own information. Under what circumstance would the tab know more than its parent? If so then is it even a component? Who is using who?
React is suited for one way data flows from parent to child, so it makes more sense to hold information in tabs component, and pass pieces of info to child tabs.
<tabs>
<button text={tab[0].text}/>
<tab>
write transcluded children here
</tab>
</tabs>
Even this begs the question - why are you using so many components to begin with? Why not use css classes to represent tabs container and individual tabs like a normal person?
If you insist on making a tabs component, at least keep it all in one component, something like
<tabs data={[{title:"tab-title", content: (<p>stuff</p>)}]} />
Also I absolutely do not agree with defining tabs in a model/store. common sense is UI and data should be separate.
As someone else mentioned you can use ref, but i'd reserve that for very limited purposes such as getting a field from a form.

Related

How to force tabs to mount without ever clicking on the tab?

Using React & material-ui, I have a pretty big tab container and want to keep data fetches local to each Tab component. I want to be able to essentially greedy load some of the Tab components so as soon as the Tab container is mounted, the Tabs with a greedyLoad prop passed to them are mounted (although not the active tab/visible) and make the fetch for the data they need.
The reason is some of the tabs need a count from the data I fetch in the tab label.
I understand I can fetch the data from the parent component and pass the data as a prop downwards, but I really would like to keep the fetch’s local to each tab component. I’ve seen it done at a previous company I worked at and totally forgot how it worked. Something with CSS I think. Thanks in advance
If you hide the component with CSS, your component will mount on the DOM, but it will be invisible to the user. We just need to add some inline css and make use of the display: none property
function myComponent(show) {
// TODO: fetch the data
return (
<div style={{display: show ? "block" : "none"}}>
<h1 >This component may be invisible!</h1>
<p>{data}</p>
</div>
);
}

Angular component creation best practices

I'm often in doubt when I want a new behaviour of a component.
Let's make a simple example, I have <app-title> component:
<div>
<h1>{{title}}</h1>
</div>
Some time after, inside another page I need to put a button alongside the title. The problem is, should I create a new title component or should I parametrize the existing one?
I can edit <app-title> to look like this:
export class AppTitleComponent implements OnInit {
#Input() showButton: boolean;
title = 'App title';
constructor() {}
ngOnInit() {}
}
<div>
<h1>{{title}}</h1>
<button *ngIf="showButton">{{buttonTitle}}</button>
</div>
This is a simple example and may be obvious but using Angular I always have this problem, think about complex components: the #Input() would become many using this method, but creating a new component would increase files and complexity.
From this example you could say to create two components, one for title and another for button but that's only because this is a very simple case. Think about changing a component from "compact" mode to "expanded" and viceversa. On the one hand you may need to have the large component and on the other hand have it smaller in size and showing less information
Is there some guideline about this?
Thanks
I think it's important thinking about behavior within the context of your component. Is the button core to behavior of the title component? Does it make sense to not only display the button, but also handle its events within the context of the title component? If the answer is no, then at some granular level I'd split the components.
Here are some other things you can consider:
Anticipating that your title component may need some content wrapped with the title, you can use transclusion:
<div>
<h1>{{title}}</h1>
<ng-content></ng-content>
</div>
Then, in the parent, you'd do something like this:
<div>
<app-title-component title='title'>
<button>Some Button Text</button>
</app-title-component>
</div>
You could write a wrapper component that packages the title and the button together... ie:
<div>
<app-title-component></app-title-component>
<button>Some Button Text</button>
</div>
You could, as suggested, parameterize the configuration. I recommend thinking about if the behavior that you are parameterizing is part of the core behavior of the component. For example, if you want to paramaterize whether or not a legend shows on a chart, that makes sense because the legend is a core feature of the chart. But I probably wouldn't parameterize whether or not the chart should be followed by a raw data sheet. Instead I would create a new component for that and render them in sequence, because the data sheet is not part of the core behavior of the chart, even though sometimes I'd want to put them next to each other.
At the end of the day, you have to think about the decision in terms of your app, your app's future usability, and developer ease (e.g.- will it make sense to a future developer that this button is packaged with title).
I hope you find this helpful.

How to get an value from Child component to Parent on nuxt.js?

I am stuck trying pass data from Child A ($emit) component to Parent and from Parent to Child B (props).
Using nuxt.js I have:
layouts/default.vue
This default template will load a lot of components.
Those components will be used or not based on variable from child, the variable will set the v-if directive.
The children are the pages like:
pages/blog/index.vue
pages/about/index.vue
...
The goal is the Child set on Parent what components would be used, the flag can change anytime, the user can choose what will be rendered on admin area.
I have tried use local computed methods on child component, and vuex, no luck with both.
The idea on layouts/default.vue.
<template>
<div>
<TopBar v-if=showTopBar></TopBar>
<Nav v-if=showNav></Nav>
etc...
<nuxt />
</div>
</template>
<script>
import TopBar from "../components/TopBar";
import Nav from "../components/Nav";
etc...
export default {
data() {
return {
showTopBar: false,
showNav: false
etc...
};
},
}
</script>
On child already have use the $emit but no luck.
Child on this situation are pages, and the layout of those pages will be defined by variable from a fetch on the API, user can change the layout anytime.
The goal is have someting like double way between Child Components, example:
Calling route /blog will call pages/blog/index.vue
This would send to layout/default.vue using $emit what components would be rendered (choosed from user in admin area and fetched from API) and the component ID. (example: {topBar: true, topBarID: 2})
On layouts/default.vue after get the $emit from pages/blog/index.vue I would have for example TopBar false, and then not render it, or have received true with an ID, this Id will be send to TopBar as prop for render the customized TopBar made by user on Admin area.
Would be possible someone show an example how to get the pass those data for this specific cenario please?
(Does not matter if using local variables from the Child component or vuex, just looking for an example how to get the contents of variable from Child instead an plain object or undefinied object).
PS.: If there an better approach to deal with dynamic layouts, I am accepting suggestions too.
PS2.: I know I would use specific template per page, like layout/blog and layout/contact, etc... but since the idea is make an CMS, this would not fit on this scenario, I mean, from the admin area user should be able to create pages enabling or disabling components through an page Wizard (the idea is getting something like Wix, every component customization from user will be stored in the database using an Id, and on layouts user choose the previous components mounting the page, in the end all call will be made using the ids of those), and not need to add specific layouts programing, because this the Idea of set all possible components and layouts in layout/default.vue sounds at this moment an better approach, but if is not, I would love see other ways to get same goal.
The correct way to do it would be:
<child-component-1 :showNav.sync="showNav">
And within the child component you would update that by doing:
this.$emit('update:showNav', value)
The parent would define this property:
data() {
return {
showNav: default_value
}
}
You would have to pass that variable to every child component. Every child component would have to define it as a property.
Perhaps a better way to do it would be to instead create a simple store within nuxt and use that to house the settings.

Vue Slot: How to parse then render slot components

Currently building a web page in Vue, and have hit a bit of an issue parsing and then rendering the <slot>'s child components.
I need to be able to take the slot, parse the components into an array, and then render those components for the end-user.
What I've Tried
I've tried many variations of things, most starting with this: this.$slots.default
This is the last version I tried
let slotComponents = [];
this.$slots.default.forEach(vNode => {
slotComponents.push(vNode);
});
But I've also tried selecting the elements within the vNode and using things like $childeren to select the components. No luck so far.
Potential Issues
The cause could be any number of things, but here is what I thought was going on (in order)
I'm not getting the components into the array properly
I'm not rendering them properly or missed something about how they render
Vue isn't supposed to do this?
Edit - Context
Seems like it would be easier if I gave you the full context of my specific problem.
Goal
To create a dynamic tab component. Should look like this.
// Example of component use
<tab-container>
<tab>
<!-- Tab Content -->
</tab>
<tab>
<!-- Tab Content -->
</tab>
<tab>
<!-- Tab Content -->
</tab>
<trash>
<!-- This one won't show up -->
</trash>
</tab-container>
In order to parse through this content, I needed to get the slot data out.
// Inside the <tabs-container> component
computed: {
tabs: function() {
let tabs = []
this.$slots.default.forEach(vNode => {
tabs.push(vNode);
});
return tabs;
}
}
// Inside the <tabs-container> template
<div>
{{tabs[currentTab]}}
</div>
You shouldn't be using template and computed properties if you want to programmatically render out <tab> inside <tab-container>. {{}} in templates are designed to perform basic operations of JS. The most appropriate way will be to use render function.
Render functions - Vue docs
Here is a working example that takes in few tabs components and shows only active tab component: https://jsfiddle.net/ajitid/eywraw8t/403667/

Why/when would ng-content b a good practice? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
Maybe a strange question regarding ng-content / Content projection. I get the idea that you can dynamically put in 'content' which can in turn be projected by using ng-content. One thing I could also do to achieve this is to make an Input property to pass in the information I want to show in the component.
I've come across this project; https://trimox.github.io/angular-mdc-web/#/home
which is the new material components for Angular. One thing I found out while browsing through the code is that he makes extreme use of ng-content to project some 'sub-components'. I cant help but wonder... why?? Because I can also try and solve this using one card component and a JSON/object like configuration which I pass on.
<mdc-card class="demo-card">
<mdc-card-primary-action mdc-ripple>
<mdc-card-media class="demo-card__media--16-9" [wide]="true"></mdc-card-media>
<div class="demo-card__primary">
<h2 class="demo-card__title" mdcHeadline6>Our Changing Planet</h2>
<h3 class="demo-card__subtitle" mdcSubtitle2>by Kurt Wagner</h3>
</div>
<div class="demo-card__secondary" mdcBody2>
Visit ten places on our planet that are undergoing the biggest changes today.
</div>
</mdc-card-primary-action>
<mdc-card-actions>
<mdc-card-action-buttons>
<button mdc-button mdcCardAction="button">Read</button>
<button mdc-button mdcCardAction="button">Bookmark</button>
</mdc-card-action-buttons>
<mdc-card-action-icons>
<button mdcIconButton mdcCardAction="icon" iconOn="favorite" iconOff="favorite_border"></button>
<mdc-icon mdcCardAction="icon" mdcRipple>share</mdc-icon>
<mdc-icon mdcCardAction="icon" mdcRipple>more_vert</mdc-icon>
</mdc-card-action-icons>
</mdc-card-actions>
Above you can see for example a 'card' component with several items in it. I've also found the source for these components; https://github.com/trimox/angular-mdc-web/blob/master/packages/card/card.ts.
My original question remains; when is it a good practice to use ng-content and when it's not. and why is it a good practice? I;ve also see DevExpress use it in their devextreme library; https://js.devexpress.com/Demos/WidgetsGallery/Demo/DataGrid/RecordGrouping/Angular/Light/
Thanks
Let me simplify your question. Let's say you want to create a card component. You could write your own child components (along with the code to perform the necessary content projection), like this:
<card>
<card-title>My Title</card-title>
<card-content>
<p>Content here...</p>
<card-action>Some button here</card-action>
</card-content>
<card-footer>My footer</card-footer>
</card>
...or you could just create one <card> component and have it take in a configuration object like this:
config = {
title: "My title",
content: {
innerHTML: "<p>Content here...</p>",
actions: [ {type: "button", text: "Some button here", onClick: "myFunction()"} ]
},
footer: "My footer"
}
So why would you want to go with the first option?
1. Semantic HTML
One reason is that, in general, it's good practice to separate structure (HTML), presentation (CSS), and behavior (JS / TS). Since a card component will probably contain other structural elements like its title, content, actions, footer, etc. it's best to express this structure in the HTML rather than do it through JavaScript/TypeScript. You should let your HTML markup convey the underlying meaning of your content. Doing so makes your code more semantic, clean, maintainable, and human readable. Perhaps if you're just developing an application yourself (rather than working in a team), then there's less of a need to make it semantic/readable to others because you understand your own code. But you should still make it readable for your future self because if you want to review your code a year later, you'll probably want your code to be semantic and easily readable.
2. Making use of Angular's template synatx
Another reason for going with the first option (using content projection + child components) is that you can make use of Angular's powerful features, such as data binding and *ngFor, in your HTML. For example, if you want to show a list of cards, with each card showing a social media post (like in Facebook), you could easily write:
<card *ngFor="let post of posts">
<card-title>{{post.title}}</card-title>
<card-content>
<p>{{post.content}}</p>
<card-action>
<card-button (click)="likePost(post)">Like</card-button>
<card-button (click)="sharePost(post)">Share</card-button>
</card-action>
</card-content>
<card-footer>{{post.footer}}</card-footer>
</card>
Whereas it would probably be more difficult to implement the same thing with a JS/TS configuration object.
Imagine If you are Passing Data from Parent to Child. Normally we will use Input property binding and we will get the data in child ts and we will bind the data. Instead of doing that ng-content we can place the content inside component tag from parent and bind your parent data and use ng-content inside child so that we can avoid input property binding
Parent Component
<parent>
<child>
<div class="head">{{heading}}</div>
</child>
</parent>
child component
<ng-content select=".head"></ng-content>
Here we are binding the parent data directly instead of using input property binding to process data

Categories