Use <component> that was imported in the parent - javascript

I'm building a component that manages other components.
It dynamically render components in specific places depending on the props and inputs, much like an orchestrator.
Use case
My orchestrator have the following placeholders, like a grid (p1 ... p6):
|-p1-|-p2-|-p3-|
|-p4-|-p5-|-p6-|
In a given moment, it renders the component C1 into p2 and C2 into p6:
|-p1-|-C1-|-p3-|
|-p4-|-p5-|-C2-|
In another moment, it replaces the C1 by C3:
|-p1-|-C3-|-p3-|
|-p4-|-p5-|-C2-|
The problem
Given this dynamism, I can't (as far as I know) use slots. So I'm using component tags with the :is prop in order to dynamically render the correct component. The problem is that in order for the :is to work, I must have the component defined in my orchestrator/manager component. And I would like to keep it separated for reuse, doesn't make sense to import the components there. One solution would be to globally register the components. I would like to avoid that if possible. Is there a way? I'm open to any kind of reflection-magic you may think n_n'

You can just pass the component via a prop like this:
Orchestrator.vue
<component :is="comp"/>
export default {
props: ['comp']
}
Test.vue
<orchestrator :comp="MyComponent"/>
import Orchestrator from './orchestrator.vue'
import MyComponent from './my-component.vue'
export default {
components: {
Orchestrator,
},
data() {
return {
MyComponent,
};
},
}

You should be able to use async components, loaded dynamically.
For example...
<component :is="p2Replacement"></component>
data () {
return {
p2Replacement: null
}
},
methods: {
setP2Replacement(component) {
this.p2Replacement = () => import(`path/to/${component}.vue`)
}
}
And call it like setP2Replacement('C1') or setP2Replacement('C3').
The fixed parts in the import expression are required so Webpack can include the appropriate files using a glob pattern. See https://webpack.js.org/guides/dependency-management/#require-context

Related

How can I pass data from parent to nested components?

I'm creating a Vue3 web app and I was wondering what is the best way to pass data from parent to nested components (more specific, from parent to child of children).
Let's say I have this schema:
Card
|
|
CardHeader CardBody
|
|
BodyHeader BodyParams BodyResponse
And I want to know the best way to pass data from Card to BodyHeader, BodyParams and BodyResponse
On Card Component I will be fetching some data from an API. Lets say that i fetch a json like this:
{
header: {
title: 'hello header'
},
body: {
headers: ['authorization', 'anotherheader']
params: ['currency', 'hair'],
response: ['200', '401']
}
}
I know I just could do this:
<Card/> :
<template>
<CardHeader :header="hedaer"></CardHeader>
<CardBody :body="body"></CardBody>
</template>
Then, in <CardBody/> :
<template>
<CardBodyHeader :headers="body.headers"></CardBodyHeader>
<CardBodyParams :params="body.params"></CardBodyParams>
<CardBodyResponse :responses="body.responses"></CardBodyResponse>
</template>
<script>
import CardBodyHeader from "./CardBodyHeader.vue";
import CardBodyParams from "./CardBodyParams.vue";
import CardBodyResponse from "./CardBodyResponse.vue";
export default {
components: { CardBodyHeader, CardBodyParams, CardBodyResponse },
props: {
body: {type: Object}
}
};
</script>
But I don't know if that would be the optimal solution.
I guess there are many ways, you could do that.
Easiest one would be state management, and you can use something like Pinia and Vuex to do so or maybe even write your own store (But why do that when you have Pinia).
Second one would be the usage of props, but you should keep in mind that props should never be mutated.
==> https://vuejs.org/guide/components/props.html
Third one would be the use of Vue3 (Provide / Inject) as seen on the docs ==> https://vuejs.org/guide/components/provide-inject.html
You can also send data from a child to a parent component Vue's built-in $emit() method.
But of course each and every one of these is solving different problems, so try to have a quick read on docs about each and every one of them to solve the issue you're facing, and to know which to use on the upcoming issues you might face.
If you don't use any state management library like Vuex or pinia.
I suggest you to use provide/inject to prevent props drilling.
You need to provide the data from the most parent component, and in the child components that need these data, just use inject
Ex parent:
export default {
provide: {
message: 'hello!'
}
}
Ex: nested child
export default {
inject: ['message'],
created() {
console.log(this.message) // injected value
}
}

Hooks can only be called inside the body of a function component. (fb.me/react-invalid-hook-call)

import { useEffect } from "react";
export const Routes = () => {
useEffect(() => {
console.log("red")
}, []);
const a = [{ name: "sathish" }];
const b = [{ name: "pandi" }];
const c = [{ name: "suren" }];
if (false) {
return a;
} else {
return b;
}
};
With this code I have error like × Hooks can only be called inside the body of a function component.
Take a look at the Rules of Hooks to understand their restrictions and what they're meant to be used for.
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function. By following this rule, you ensure that Hooks are called in the same order each time a component renders.
Unlike other answers and comments, importing React and returning JSX is not a requirement for a function to be considered a component.
Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.
The minimal requirements of a React component:
return a React element1 which can be anything that can be rendered: numbers, strings, other elements or an array of any of these.
Note that booleans and null are ignored. Returning strictly undefined (or not returning, which is the same) would fail.
then it must be used as a component: <MyComponent /> or React.createElement(MyComponent) inside the rendering phase.
You're not using Routes as a React component, you're calling it as a function, outside the rendering phase.
import Cookies from 'js-cookie';
import { Routes } from 'routes.js'
import { Redirect } from 'react-router-dom';
const routes = Routes(); // <-- Called as a function, not a component
This is why you're getting the error.
While you're calling useEffect at the right place inside the Routes function, since it's not called within React's rendering flow, React is detecting it as if it was outside of a function component.
That being said, since you're not explaining what you're trying to accomplish, we can't tell you how to fix it. Telling you to use Routes as it is inside another component might be misleading.
1. While referred as element in the React documentation, it's known as a node when dealing with prop-types.
Technically you are missing 2 important steps, the import React part and returning JSX.
What you can do to make it work is the following:
import React, { useEffect } from 'react';
export const Routes = () => {
useEffect(() => {
console.log("red")
}, []);
const a = [{ name: "sathish" }];
const b = [{ name: "pandi" }];
const c = [{ name: "suren" }];
return a.map((e, i) => <span key={i}>{e.name}</span>);
};
Additionally I have added an Array.prototype.map() to represent the name from your array in a <span> element. For the code sample I have removed your if statement.
Update:
Also based on the comments the issue is related to calling outside of Admin component the Routes component in your code.
What you need to do also to make it work to include into the Admin component render function as the following:
class Admin extends Component {
// rest of your code
render() {
return <>
{/* rest of the code */}
<Routes />
</>
}
}
I hope this helps!

Vue: Components/templates as props

I am currently trying to learn vue and struggling with the whole component concept.
Let's say I have some components that define tabs(like browser tabs).
This component has a prop called name.
So you'd maybe use the component like so:
<tab :name="tab.display_name"></tab>
However lets say things need to be a bit more complex. Like for example, you don't just want the name to be a string, but regular HTML. Okay, so, you can use the prop in a v-html directive and use the tab component like this:
<tab :name="'<span class=\'fancy-styling\'>' + tab.display_name + '</span>'"></tab>
This one took me a while to figure out because of all the quotes. Is there a way to escape this escape hell(pun totally intended)?
How could I take it this out into its own snippet/template?
And what if we make it even more complex - say we require the prop be a vue component?
<tab :name="'<my-custom-component #click="onClick()">' + tab.display_name + '</my-custom-component>'"></tab>
The last one is invalid on a number of levels, not the least of which being the mess of quotes.
How would I accomplish this? What is the best approach?
If you are trying to inject html in props, you should really be looking at using slots.
https://v2.vuejs.org/v2/guide/components-slots.html
The whole point of using components is that you create a separation of concerns. Defining all the layered components in a single line completely defeats that purpose. Create three separate components. One container component like this.
index.vue
<template>
<tab :name="mycoolname"></tab>
</template>
<script>
import tab from tab.vue
export default {
components: {
tab
}
</script>
The name prop you can than use in that template using {{name}}. Than within the tab component, like this:
tab.vue
<template>
<h1>{{name}}</h1>
<my-custom-component #click="doStuff()"></my-custom-component>
</template>
<script>
import my-custom-component from my-custom-component.vue
export default {
props: [
'name'
],
components: {
my-custom-component
},
methods: {
doStuff () {
console.log('hooray this totally works')
}
</script>
I am using separate build files here, but the concept remains the same when you just import vue into your project. Components can contain everything, from a simple div with a <p> inside to a full page. This component system is insanely powerful to keep things organised.
Without webpack it would look something like this:
var myCustomComponent = {
template: `fancyHTML`
}
var Tab= {
components: {
'my-custom-component': myCustomComponent
},
template: `even more fancy html containing <my-custom-component>`
new Vue({
el: '#app'
components: {
'tab': Tab
}
})
}

Use Higher Order Component in React to format child components

I am building a wrapper component that renders children components in a grid, either vertically or horizontally by taking the layout config as a prop:
class App extends React {
render() {
return (
<WrapperComponent layout="horizontal">
<ChildComponent1>
<ChildComponent2/>
<ChildComponent3/>
</WrapperComponent/>
}
}
I want to create a HOC that returns either a <VerticalLayout/> or a <HorizontalLayout/> component depending on the configuration passed to <WrapperComponent/>.
So the code pattern should be:
const LayoutComponent = HOC(InputComponent).
I cannot pass <WrapperComponent/> as an input to HOC, as it needs to wrap the <ChildComponents/>, and it is the entry point for this layout:
How can I achieve the desired result with HOC implementation? I know that this can be achieved without HOC, but I specifically want to write this program by implementing HOC, as some of the tasks/code of <VerticalLayout/> and <HorizontalLayout/> will be the same and also because I want to practice writing HOCs.
Don't reinvent the wheel! it's already there and ready to be used, have a look at : https://github.com/acdlite/recompose/blob/master/docs/API.md#branch
Basically you pass your configuration as a condition, something around these lines should do the trick:
branch(
test: ( { config } => (config.isVertical),
left: <VerticalLayout/>,
right: <HorizontalLayout/>
)

Rendering a list based on a shared property in Vue

I'd like to render out a list of components based on whether the type property is of a certain type. For example, if resource.type === 'article' then render all of the resources with type article, etc.
My resources array looks like this
I have created a component that is basically just a view.
<template>
<div class="type-feed">
<resource-card></resource-card>
</div>
</template>
<script>
import Vue from 'vue';
import VueFire from 'vuefire';
import ResourceCard from '../components/ResourceCard'
var resourcesRef = firebase.database().ref('resources');
// explicit installation required in module environments
Vue.use(VueFire);
export default {
name: 'type-view',
components: {
ResourceCard
},
data () {
return {
}
},
firebase: {
resources: resourcesRef
}
}
</script>
<style>
</style>
The <resource-card> component should take in the list and render a specific information on an individual resource object. Something like the following
<resource-card v-for="(resource, index) in resources" :resource="resource>
My question is, what is the best way to render a list by resources.type?
If <resource-card> is not used anywhere else, then just pass the resource to it and let it decide what to render according to resource.type should be enough.
If you could need a article card later elsewhere, or you want a more elegant design, then you may define a component for each resource type, and in the v-for, you can use dynamic components to render different components in one loop.

Categories

Resources