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

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; }[]; }

Related

Get Typescript type as value

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

Typescript dynamically infer type from object

I have a JS Object with React components, indexed by ID.
const MODAL_ENTITIES = {
changeEmail: ChangeEmailModal,
changeUsername: ChangeUsernameModal,
};
I would like to have a ModalEntity type which results in this:
type ModalEntity = {
id: 'changeEmail',
props: React.ComponentProps<typeof ChangeEmailModal>
} | {
id: 'changeUsername',
props: React.ComponentProps<typeof ChangeUsernameModal>
};
My problem is, I want the type to be dynamically generated from the MODAL_ENTITIES object, since I want the process of adding a modal to be as effortlessly as possible.
Is there a way to define this type dynamically? I could do this but I want to avoid generics, I would like T to be inferred:
export type ModalEntity<T extends keyof typeof MODAL_ENTITIES> = {
id: T;
props: React.ComponentProps<typeof MODAL_ENTITIES[T]>;
};
I made a mockup. The idea is to get generic T out of your ModalEntity type so that it can be used easily when you add a new modal.
Placeholders for your modals, assuming that each modal has different props:
import React from 'react';
const ChangeEmailModal: React.FC<{ id: string; name: string; email: string }> = ({ id, ...props }) => {
return (
<div id={id}>
{props.name} {props.email}
</div>
);
};
const ChangeUsernameModal: React.FC<{ id: string; otherName: string; username: string }> = ({ id, ...props }) => {
return (
<div id={id}>
{props.otherName} {props.username}
</div>
);
};
const MODAL_ENTITIES = {
changeEmail: ChangeEmailModal,
changeUsername: ChangeUsernameModal
};
Then we get the keys from your MODAL_ENTITIES in a dynamic way:
export type ModalEntities = typeof MODAL_ENTITIES;
// this gets all the keys in type ModalEntities
type StringKeys<T> = {
[k in keyof T]: k;
}[keyof T];
type ModalEntitiesKeys = StringKeys<ModalEntities>;
Finally:
export type ModalEntity = {
[K in ModalEntitiesKeys]: {
id: K;
props: React.ComponentProps<typeof MODAL_ENTITIES[K]>;
};
}[ModalEntitiesKeys];
The ModalEntity type will look like this and it's no longer generic. the type of props fields will be inferred dynamically as you requested regardless of different modal props.
type ModalEntity = {
id: "changeEmail";
props: {
id: string;
name: string;
email: string;
} & {
children?: React.ReactNode;
};
} | {
id: "changeUsername";
props: {
id: string;
otherName: string;
username: string;
} & {
children?: React.ReactNode;
};
}
You can elaborate more on this idea.

Passing props in React jsx as generics

In my react app i want to pass a specific interface as a generic into a unspecific component.
For example i have three specific interfaces
SpecificInterfaces.jsx
export interface InterfaceA {
name: string
age: number
...
}
export interface InterfaceB {
name: string
movies: string[]
count: number
...
}
export interface InterfaceC {
name: string
somestuff: someType
}
For each of the interfaces i have a specific component ComponentA, ComponentB and ComponentC.
These Components need to be used in a shared component ComponentShared.
Now for example i want in my ComponentA to return SharedComponent with the generic Type of InterfaceA and props of Type InterfaceA like this:
ComponentA.jsx
export interface Props<T> {
importData: T[]
... some props...
}
const props: Props<InterfaceA> = {
importData: importData //This is from Interface Type InterfaceA
... someProps ...
}
return (
<React.Fragment>
<SharedComponent<InterfaceA> {...props} />
</React.Fragment>
)
And in my sharedComponent i want to access the specific passed generic type like this:
SharedComponent.jsx
const SharedComponent= <T,>({
importData,
...the passed Props
}: Props<T>): JSX.Element => {
importData.map((data: T) =>
data.name)
At importData.map((data:T) => data.name) it throws an error, saying T has no member of name. So i guess something isnt working with my generics i pass in here, because the InterfaceA im passing in as generic has the member "name" like any ohter InterfaceB and InterfaceC. What am i doing wrong?
TypeScript doesn't know anything about the generic inside your function unless you inform it. You need to extend your generic T from a type that has the properties that you use inside the function. Consider this example:
TS Playground
function logNamesBroken <T>(objects: T[]): void {
for (const obj of objects) {
console.log(obj.name);
/* ^^^^
Property 'name' does not exist on type 'T'.(2339) */
}
}
type BaseObject = {
name: string;
};
function logNames <T extends BaseObject>(objects: T[]): void {
for (const obj of objects) {
console.log(obj.name); // ok now
}
}
More, based on the code in your question:
TS Playground
import {default as React} from 'react';
interface BaseItem {
name: string;
}
interface InterfaceA extends BaseItem {
age: number;
}
interface Props<T extends BaseItem> {
importData: T[];
}
const SharedComponent = <T extends BaseItem>({
importData,
}: Props<T>): React.ReactElement => {
return (
<ul>
{
importData.map((data, index) => (
<li key={`${index}-${data.name}`}>{data.name}</li>
))
}
</ul>
);
};
const importData: InterfaceA[] = [{name: 'a', age: 1}, {name: 'b', age: 2}];
const props: Props<InterfaceA> = {
importData,
};
const AnotherComponent = (): React.ReactElement => (
<React.Fragment>
<SharedComponent {...props} />
</React.Fragment>
);

Flow type: extendable React Component prop types

Let say there exist the SelectOption component, which props looks like:
type OptionType = {
+id: string,
+label: string,
}
type Props = {|
+options: OptionType[],
+onItemPress: OptionType => void
|}
Intentionally I wrote the OptionType type as a not exact because I expect that I will need to extend this type,
but I expected that id and label are always required.
But this doesn't work as I expected.
type CustomOptionType = {
+id: string,
+label: string,
+locationId: string,
}
const customOption = [
{
id: 'id',
label: "label",
locationId: 'ID-location'
}
]
class PlacePicker extends React.Component<{}> {
handleItemClick = (option: CustomOptionType) => {
//
}
render() {
const mockedCustomOption = [
{
id: 'id',
label: "label",
locationId: 'ID-location'
}
]
return(
<Select options={mockedCustomOption} onPressItem={this.handleItemClick} />
// Cannot create `Select` element because property `locationId` is missing in `CustomOptionType` [1] but exists in `OptionType` [2] in the first argument of property `onPressItem`.
)
}
}
With this approach I have this error:
Cannot create Select element because property locationId is
missing in CustomOptionType [1] but exists in OptionType [2] in
the first argument of property onPressItem.
How should I wrote props that have some required fields(id, label), but with possibility extend the OptionType?
I changed the OptionType to Interface which solved my issue.
https://flow.org/en/docs/types/interfaces/

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