PropTypes for specific children components - reactjs

I’m struggling to figure out how to specify PropTypes for a set of specific children components. My dialog component is allowed to get components of the type Title, Body and/or Footer. All these components may only be used once but can appear together at the same time.
Is there a recommended way to specify an appropriate PropType?
const Title = ({ text }) => (
<h1>{ text }</h1>
);
const Body = ({ text }) => (
<p>{ text }</p>
);
const Footer = ({ text }) => (
<small>{ text }</small>
);
const Dialog = ({ children }) => (
React.Children.map(children, (child) => {
return (
<div>{child}</div>
)
})
);
Dialog.propTypes = {
children: PropTypes.oneOfType([
PropTypes.instanceOf(Title),
PropTypes.instanceOf(Body),
PropTypes.instanceOf(Footer)
]).isRequired
}

Edited
You could use a custom PropType validation to check if the prop value fits the rules your component expects: component name (Title, Body and/or Footer), if there is only one of each or not, the order of these components...
But it's overkill.
The best in my opinion it's to have these components inside the Dialog component and use props to customize it. eg:
CodeSandbox
const Title = ({ children }) => <h2>{children}</h2>;
const Dialog = ({
title,
showConfirmationButton,
showCancelButton,
onConfirmation,
onCancel,
children
}) => (
<div>
{!!title && <Title>{title}</Title>}
{children}
{(showConfirmationButton || showCancelButton) && <hr />}
{showCancelButton && <button onClick={onCancel}>Cancel</button>}
{showConfirmationButton && <button onClick={onConfirmation}>Ok</button>}
</div>
);
Dialog.propTypes = {
title: PropTypes.string,
showOkButton: PropTypes.bool,
onClickOk: PropTypes.func,
children: PropTypes.any.isRequired
};
export default function App() {
return (
<Dialog
title="Title dialog"
showConfirmationButton
onConfirmation={() => console.log("ok")}
showCancelButton
onCancel={() => console.log("cancel")}
>
<p>The Dialog body as children.</p>
</Dialog>
);
}
Or if you still need to pass the component as a prop, then the best should be:
const Dialog = ({ title, body, footer }) => (
<div>
{ title }
{ body }
{ footer }
</div>
)
Dialog.propTypes = {
title: PropTypes.instanceOf(Title),
footer: PropTypes.instanceOf(Footer),
body: PropTypes.instanceOf(Body)
}
You can check the available prop types here https://reactjs.org/docs/typechecking-with-proptypes.html

Related

Set and access refs on {children} from parent with cloneElement

I'm trying to create a full page scrolling effect similar to fullpage.js.
I would like to accomplish this by using components like this:
<FullPageContainer>
<FullPageSection>1</FullPageSection>
<FullPageSection>2</FullPageSection>
<FullPageSection>3</FullPageSection>
</FullPageContainer>
The full page container should be able to control its view and scroll to any of its children via a function scroll(). Scroll, keyboard, and UI elements would trigger this function.
I was thinking I could create refs within <FullPageContainer> and set them on the children. This way no matter how many children were nested, they would all be linked up by refs. However I am having trouble with the implementation.
FullPageContainer
import React from "react";
export const FullPageContainer = ({
children,
}: {
children: React.ReactNode;
}) => {
// Create one ref for each child
let refs = children.map(() => React.useRef<HTMLDivElement>());
const childrenWithProps = React.Children.map(children, (child, index) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { ref: refs[index] });
}
return child;
});
return (
<div>
<button
onClick={() => {
console.log(refs);
}}
>
click
</button>
{childrenWithProps}
</div>
);
};
FullPageSection
import React from "react";
export const FullPageSection = React.forwardRef(
({
children,
ref,
}: {
children: React.ReactNode;
ref: React.RefObject<HTMLDivElement>;
}) => {
return (
<div
ref={ref}
style={{
height: "100vh",
width: "100%",
}}
>
{children}
</div>
);
}
);
I can't seem to access the refs from FullPageContainer. From my understanding, the refs should be set once the mounts, so I added a button to check the state of refs after everything renders. All of them are still coming back as undefined!
You don't need to create those ref, but use ref from child instead,
Might be this what you want?
export const FullPageContainer = ({
children,
}: {
children: React.ReactNode;
}) => {
// Create one ref for each child
let refs = useRef([]);
const childrenWithProps = React.Children.map(children, (child, index) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { ref: (ref) => (refs.current[index] = ref) });
}
return child;
});
return (
<div>
<button
onClick={() => {
console.log(refs.currrent);
}}
>
click
</button>
{childrenWithProps}
</div>
);
};
export const FullPageSection = ({
children
}: {
children: React.ReactNode
}) => {
return (
<div
style={{
height: "100vh",
width: "100%",
}}
>
{children}
</div>
);
};

how to add components into reusuable component? react

I'm not sure my title make sense :/ sorry for my poor description.
anyway what I was trying was.. making a reusuable component.
and the props of the reusuable component is another component.
here's what I did:
const Accordion = ({ title, content }) => {
return (
<Wrapper>
<Title>{title}</Title>
<Content>{content}</Content>
</Wrapper>
);
};
const ParentComponent = () => {
const title = () => <div>title</div>;
const content = () => {
return (<div><h2>...text...</h2><div>...text..</div></div>)
}
return <Accordion title={title} content={content} />;
};
it seems nice to me, but it does not work 🤯
the title and the content(...text...) was not showing at all.
it works in this way though, this is not what I want 🤯🤯
<Accordion title='title text' content='content context' />;
thanks for your help.
Just do this:
You need to render it like <element />
const Accordion = ({ title, content }) => {
return (
<Wrapper>
<Title><title /></Title>
<Content><content /></Content>
</Wrapper>
);
};

Is it good idea to pass multiple components as a prop?

I have a two menu components: profile-menu and main-menu.
Each menu has links with icons and component-button to open it.
Is it good idea to pass multiple components as links array and than render it with map like in this example? if not, what is alternative?
const Icon = () => <div>1</div>
const Icon2 = () => <div>2</div>
const links = [{ href: '/', name: 'MainPage', icon: Icon }, { href: '/test', name: 'Test', icon: Icon2 }]
const MainMenu = () => {
return (
<Navigation side='left' links={links}>
<img src='smt..' />
</Navigation>
)
}
const Profile = () => {
return (
<Navigation side='right' links={links}>
<img src='smt..' />
</Navigation>
)
}
const Navigation = ({ side, links, children }) => {
const menuRelayRef = useRef(null) // to close menu on page click
// some logic
return (
<Fragment>
{cloneElement(children, { ref: menuRelayRef })}
<div show={show} side={side}>
{links.map((l) => {
const Icon = l.icon // is it ok?
return (
<div>
<button>{l.name}</button>
<Icon />
</div>
)
})}
</div >
</Fragment>
)
}
i think it depends on your Icon component :
if Icon component is img tag so you should pass only src,alt.
if Icon component is SVG icon so you should pass SVG icon component.
generally if your component has same parts, so try to DRY (Dont Repeat Yourself),
and pass only dynamic parts of component.but sometimes you have no other way and passing component is best solution.

How to fix the error cannot assing "xx" to type intrinstic attributes and props using react typescript?

i am getting an error cannot find count and cannot assign {count: number; title:string} type to IntrinsicAttributes using react and typescript.
i have two components ParentComponent and ChildComponent
within parent component i am passing count prop to ChildComponent and the code is like below,
function ParentComponent = () => {
render = () => {
return (
<div>
<ChildComponent count={5} title="sometitle"/>
</div>
)
}
}
interface Props {
count: number;
title: string;
}
function ChildComponent = ({ count, title }: Props) => {
render =() =>{
return (
<>
<span>
{title}
</span>
<span>
{count}
</span>
</>
);
}
}
Could someone help me understand or fix this. thanks.
Try this:
const ParentComponent = () => {
return (
<div>
<ChildComponent count={5} title="sometitle" />
</div>
);
};
interface Props {
count: number;
title: string;
}
const ChildComponent = ({ count, title }: Props) => {
return (
<>
<span>{title}</span>
<span>{count}</span>
</>
);
};
Notes:
You can't use the React.ClassComponent method render in React.FunctionComponents (you simply return instead)
You're mixing function syntax with arrow function syntax. I used the latter above.

In React, how can I use HOC (High Order Component) to create several components out of a generic component?

I have created a generic component to be used as a wrapper for other components to be used as labels. Here is my generic component:
const Label = ({ data, attribute, style, link }) => {
if (link) {
return (
<Link to={link} style={style}>{data ? `${data[attribute]}` : ''}</Link>
);
}
return (
<div style={style}>{data ? `${data[attribute]}` : ''}</div>
);
};
I want to use this as my generic component for rendering different label components such as:
const CityLabel = ({ data }) => (
<div>{data ? `${data.city}` : ''}</div>
)
and
const UserLabel = ({ user }) => (
<div>{user ? `${user.firstName} ${user.lastName}` : ''}</div>
)
etc...
How can I use a HOC to do this?
This example assumes UserLabel only renders name instead of firstName & lastName as your Label component cannot handle two attributes.
const Label = ...,
makeLabel = (
(Label) => (mapLabelProps) => (props) =>
<Label {...mapLabelProps(props)} />
)(Label),
CityLabel = makeLabel(({data, style, link}) => ({
data,
attribute: 'city',
style,
link
})),
UserLabel = makeLabel(({user, style, link}) => ({
data: user,
attribute: 'name',
style,
link
}));
render(){
return (
<div>
<CityLabel data={{city:"NYC"}} />
<UserLabel user={{name:"obiwan"}} />
</div>
)
}

Resources