Get Typescript type as value - javascript

I have components like these
type TestComponentProps = {
title: string;
}
const TestComponent: React.FC<TestComponentProps> = ({
title,
}) => {
return <div>TestComponent: {title}</div>;
};
type TestComponent2Props = {
body: string;
}
const TestComponent2: React.FC<TestComponent2Props> = ({ body }) => {
return <div>TestComponent2: {body}</div>;
};
I would need an interface that would allow me to configure which component to render and get the props of that particular component
const dataToRender:Array<{
component: TestComponent | TestComponent2,
data: propsOf<component>
}> = [
{
component: TestComponent,
data: { title: '123' }
},
{
component: TestComponent2,
data: { body: 'lorem ipsum' }
}
];
Ideally I'd need to get the props of the particular component in a way "I want to render this component and I can only accept the correct props based on the props of that component"

You can do this, but not with plain typesciprt type annotations. In vue js for particular, for better typing you need to use defineComponent wrapper, that just return it argument (id) but with types.
In you case you can use this function.
function defineComponent<TProps, TModal extends React.ComponentType<TProps>>(x: {
component: TModal & React.ComponentType<TProps>
data: TProps
}) {
return x
}
And use it like so:
const dataToRender = [
defineComponent({
component: TestComponent,
data: { title: "123" },
}),
defineComponent({
component: TestComponent2,
data: { title: "lorem ipsum" }, // error here
}),
] as const

Related

Why does passing props cause it to be embed inside an object?

Is there a way to pass a prop without embedding it inside the object?
For instance, consider the parent component passing the "partners" data
const partners = [
{
name: "Test1",
},
{
name: "Test2",
},
{
name: "Test3",
},
{
name: "Test4",
},
];
const Partners: NextPage = () => {
return (
<>
<PartnerPanel props={partners} />
</>
);
};
The child component
// incorrect type
type PropsType = [
{
name: string;
}
];
//correct type
type PropsType = { props: { name: string; }[]; }
export const PartnerPanel: FC<PropsType> = (props): JSX.Element => {
return <div>{props.props[0].name}</div>;
};
Why is it that the props are embedded inside another props, for instance, I have to do props.props[0].name to get the value? Instead of props[0].name
The nested props object is something you are doing, not something that happens automatically with props.
You have named a prop - props
<PartnerPanel props={partners} />
This is a confusing name. You should probably rename it to be clearer as partners.
<PartnerPanel partners={partners} />
Then you would access it more logically as
export const PartnerPanel: FC<PropsType> = (props): JSX.Element => {
return <div>{props.partners[0].name}</div>;
};
You will also need to change your type
type PropsType = { partners: { name: string; }[]; }

How to store React component in object

I have an object of all apps:
export const allApps: appInterface[] = [
{
id: uuidv4(),
name: "Minesweeper",
icon: minesweeperIcon,
disabled: false,
},
];
I want to add component property to each object like this:
export const allApps: appInterface[] = [
{
id: uuidv4(),
name: "Minesweeper",
icon: minesweeperIcon,
disabled: false,
component: Minesweeper, //It is imported as import Minesweeper from ../Components/Minesweeper";
},
];
And then I want to display all components in App.js:
allApps.forEach((app)=>{
<div>{app.component}<div> // for eg: <div><Minesweeper/></div>
});
Is this possible to do?
Try as below code it should work.
allApps.map((app)=>{
const { component: Component } = app;
return <Component />;
});

How to pass dynamic params to name route - Vue JS

I have this LayoutVertical.vue parent component:
<script>
import LayoutVertical from "#core/layouts/layout-vertical/LayoutVertical.vue";
import AppCustomizer from "#core/layouts/components/app-customizer/AppCustomizer.vue";
import { $themeConfig } from "#themeConfig";
import navMenuItems from "#/navigation/vertical";
export default {
components: {
AppCustomizer,
LayoutVertical,
},
data() {
return {
showCustomizer: $themeConfig.layout.customizer,
navMenuItems,
};
},
};
</script>
This navMenuItems property contained:
export default [
{
header: "Projects",
},
{
title: "Import",
route: {
name: "project-import",
params: { token: token },
},
icon: "SettingsIcon",
},
];
And the route for this:
{
path: "/project/import/view/:token",
name: "project-import",
props : true,
component: () =>
import("#/views/apps/projects/project-import/ProjectImport.vue"),
},
Now, I have another compnent called AppNavbarVerticalLayout.vue which is child component of that parent. In this component I have a onChange methods called chooseProject. From this method I can get a token value.
My question is how can I pass this token value to that navMenuItems property's params?
Can I set a params for a specific name route?

Nuxt.js cannot update head() properties from asyncData()

I created a server-side rendered Vue.js blog using Nuxt.js with Typescript and Ghost but I'm having some issues to update html metatag using data from asyncData().
From Nuxt.js documentation I know that asyncData() is called every time before loading page components and merges with component data.
I'm getting this error:
Property 'title' does not exist on type '{ asyncData({ app, params }: Context): Promise<{ title: string | undefined; excerpt: string | undefined; feature_image: Nullable | undefined; html: Nullable | undefined; }>; head(): any; }'.
This is my code:
<script lang="ts">
import { Context } from '#nuxt/types'
import { PostOrPage } from 'tryghost__content-api'
export default {
async asyncData ({ app, params }: Context) {
const post: PostOrPage = await app.$ghost.posts.read(
{
slug: params.slug
},
{ include: 'tags' }
)
return {
title: post.title,
excerpt: post.excerpt,
feature_image: post.feature_image,
html: post.html
}
},
head () {
return {
title: this.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.excerpt
}
]
}
}
}
</script>
I already tried some solutions like using data() to set a default value for each item but nothing. Do you have any suggestion?
Without a typescript plugin like nuxt-property-decorator you won't have Typescript support for nuxt (either way, type checking and autocomplete still won't work).
That's why asyncData & fetch should be in Component options.
#Component({
asyncData (ctx) {
...
}
})
export default class YourClass extends Vue {
...
}
instead of
#Component
export default class YourClass extends Vue {
asyncData (ctx) {
...
}
}
If you still want to use asyncData() inside of your component class instead of setting the option, see this working example using the npm module nuxt-property-decorator.
Here is the working code after implementing the suggestion from #nonNumericalFloat :
import { Component, Vue } from 'nuxt-property-decorator'
import { Context } from '#nuxt/types'
import { PostOrPage } from 'tryghost__content-api'
import Title from '~/components/Title.vue'
#Component({
components: {
Title
}
})
export default class Page extends Vue {
post!: PostOrPage
async asyncData ({ app, params }: Context) {
const post: PostOrPage = await app.$ghost.posts.read(
{
slug: params.slug
},
{ include: 'tags' }
)
return {
post
}
}
head () {
return {
title: this.post.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.post.excerpt
}
]
}
}
}

ngrx dealing with nested array in object

I am learning the redux pattern and using ngrx with angular 2. I am creating a sample blog site which has following shape.
export interface BlogContent {
id: string;
header: string;
tags: string[];
title: string;
actualContent: ActualContent[];
}
and my reducer and actions are as following:
import { ActionReducer, Action } from '#ngrx/store';
import * as _ from 'lodash';
export interface ActualContent {
id: string;
type: string;
data: string;
}
export interface BlogContent {
id: string;
header: string;
tags: string[];
title: string;
actualContent: ActualContent[];
}
export const initialState: BlogContent = {
id: '',
header: '',
tags: [],
title: '',
actualContent: [],
};
export const ADD_OPERATION = 'ADD_OPERATION';
export const REMOVE_OPERATION = 'REMOVE_OPERATION';
export const RESET_OPERATION = 'RESET_OPERATION';
export const ADD_IMAGE_ID = 'ADD_IMAGE_ID';
export const ADD_FULL_BLOG = 'ADD_FULL_BLOG';
export const ADD_BLOG_CONTENT_OPERATION = 'ADD_BLOG_CONTENT_OPERATION';
export const ADD_BLOG_TAG_OPERATION = 'ADD_BLOG_TAG_OPERATION';
export const blogContent: ActionReducer<BlogContent> = (state: BlogContent= initialState, action: Action ) => {
switch (action.type) {
case ADD_OPERATION :
return Object.assign({}, state, action.payload );
case ADD_BLOG_CONTENT_OPERATION :
return Object.assign({}, state, { actualContent: [...state.actualContent, action.payload]});
case ADD_BLOG_TAG_OPERATION :
return Object.assign({}, state, { tags: [...state.tags, action.payload]});
case REMOVE_OPERATION :
return Object.assign({}, state, { actualContent: state.actualContent.filter((blog) => blog.id !== action.payload.id) });
case ADD_IMAGE_ID : {
let index = _.findIndex(state.actualContent, {id: action.payload.id});
console.log(index);
if ( index >= 0 ) {
return Object.assign({}, state, {
actualContent : [
...state.actualContent.slice(0, index),
action.payload,
...state.actualContent.slice(index + 1)
]
});
}
return state;
}
default :
return state;
}
};
and this is working fine but i am not sure if its the right approach or should i somehow separate the ActualContent into its own reducer and actions and then merge them.
Sorry if this post does not belong here and you can guide me where should put this post and i will remove it from here. Thanks in advance.
P.S. I have done some research but couldnt find any article that has complex nested objects so that i can refer. Please add any useful blog links of ngrx or related topic which can help me out.
Instead of having a nested structure
export interface BlogContent {
id: string;
header: string;
tags: string[];
title: string;
actualContent: ActualContent[]; <------ NESTED
}
You should have a normalized state.
For example here you should have something like :
// this should be into your store
export interface BlogContents {
byId: { [key: string]: BlogContent };
allIds: string[];
}
// this is made to type the objects you'll find in the byId
export interface BlogContent {
id: string;
// ...
actualContentIds: string[];
}
// ----------------------------------------------------------
// this should be into your store
export interface ActualContents {
byId: { [key: string]: ActualContent };
allIds: string[];
}
export interface ActualContent {
id: string;
// ...
}
So if you try to populate your store it'd look like that :
const blogContentsState: BlogContents = {
byId: {
blogContentId0: {
id: 'idBlogContent0',
// ...
actualContentIds: ['actualContentId0', 'actualContentId1', 'actualContentId2']
}
},
allIds: ['blogContentId0']
};
const actualContentState: ActualContents = {
byId: {
actualContentId0: {
id: 'actualContentId0',
// ...
},
actualContentId1: {
id: 'actualContentId1',
// ...
},
actualContentId2: {
id: 'actualContentId2',
// ...
}
},
allIds: ['actualContentId0', 'actualContentId1', 'actualContentId2']
};
In your logic or view (for example with Angular), you need your nested structure so you can iterate over your array and thus, you don't want to iterate on a string array of IDs. Instead you'd like actualContent: ActualContent[];.
For that, you create a selector. Every time your store change, your selector will kicks in and generate a new "view" of your raw data.
// assuming that you can blogContentsState and actualContentsState from your store
const getBlogContents = (blogContentsState, actualContentsState) =>
blogContentsState
.allIds
.map(blogContentId => ({
...blogContentsState.byId[blogContentId],
actualContent: blogContentsState
.byId[blogContentId]
.actualContentIds
.map(actualContentId => actualContentsState.byId[actualContentId])
}));
I know it can be a lot to process at the beginning and I invite you to read the official doc about selectors and normalized state
As you're learning ngrx, you might want to take a look into a small project I've made called Pizza-Sync. Code source is on Github. It's a project were I've done something like that to demo :). (You should also definitely install the ReduxDevTools app to see how is the store).
I made a small video focus only on Redux with Pizza-Sync if you're interested : https://youtu.be/I28m9lwp15Y

Categories