Extending React Component in order to add Custom Fields? - reactjs

i'm trying to create a component that returns a Flex Element from chakra-ui , naturally i would like to keep the Auto Complete from the Flex Type when using my component but additionally i would like to add an extra prop to it called "iconName".
This is the code i wrote:
type TFlex = typeof Flex
const Tab: TFlex = ({ iconName, ...props }) => {
return (
<Flex align="center" height="12" minWidth="12" {...props}>
<Icon name={iconName} size="8" />
</Flex>
)
}
export default Tab
But doing so is yielding the following error:
From my understanding I would need to:
Extend the FlexProps Inteface and call it MyFlexProps
Extend the Flex Type and call it MyFlexType
Type my Tab instance with MyFlexType
But unfortunately i could not make this work and also i'm not sure if that's the right approach?
Thank you very much for your help.

Turns out this solved my issue:
interface ITab extends FlexProps {
iconName?: string
}
const Tab: React.FC<ITab> = ({ iconName, ...props }) => {
return (
<Flex align="center" height="12" minWidth="12" {...props}>
<Icon name={iconName} size="8" />
</Flex>
)
}

Related

Dynamically style components passed as props

The goal is to style a prop inside a function (if the prop exists).
More specifically, I basically pass an icon (from styled-icons) as a prop to it and it is supposed to add styling to that icon.
This works with warnings:
const InputRounded = ({ type, placeholder, icon }) => {
const Icon = styled(icon)`
color: #807da0;
width: 1.75rem;
margin: 0 1.25rem 0 0.75rem;
`
return (
<Container>
<Input type={type} placeholder={placeholder} />
<Icon />
</Container>
)
}
Here is how I call it:
import { LockPassword } from '#styled-icons/remix-line'
<Input type="password" placeholder="Password" icon={LockPassword} />
I am aware that one shouldn't create a component inside a function, but I have tried numerous ways otherwise and haven't reached anywhere. Any help for a better method would be greatly appreciated.
You can, very simply, pass a style prop to icon.
const InputRounded = ({ type, placeholder, icon: Icon }) => {
return (
<Container>
<Input type={type} placeholder={placeholder} />
<Icon style={{ color: '#807da0', ... }} />
</Container>
)
}
You could probably use the cloneElement method in React
https://reactjs.org/docs/react-api.html#cloneelement
return (
<>
{ React.cloneElement( icon, [props] }
</>
)
It is similar to
<element.type {...element.props} {...props}>{children}</element.type>
To override some values you can do inline styling.
or otherwise you could use some css classes to override with selectors as well.
if you are going to use styled-icons
you can just simply provide it the props directly.
Styled Icons accept all the valid props of an <svg /> element
https://github.com/styled-icons/styled-icons#props

Reusing Control component twice with different values

I have a custom Control component that adds an icon to one of my two selections. I need to use the Control component again but to replace the icon with a different one for my second selection but react-select doesn't work if I make a new one called Control2 for example. How would I do this?
const Control = ({ children, ...props }) => {
const style = { cursor: 'pointer'};
return (
<components.Control {...props}>
<span style={style}">
<FontAwesomeIcon icon={faSearch}/>
</span>
{children}
</components.Control>
);
};
return (
<Select
options={data}
components={Control}
isMulti
/>
// I want a new Select with new Control with different fontAwesome icon
<Select
options={data2}
components={Control}
isMulti
/>
);
I am not very clear of the complete implementation here but I think you are looking for something like this
<Select
options={data2}
components={ () => <Control faSearch="whatevericon">}
isMulti
/>
Solved by checking props for custom parent name and switching icon based off it. Simple solution example:
const Control = ({ children, ...props }) => {
const style = { cursor: 'pointer'};
let wantedIcon = "defaultIcon";
if(props.selectProps.classNamePreFix == "myControl2") {
wantedIcon = "differentIcon";
}
return (
<components.Control {...props}>
<span style={style}">
<FontAwesomeIcon icon={wantedIcon}/>
</span>
{children}
</components.Control>
);
};

How do I update state to specific value?

I hope you're all well.
I would be so grateful if any of you can shed some light on the following question..
There are two relevant components:
Parent component which fetches data using GraphQL
let authors = "";
const { loading, data } = useQuery(FETCH_AUTHORS_QUERY);
console.log(`Loading: ${loading}`);
//console.log(data);
if (data) {
authors = { data: data.getAuthors };
}
return (
<Grid columns={3}>
<Grid.Row className="page-title">
<h1>Recent Essays</h1>
</Grid.Row>
{loading ? (
<h1>Loading essays..</h1>
) : (
authors.data &&
authors.data.map(author => (
<Grid.Column key={author.id} style={{ marginBottom: 20 }}>
<AuthorCard author={author} />
</Grid.Column>
))
)}
<Grid.Row></Grid.Row>
</Grid>
);
}
const FETCH_AUTHORS_QUERY = gql`
{
getAuthors {
id
Author
Description_1
Description_2
}
}
`
Child component called 'AuthorCard' (you can see placed in the parent component above):
function AuthorCard({ author: { Author, Description_1, id } }) {
const [writer, setWriter] = useState();
return (
<Card fluid>
<Card.Content>
<Image
floated="right"
size="mini"
src="https://image.cnbcfm.com/api/v1/image/105691887-1556634687926ray.jpg?v=1576249072"
/>
<Card.Header>{Author}</Card.Header>
<Card.Meta as={Link} to={`/essays`}></Card.Meta>
<Card.Description>{Description_1}</Card.Description>
</Card.Content>
<Card.Content extra>
<Button as="div" labelPosition="right">
<Button color="teal" basic>
<Icon name="heart" />
</Button>
<Label basic color="teal" pointing="left"></Label>
</Button>
<Button labelPosition="right" as={Link} to={`/essays`}>
<Button
color="blue"
basic
key={Author.id}
onClick={() => setWriter(Author)}
>
<Icon name="comments" />
</Button>
</Button>
</Card.Content>
</Card>
);
}
export default AuthorCard;
The issue is as follows:
When I click the as={Link} to={/essays} button in the child component, I would like to 'setWriter' state to the individual Author whose button I am clicking on.
However, I am not able to specify the individual author whose button is being clicked on. React thinks I want to 'setWriter' for the entire list of authors. For example, when I console.log(Author) within the button, I can see every author in the list printed in the console.
I have tried adding id, using event.target .value, and onChange instead of onClick.
I just haven't been able to target the individual author in order to update the state (which will be needed in a different component).
Please let me know if anything isn't clear or if it would be helpful to provide more details.
Thanks in advance and happy coding!!!
Dan
You should use a destructuring assignment expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
I think you should declare your child component like this:
function AuthorCard({ Author, Description_1, id }) {
...
...
...
}
Thanks for the reply.
I finally solved the issue. I had to use:
onClick={e => setWriter(e.currentTarget.Author)}
I hadn't known about using 'current target.'
I can then transfer the state to other components through useContext.
Hopefully this can help someone else!
Dan

Passing component as prop to child component

I want to pass multiple badge components as props to a component.
<MouseOverPopover
data={
row.original.region == null ? (
"-"
) : !Array.isArray(row.original.region) ? (
<Badge variant="info"> {row.original.region} </Badge>
) : (
row.original.region.map((region) => (
<Badge variant="info" style={{ margin: "2px" }} key={region}>
{region}
</Badge>
))
)
}
/>
Here is the MouseOverPopover component
export default function MouseOverPopover({ data }) {
I then use { data } to display the content.
However, the badges are not rendering and I got this error in the log
Failed prop type: Invalid prop variant of value info supplied to ForwardRef(Badge)
I am guessing that passing component inside props is probably the wrong way to do that. Is there a right way I can do this?
First point: error it self says
Failed prop type: Invalid prop variant of value info supplied to ForwardRef(Badge)
Means below Badge with props variant="info" is invalid.
<Badge variant="info"> {row.original.region} </Badge>
^^^
There are only two variant for Badge 'dot'| 'standard' check here
Second point: different way to pass your props
Here React official site Composition vs Inheritance
Containment is one of the way to pass your props Badges/any other components to MouseOverPopover (as it represent generic “boxes”) component;
As per my opinion do this
const getBadges = () => {
if (row.original.region == null) {
return "-";
} else if (!Array.isArray(row.original.region)) {
return <Badge variant="standard">{row.original.region}</Badge>;
} else {
return row.original.region.map((region) => (
<Badge variant="standard" style={{ margin: "2px" }} key={region}>
{region}
</Badge>
));
}
};
<MouseOverPopover>{getBadges()}</MouseOverPopover>;
MouseOverPopover component:
export default MouseOverPopover = ({ children }) => {
return (
...
{children} //badge will render here
...
)
}
Your code will also works you used your own convention (data={...}) and I used children as props

antd SubMenu "TypeError: Cannot read property 'isRootMenu' of undefined"

I use antd 3.15 and GraphQL to fetch data and generate a list of SubMenu and Menu.Item inside of Menu. However, I got the error message like this Uncaught TypeError: Cannot read property 'isRootMenu' of undefined I have no idea what is wrong with my code. isRootMenu is not a prop listed anywhere on the doc. ant.design/components/menu/#header and when I hardcoded all the SubMenu and Menu.List there is no problem. Can I iterate data from GraphQL to generate the SubMenu and Menu.List?
Can someone help me with this issue, please? Thank you! Here is my code:
import * as React from 'react';
import './SideNav.scss';
import { Menu, Icon } from 'antd';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
const FLOORS_QUERY = gql`
query {
getAllFloors {
id
floorName
rooms {
id
roomName
roomNumber
roomDescription
}
}
}
`;
export default class SideNav extends React.Component {
render() {
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>;
if (error) console.log(error);
console.log(data);
return (
<React.Fragment>
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
}
>
<React.Fragment>
{floor.rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</SubMenu>
))}
</React.Fragment>
);
}}
</Query>
</Menu>
);
}
}
You should pass the props to the submenu.
const CustomComponent = (props) => (
<Menu.SubMenu title='SubMenu' {...props}>
<Menu.Item>SubMenuItem</Menu.Item>
</Menu.SubMenu>
)
so a solution to your question would be to do the following;
move the query outside of the containing menu
pass the props to the SubMenu
const FloorMapSubMenu = ({ id, floorName, rooms, ...other }) => {
return (
<Menu.SubMenu
key={id}
title={
<span>
<Icon type="plus" />
<span>{floorName}</span>
</span>
}
{...other} // notice the other props, this is were the 'isRootMenu' is injected from the <Menu> children
>
<React.Fragment>
{rooms.map((room) => (
<Menu.Item key={room.id}>{room.roomNumber}</Menu.Item>
))}
</React.Fragment>
</Menu.SubMenu>
)
}
class SideNav extends React.Component {
render() {
return (
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <h4> loading... </h4>
if (error) console.log(error)
console.log(data)
return (
<Menu theme='light' defaultSelectedKeys={['1']} mode='inline'>
{data.getAllFloors.map((floor, i) => (
<FloorMapSubMenu key={i} id={floor.id} floorName={floor.floorName} rooms={floor.rooms} />
))}
</Menu>
)
}}
</Query>
)
}
}
I found out the Ant design SubMenu needs to use the parent to check some properties like isRootMenu at
SubMenu.js:260
getPopupContainer = props.parentMenu.isRootMenu ? props.parentMenu.props.getPopupContainer : function (triggerNode) {
return triggerNode.parentNode;
}
In order to solve it you need to manually pass parent props into SubMenu like
<Menu.SubMenu {...this.props}/>
to solve the problem. Hope this helps u
Related Github issue item https://github.com/react-component/menu/issues/255
I had this issue while trying to add a <div> as a child of Menu. I just added an empty Menu.Item as the first child of my menu, and the error went away.
<Menu>
<Menu.Item style={{display: 'none'}} />
<div>...</div>
</Menu>
I ran into the same issue. It seems Antd does not allow to place arbitrary components into a Menu/SubMenu. My guess is that Menu.Item needs to get some props, which are passed from Menu/SubMenu to its children.
So you can either create a custom component that passes all props down, or remove the inner <React.Fragment> declaration (the one that is inside the SubMenu), which is not needed anyway.
I was able to make it work by putting the <Query> Component at the top:
<Query query={FLOORS_QUERY}>
{({ loading, error, data }) => {
if (loading) return <Spin />;
if (error) console.log(error);
console.log(data);
return (
<Menu theme="light" defaultSelectedKeys={['1']} mode="inline">
{data.getAllFloors.map((floor) => (
<SubMenu
key={floor.id}
title={
<Link to="/{floor.id}">
<span>
<Icon type="plus" />
<span>{floor.floorName}</span>
</span>
</Link>
}
>
{floor.rooms.map((room) => (
<Menu.Item key={room.id} onClick={this.showRoomProfile}>
{room.roomNumber}
</Menu.Item>
))}
</SubMenu>
))}
</Menu>
);
}}
</Query>
According to the Typescript definitions the childrens of Menu should be of kind Item, SubMenu, ItemGroup or Divider. If you must place a different component on the Header, wrap the Menu and the desired component on a Header component component as such:
import { Layout } from 'antd';
const { Header, Footer, Sider, Content } = Layout;
<Layout>
<Layout.Header>
<div className="logo" />
<Menu>
<Menu.Item key="1">nav 1</Menu.Item>
<Menu.Item key="2">nav 2</Menu.Item>
</Menu>
<Layout.Header>
</Layout>
I have run into the same issue. But my issues was I have using ternary condition to show some menu's dynamically inside part used the <></> element. That caused me this issue. Once removed that everything work fine.

Resources