I was wondering if there is a way to implement a child component element out of the <router-outlet>, like in the example, I wish each component inside of the outlet could show its own buttons on the parent component.
<div class="header">
<h1>Page Title</h1>
<div class="action-buttons">
<!-- child component buttons -->
</div>
</div>
<div id="wrapper">
<router-outlet></router-outlet>
</div>
I'm not sure if it's the best way but what I'm doing is moving the buttons from the child to the parent using Renderer2 every time the route changes.
this.router.navigate(["/"]).then(() => {
this.moveButtons();
});
private moveButtons() : void {
const parent = document.querySelector('.float-buttons');
const buttonsParent = document.getElementById('childButtons');
parent.childNodes.forEach(child => {
this.renderer.removeChild(parent,child);
});
if(!buttonsParent) return;
const children = buttonsParent.childNodes;
children.forEach(child => {
this.renderer.appendChild(parent, child);
});
}
Related
How do I render before or after a child element in a container?
I am learning React by integrating it into my own website. I started with this:
function createErrorSection(name, title, description) {
const section = document.createElement('section');
const container = document.createElement('div');
const h2 = document.createElement('h2');
const p = document.createElement('p');
section.appendChild(container);
container.appendChild(h2);
container.appendChild(p);
section.id = name;
section.classList = 'section-error';
container.classList = 'section-error-container';
h2.textContent = title;
p.textContent = description;
return section;
}
Which I turned into this:
function createErrorSection(name, title, description) {
return (
<section id={name} className='section-error'>
<div className='section-error-container'>
<h2>{title}</h2>
<p>{description}</p>
</div>
</section>
);
}
This is eventually propagated down to either node.before(section) or node.after(section).
I checked inside ReactDOM, ReactDOM/server and React with no luck. I saw I could create an HTML string, but I need an HTMLElement and would rather not do my own rendering if it can be avoided (I want to learn the React way, I already know the vanilla way).
My end goal is to learn how and when to use React properly. I'd love to know the proper way, but insight, advice and workarounds are also greatly appreciated!
In React you rather want to create a custom component with a single argument which contains the corresponding properties:
// single argument contains all props
function ErrorSection({name, title, description}) {
return (
<section id={name} className='section-error'>
<div className='section-error-container'>
<h2>{title}</h2>
<p>{description}</p>
</div>
</section>
);
}
now you need to import ReactDOM and call render in order to show the component ErrorSecion with some specific property values inside a HTML node with the id #app. Make sure that your HTML document contains such a node.
import ReactDOM from "react-dom";
ReactDOM.render(
<ErrorSection name="..." title="..." description="..." />,
document.querySelector("#app")
);
Most of the react apps render some dynamically generated nested components into the DOM using a single empty HTML node inside the document body (e.g. div#app or div#root). So you most likely will only need to have a single ReactDOM.render call in your entire project.
First of all, component's name should be written in PascalCase.
In React, you should rethink the way you render elements.
There are different approaches for different purposes:
Pass components to the children prop
const Wrapper = ({ children }) => (
<div className="wrapper">
<h1>Wrapper heading</h1>
{children}
</div>
);
Now you can pass children to the wrapper this way:
const AnotherComponent = () => (
<Wrapper>
<div>Element that will be rendered where the children prop is placed</div>.
</Wrapper>
);
Pass components to custom props:
If you need to render many components in different spots, you can do this:
const MultiSpotComponent = ({ HeaderComponent, FooterComponent }) => (
<div>
{HeaderComponent}
<div>Some content</div>
{FooterComponent}
</div>
);
And then pass your components to the props the same way you do with attributes in HTML:
<MultiSpotComponent HeaderComponent={CustomHeader} FooterComponent={CustomFooter} />
Notice that I used self-closing tag for the component, because I don't render children inside it.
Render list
const AnotherComponent = () => {
const dynamicArray = ['some', 'dynamic', 'values'];
return (
<div>
{dynamicArray.map(value => <div key={value}>{value}</div>)}
</div>
);
};
I have described only 3 most-used approaches, but there are more ways to render elements. You can learn more at Official React Documentation
I am trying to create a template with two parts
the tab (slot) -> could only get the slot to work with using a ref
The content (slot)
This component(tab) is wrapped in a component(tabs aka parent) that organizes the tabs based on certain props.
The overall goal is to create something like so:
https://getbootstrap.com/docs/4.0/components/navs/#tabs
Except with the ability to have custom tabs. For simplicity, I want to keep all the information relating to the tab within the tab component
1 - the header is not rendered in the component itself but pushed to the parent ***
2 - the tab component pushes the $ref to the parent and then the parent renders the HTML and listeners
How can i push(or another method to pass the information to the parent) data to the parent and keep all the listeners and js associated with the components in the tab slot
//tab component
<template>
<div>
<div class="tab" ref="tab">
<slot name="heading"> //-> Only available in setup context.slots if the default content is not used therefore resulted in using ref
//Default content
{{heading}} //-> if I add content to the heading slot via different components, the JS/listeners associated to those components do not work. I assume because I'm only pushing the HTML
</slot>
</div>
<div class="content ">
<slot/>
</div>
<div>
</template>
<script>
import {onMounted, ref} from '#nuxtjs/composition-api'
setup(props, {parent}){
const tab = ref()
onMounted(()=>{
let tab = {
data: tab.value //The entire ref
//data: tab.value.innerHTML => Works for passing the html but no listeners or js work
}
//parent has data.tabs = []
parent.data.tabs.push(tab)
})
return {
tab
}
},
props:{
....
}
</script>
//tab parent component (part to render tab via data.tabs)
<ul
>
<li
v-for="(child, index) in data.tabs"
class="s-tabs--li"
v-bind:key="index"
v-html="child.data"
></li>
</ul>
//Used in action
<s-tabs>
<s-tab heading="Home">
<div>
Home
</div>
</s-tab>
<s-tab heading="Service" icon="flower" tag="music">
<div>
Service
</div>
</s-tab>
</s-tabs>
I am adding component dynamic using viewRef and component factory resolver, I could achieve the dynamic creation. Now i need to add a drag and drop feature to the every component added individually
<button type="button" (click)="createComponent()">
Create Widget
</button>
<div cdkDropList (cdkDropListDropped)="drop($event)" [cdkDropListData]="components" >
<div cdkDrag>
<ng-container #container ></ng-container>
</div>
</div>
ts file for adding component
createComponent() {
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(
DemoComponent
);
let componentRef: ComponentRef<
DemoComponent
> = this.container.createComponent(componentFactory);
let currentComponent = componentRef.instance;
currentComponent.selfRef = currentComponent;
currentComponent.type = ++this.index;
currentComponent.type1 = ++this.heleloIndex;
currentComponent.index = ++this.index;
// prividing parent Component reference to get access to parent class methods
currentComponent.compInteraction = this;
// add reference for newly created component
this.components.push(componentRef);
}
Code in Child Component
<h1>Alert {{type}}</h1>
<h2>Alert {{type1}}</h2>
<button (click)="change()">change Widget data
</button>
<button (click)="removeMe(index)">Remove Widget
</button>
<hr>
The drop method
public drop(event: CdkDragDrop<any[]>) {
console.log(event);
moveItemInArray(this.components, event.previousIndex, event.currentIndex);
console.log(event.container.data);
}
Now i can drag the component. But that is applying to whole div. I need to drag and drop individual blocks
NOTE: I am not using ngFor
[Example depiction][1]
[1]: https://i.stack.imgur.com/Vh1Q6.jpg
My particular use case of React is thus:
I wish to add a small React Component to a card that is an existing, fully-functional HTML element, per all the cards on the page. This React Component shall serve to implement a new feature on those cards : reverting changes.
The HTML (well, the MVCE version of it)
is something like this:
<div id="some-id" class="card float-sm-left menu-visual-card " onclick="(function(event) { console.log('I got clicked, and a modal will spawn' ) })(event)">
<div class=card-block>
<h5 class="card-title format-text">Some title</h5>
<!-- some business elements here -->
</div>
<!-- card footer -->
<div class=customized-indicator-react></div>
</div>
The React Component
in its tl;dr version is the following:
class CustomizedIndicatorComponent extends React.Component {
constructor(props) {
super(props)
// business logic
let active = this.props.active
this.state = {
active : active
}
}
toggleActive = () => {
this.setState({
...this.state,
active : !this.state.active
})
}
// setup
componentDidMount() {
// here's where I tried to add a jQuery onclick listener to stop propagation, only to have the React Component listener get stopped
}
// teardown
componentWillUnmount() {
console.log("CustomizedIndicatorComponent destroyed!")
}
// the UI logic
render() {
if (this.state.active) {
return (
<div>
<div
className="badge badge-sm badge-info float-sm-left customized"
style={{marginRight:"10px"}}
>Customized</div>
<div
onClick={(e) => {
e.stopPropagation()
this.toggleActive()
}}
title="Click to undo customizations">
<i className="fa fa-undo" aria-hidden="true"></i>
</div>
</div>
)
}
return <div />
}
}
What happens when you run this?
When I run this, it renders. However, when I click the widget to "de-activate" the element, the container's event-handler still fires!!
I know there is a slew of internet questions about this issue or something close to it, but none of the ones I could find seem to be about this exact use case.
Also, adding an event listener in componentDidMount doesn't work, as that prevents anything from firing!
Is there any way I can make this work without wasting developer-hours refactoring everything including the parent HTMLElements?
A "hacky" way you may consider is to get the parent's id from inside the React component and disable the click event from there.
If id could not be passed as a property to the React component, you can try using ReactDOM.findDOMNode(this).parentNode.getAttribute("id") to get it and then disable the event using:
document.getElementById(id).style.pointerEvents = 'none';
I want to achieve a carousel like Materialize.
Have an API from where I am fetching the data, so according to Materialize
I compared the console or Materialize default and my rendered components.
I guess the problem is, it's not inheriting the properties of carousel-item
Class carousel-item is supposed to Render inside of Class carousel.
<div className="carousel">
// These are supposed to be dynamic, below component is not present here
<div className="carousel-item">
</div>
</div>
How I am trying to render the data is in this manner.
renderAlbums(){
return this.state.albums.map(album =>
<Card song={album.name} singer={album.artist_name} src={album.cover_photo_url}/>
);
}
Rendered the data <Card />(It contains the class of carousel-item), which is supposed to place Card containing class of carousel-item.
class Carousel extends Component {
state = { albums: [] };
componentWillMount() {
axios.get('https://cors-anywhere.herokuapp.com/https://stg-resque.hakuapp.com/albums.json')
.then(response => this.setState({albums: response.data}));
}
renderAlbums(){
return this.state.albums.map(album =>
<div className="carousel-item"><Card key={album.name} song={album.name} singer={album.artist_name} src={album.cover_photo_url}/></div>
);
}
render() {
return (
<div className="carousel center">
{this.renderAlbums()}
</div>
);
}
}
export default Carousel;
This is my Card component
class Card extends Component {
render() {
return (
<div className="card z-depth-4">
<div>
<img src={this.props.src} />
</div>
<p>{this.props.song}</p>
<div className="singer">{this.props.singer}</div>
</div>
);
}
}
export default Card;
EDIT:
Want that content to display like this.
But it's not working the way it's expected.
Please suggest me, what am I doing wrong?
In axios.get, I see that you are using proxy link.
One reason is, it can be creating problems.
Other reason can be you are trying to put carousel-item into carousel.
Try adding center class to both i.e. carousel as well as carousel-item.
Check if these works.
First of all, there is nothing in your Carousel that says which element is active. You need to have a state variable that points to the active element.
Then you only need to draw [-2, -1, 0, 1, 2] offsets vs the active one. And each rendered card needs to know which offset to know their style.