What is the best approach for adding optional components inside a component?
I have one like this:
type Props = {
children: React.Node,
title?: string,
/**May add any component next to the header. Should be inside a fragment. */
headerComponents?: React.Node,
className?: string,
}
export const Content = ({ children, className, title, headerComponents }: Props) => (
<div className={`page-content ${className}`}>
<div className='page-content-header'>
{
title && (
<h2 className='content-title'>{title}</h2>
)
}
{
headerComponents && (
<div className='page-header-right'> {headerComponents} </div>
)
}
</div>
{children}
</div>
);
The headerComponent acts as a prop that can receive another component, like this:
<Page.Content
headerComponents={
<>
<Button>First</Button>
<Button>Second</Button>
</>
}
title='Example title'
>
<div>Example text</div>
</Page.Content>
And it works. But I'm wondering if there's a better approach.
This approach looks fine to me. It is readable and intuitive to pass props to the component, followed by conditionally rendering the optional components based on the values of the props.
Related
In my application I have a component that I want to style with the css prop from outside.
function Component({css}:{css?: React.CSSProperties}) {
// some stuff going on here
return (
<div
css={{
color: blue,
...css
}}
>
// some stuff going on here
</div>
)
}
The background is as follows:
I want to use Component in different scenarios where I have to style the container based on the surrounding layout. E.g. flex, grid or in combination with some components I have to add different margins.
Now instead of introducing many props for all possible scenarios, I want to be able to style the container from outside the component.
E.g. usages of the component could be:
function Layout() {
return (
// some other components
<Component css={{margin: 12}}/>
// some other components
)
}
or
import {css} from "#emotion/react"
const style = css({margin: 12})
function Layout() {
return (
// some other components
<Component css={style}/>
// some other components
)
}
or
import {css} from "#emotion/react"
const style1 = css({margin: 12})
const style2 = css({color: 'red'})
function Layout() {
return (
// some other components
<Component css={[style1, style2]}/>
// some other components
)
}
I have the following problems:
If I use css as the prop name (as in the above example) the style is not applied. If I change the name of the prop to e.g. newCss it works as expected
React.CSSProperties is not the right prop type to handle all the possibilities of emotions css prop.
How can I merge the different css prop possibilities (object, list) with the css prop from Component?
In fact, we don't need to use the extra props. As Ben Laniado mentioned, the official documentation states
Any component or element that accepts a className prop can also use the css prop.
https://emotion.sh/docs/css-prop#use-the-css-prop
So what we need is accepting className and css as props and add className to the component. (We don't need css to the component but need it for types)
type ComponentProps = {
css?: SerializedStyles;
className?: string;
};
const Component: VFC<ComponentProps> = ({ className }) => {
return (
<div css={{ color: blue }} className={className}>
hello world
</div>
);
};
export default function App() {
return (
<div className="App">
<Component css={{ margin: 12 }} />
</div>
);
}
This is the full working example.
https://codesandbox.io/s/react-component-accepting-emotion-css-prop-wskbh?file=/src/App.tsx
The right way of achieving this functionality is modifying the component to accept extra props. This way the css prop passed into the component would be merged with the one within the component.
function Component({prop1, prop2, propN, ...props}) {
// some stuff going on here
return (
<div
css={{
color: blue,
}}
{...props}
>
// some stuff going on here
</div>
)
}
Now you can use additional styles on your component and it will be rendered properly.
function Layout() {
return (
// some other components
<Component css={{marginTop: "1em"}}/>
// some other components
)
}
The side effect of this solution that any additional prop would be passed directly to the HTML element inside the component that takes {...props}.
I want to get rid of the warning on StrictMode for findDOMNode when using react-transition-group but I stumbled upon an issue.
My <Slide> component looks like this:
class Slide extends React.Component {
nodeRef = React.createRef();
render() {
return (
<CSSTransition
in={this.props.in}
timeout={ANIMATION_DURATION}
mountOnEnter={true}
unmountOnExit={true}
classNames={{
enter: "slideEnter",
enterActive: "slideEnterActive",
exit: "slideExit",
exitActive: "slideExitActive"
}}
nodeRef={this.nodeRef}
>
{this.props.children}
</CSSTransition>
);
}
}
It receives a Drawer element as children, the Drawer component looks like this:
class Drawer extends React.Component {
render() {
return (
<div className="drawer">
<button onClick={this.props.onClose}>close me</button>{" "}
<div>This is my drawer</div>
</div>
);
}
}
I cannot wrap the children element with a HTML tag (to attach a ref <div ref={this.nodeRef}>{this.props.children}</div> because it breaks the animation of the content. (I'm using this for children that are different drawers with position absolute)
I've also tried with cloneElement but it still doesn't work (with the code from below it behaves like this: 1. in no animation, 2. out no animation, 3. in animation works but I get the warning findDOMNode so it seems that nodeRef is sent as null, 4. out animation does not work.
const onlyChild = React.Children.only(this.props.children);
const childWithRef = React.cloneElement(onlyChild, {
ref: this.nodeRef;
});
Is there any solution for this situation? Thanks!
The problem is that nodeRef needs to point to a DOM Node, as the name suggests, in your case it points to an instance of the Drawer class. You have two options:
Pass the ref through another prop, e.g. forwardedRef, and in the Drawer class pass that prop to the root element:
React.cloneElement(onlyChild, {
forwardedRef: this.nodeRef,
})
<div ref={this.props.forwardedRef} className="drawer">
Convert Drawer to a function component and use React.forwardRef:
const Drawer = React.forwardRef((props, ref) => {
return (
<div ref={ref} className="drawer">
<button onClick={props.onClose}>close me</button>{" "}
<div>This is my drawer</div>
</div>
);
});
i am learning react at the moment. this is the link with the code - http://redux.js.org/docs/basics/ExampleTodoList.html
I am having a bit of difficulty understanding what's going on in this part of the code
const Link = ({ active, children, onClick }) => {
if (active) {
return <span>{children}</span>
}
return (
<a
href="#"
onClick={e => {
e.preventDefault()
onClick()
}}
>
{children}
</a>
)
}
Link.propTypes = {
active: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func.isRequired
}
I am having the most difficulty understand this snippet
return (
<a
href="#"
onClick={e => {
e.preventDefault()
onClick()
}}
>
{children}
</a>
)
}
What does {children} mean here?
What does it do?
and what does this do?
children: PropTypes.node.isRequired,
what is meant by node in the above line?
When you use a Custom component, like
<MyComponent>Hello World</MyComponent>
Whatever you write between the tags (in above case Hello World) is passed to the component as a children prop.
So when write your component like
const Link = ({ active, children, onClick }) => {
You are destructuring the props and getting only active, children and onClick from the props passed to the component
Consider for example, you call the Link component like
<Link active="true" onClick={someFunc} style={{color: 'orange'}}>Hello</Link>
Then amongst all the props i.e active, onClick, style, children, you will only be accessing active, onClick,children in the component.
For your second question:
and what does this do?
children: PropTypes.node.isRequired,
So here PropTypes is a way of performing a typeCheck on the props that are passed to the component. It is being imported from the react-proptypes package.
So
children: PropTypes.node.isRequired
makes the prop children to be required. So if your render your component like
<Link />
It will not pass the type check and hence you need to do
<Link>Text</Link>
children: PropTypes.node.isRequired,
this is just the type checking of the react proptypes. Refer https://facebook.github.io/react/docs/typechecking-with-proptypes.html for more details how type checking works.
According to the example this says that the prop children is required and is of type node. This type node refers to anything that can be rendered. which is then included within the tag in your rendering.
If you care about the types of your props, you'd need to use React.PropsWithChildren, e.g.
type Props = React.PropsWithChildren<{
name: string; // strongly typed!
myProp: string;
}>;
export function MyComponent({ name, myProp, children }: Props) {
return (
<>
<div>{name}</div>
<div>{myProp}</div>
{children != null && children}
</>
)
}
(this question differs to 'is it possible to use a component inside another', this question is 'can I define a component inside the definition of another', it is not a duplicate of 'Can I write Component inside Component in React?')
Is it possible to define a component inside the definition of another component? This way I can use the props of the outside component in the inner component. It would keep the code more concise. Something like the following...
class AComponent extends Component {
CustomButton = (props) => {
let { disabled, ...otherProps } = props // <-- props of the inner component
const {isDisabled, currentClassName} = this.props // <-- props of the main component
return (
<button
className={className}
disabled={isDisabled}
{...otherProps}>
</button>
)
}
render() {
return (
<div>
<CustomButton>Add something</CustomButton>
<CustomButton>Delete something</CustomButton>
<CustomButton>Edit</CustomButton>
</div>
)
}
}
If the custom button was defined on its own (the usual way of defining components) I would have to do something like below which is ok but more verbose and less dry as I repeat the definition of {...buttonProps} for each component
let buttonProps = {
className: this.props.currentClassName,
disabled: this.props.disabled
}
return (
<div>
<button {...buttonProps}>Add something</button>
<button {...buttonProps}>Delete something</button>
<button {...buttonProps}>Edit</button>
</div>
)
While yes, it's possible to define one function component inside another function component, this is not recommended.
Referring to the ReactJs docs:
reactjs.org/docs/components-and-props
reactjs.org/docs/conditional-rendering
Most, if not all, examples show child components being defined outside of the parent component.
Defining a component inside another will cause the child component to be re-created on mount and unmount of the parent, which could cause unexpected behavior if the child is using props from the parent, and cannot handle if those props are suddenly undefined.
It's best to define components separately.
Yes! I needed to define the function component in the render method...which makes sense
class AComponent extends Component {
render() {
const ButtonLocal = (props) => {
const {isDisabled, currentClassName} = this.props
return (
<Button
disabled={isDisabled}
className={currentClassName}
{...buttonProps}>
</Button>
)
}
return (
<div>
<ButtonLocal>Add something</ButtonLocal>
<ButtonLocal>Delete something</ButtonLocal>
<ButtonLocal>Edit</ButtonLocal>
</div>
)
}
}
Very much possible to add components inside a parent component. Consider following example:
<ParentComponent style={styles}>
<ChildComponent style={styles} {...props} />
</ParentComponent>
I'm creating some button components and I have some custom props that I need and want them checked by flow. But as they are buttons I would also like any other props from the HTML button elements but don't want to type check them all.
Is there any way in react or maybe with an npm package to let me type check my new custom props and let the component receive any other ones? Or maybe just restricted to the HTML defined ones?
You should just be able to pass the rest of the props down without putting type annotations for it.
Example:
import React, { type Node } from 'react'
type Props = {
primary?: boolean,
children: Node
}
function Button({ primary, children, ...props }: Props) {
return (
<button
className={primary ? 'is-primary' : ''}
{...props}
>
{children}
</button>
)
}
export default Button
Usage:
function App() {
return (
<div>
<Button primary onClick={() => console.log('clicked!')}>
Click Me
</Button>
</div>
)
}
You can also check it out on flow.org/try.