Official ReactJs documentation recommends to create components following the dot notation like the React-bootstrap library:
<Card>
<Card.Body>
<Card.Title>Card Title</Card.Title>
<Card.Text>
Some quick example text to build on the card title and make up the bulk of
the card's content.
</Card.Text>
</Card.Body>
</Card>
It is very easy to create this structure with the help of a class component:
const CardBody = ({ children }) => <div className='body'>{children}</div>;
class Card extends Component {
static Body = CardBody;
render() {
return (
<div className='card'>{this.props.children}</div>
);
}
}
But it's also recommended to use as much as possible functional component. Unfortunately I don't know how to achieve this using only functional component.
If I follow this way, I'm no more able to use Card as a component because he is now an object of components:
const Card = {
Component: CardComponent,
Body: CardBody
}
export default Card
I'd have to use it that way, and it's not really what I want:
<Card.Component>
<Card.Body>
...
Do you have any idea how to do that?
In function component you can do like so:
// Card.react.js
const Card = ({ children }) => <>{children}</>;
const Body = () => <>Body</>;
Card.Body = Body;
export default Card;
// Usage
import Card from "./Card.react.js";
const App = () => (
<Card>
<Card.Body />
</Card>
);
Or, you can exploit named exports:
// Card.react.js
export const Wrapper = ({ children }) => <>{children}</>;
export const Body = () => <>Body</>;
// Usage
import * as Card from "./Card.react.js";
const App = () => (
<Card.Wrapper>
<Card.Body />
</Card.Wrapper>
);
For functional components
const CardBody = ({ children }) => <div className='body'>{children}</div>;
const Card = (props) => (
<div className='card'>{props.children}</div>
);
Card.Body = CardBody
And then use it like
<Card>
<Card.Body>
....
Related
I have the following code `
import { useState, useEffect } from 'react'
import LayoutContent from './layout_content';
type Props = {
children: JSX.Element | JSX.Element[]
}
const Layout = ({ children }: Props) => {
const [selected, setSelected] = useState(countries[0]);
const country= selected.id
return (
<>
<Sidebar onClick={toggle} sidebar={open} />
<LayoutContent sidebar={open} countriesWithsrc ={countriesWithsrc} selected={selected} lected={setSelected} >
{children}
</LayoutContent>
</>
)
}
export default Layout;
`
How do I pass the variable country from the Layout component to the children without state management ?.I.e I want to drill it.
If you don't want any state management you can use React.Children. It provides utilities to work with the children prop. React.Children.map will run a method for every immediate child of the component. You can use cloneElement along with that to create a clone of your element by passing in extra properties. Infact you can even modify the children of an element you are cloning, but that is not the ask here.
Do note that context would be the better way to do it.
const Layout = ({ children }: Props) => {
....
....
const modifiedChildren = React.Children.map(children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { testProp : 'test' });
}
return child;
});
....
....
return (
<>
<Sidebar onClick={toggle} sidebar={open} />
<LayoutContent sidebar={open} countriesWithsrc ={countriesWithsrc} selected={selected} lected={setSelected} >
{modifiedChildren}
</LayoutContent>
</>
)
}
Hey I use bootstrap with React and I try figure out, how I can extend my component by passing className props deeper. In my atom component I have two files. First one with component declaration.
Breadcrumb.js
export const Breadcrumb = (props) => {
const { className } = props;
const classes = getClasses(className);
return (
<Link to={props.path} className={classes} {...props}>
{props.children}
</Link>
);
};
and another one with getClasses() which returns all default BS classes.
Breadcrumb.style.js
export const getClasses = (extra = "") => {
const defaultClasses = getDefaultClasses();
const addingClasses = extra;
const classes = `${defaultClasses} ${addingClasses}`;
return classes;
};
const getDefaultClasses = () => `ps-3 fs-3 fw-bold text-decoration-none`;
What I want to achieve is, when I'll invoke my Breadcrumb component, and I'll decied to extend it on extra classes I can do that by pass className props...like
TopBar.js
export const TopBar = () => {
const breadcrumbs = useBreadcrumbs(routes, { disableDefaults: true });
const classes = getClasses();
return (
<div className={classes}>
{breadcrumbs.map(({ match, breadcrumb }) => (
<Breadcrumb
path={match.pathname}
children={breadcrumb}
className="cs_breadcrumb"
key={uuidv4()}
/>
))}
</div>
);
};
But when I do that, my declare Breadcrumb className is override by invoke Breadcrumb className... Although in Breadcrumb.js console.log(classes) returns concated classes.
Anyone knows how to achieve that or has any tips ?? I'll be glad
Change
export const Breadcrumb = (props) => {
const { className } = props;
const classes = getClasses(className);
return (
<Link to={props.path} className={classes} {...props}>
{props.children}
</Link>
);
};
to
export const Breadcrumb = ({ className, ...rest }) => {
const classes = getClasses(className);
return (
<Link to={props.path} className={classes} {...rest}>
{props.children}
</Link>
);
};
So, you need to extract the className prop in the place where props was, and also add ...rest for the rest props.
I guess you want to extend component classes with other classes passed via props.
If I understand correctly, you can try like this:
export const Breadcrumb = (props) => {
const { className } = props;
const classes = getClasses(className);
return (
<Link to={props.path} className={[classes, className].join(" ")]}
{...props}>
{props.children}
</Link>
);
};
I am gathering posts (called latestFeed) from my backend with an API call. These posts are all mapped to components and have comments. The comments need to be opened and closed independently of each other. I'm governing this mechanic by assigning a piece of state called showComment to each comment. showComment is generated at the parent level as dictated by the Rules of Hooks.
Here is the parent component.
import React, { useState, useEffect } from "react";
import { getLatestFeed } from "../services/axios";
import Child from "./Child";
const Parent= () => {
const [latestFeed, setLatestFeed] = useState("loading");
const [showComment, setShowComment] = useState(false);
useEffect(async () => {
const newLatestFeed = await getLatestFeed(page);
setLatestFeed(newLatestFeed);
}, []);
const handleComment = () => {
showComment ? setShowComment(false) : setShowComment(true);
};
return (
<div className="dashboardWrapper">
<Child posts={latestFeed} showComment={showComment} handleComment={handleComment} />
</div>
);
};
export default Parent;
latestFeed is constructed along with showComment. After latestFeed comes back with an array of posts in the useEffect hook, it is passed to the child show here:
import React, { useState } from "react";
const RenderText = ({ post, showComment, handleComment }) => {
return (
<div key={post._id} className="postWrapper">
<p>{post.title}</p>
<p>{post.body}</p>
<Comments id={post._id} showComment={showComment} handleComment={() => handleComment(post)} />
</div>
);
};
const Child = ({ posts, showComment, handleComment }) => {
return (
<div>
{posts.map((post) => {
<RenderPosts posts={posts} showComment={showComment} handleComment={handleComment} />;
})}
</div>
);
};
export default Child;
However, whenever I trigger handleComments, all comments open for all posts. I'd like them to be only the comment that was clicked.
Thanks!
You're attempting to use a single state where you claim you want multiple independent states. Define the state directly where you need it.
In order to do that, remove
const [showComment, setShowComment] = useState(false);
const handleComment = () => {
showComment ? setShowComment(false) : setShowComment(true);
};
from Parent, remove the showComment and handleComment props from Child and RenderText, then add
const [showComment, handleComment] = useReducer(state => !state, false);
to RenderText.
I faced with some problem. I have heavy JSX Element with multipe states. In another part of app I need to pass this Element to Modal window with keeping all states. What is the best solution for solving this problem? Of course I can make Parent with all states and pass it to Child. But maybe it's possible to freeze all states and pass JSX Element as independent component?
Structure will look like:
ParentElement
|_
AnotherElement
|_
SomeHeavyElement
ParentElement:
const ParentElement= () => {
return (
<React.Fragment>
<AnotherElement />
<SomeHeavyElement />
</React.Fragment>
);
};
export default ParentElement;
AnotherElement:
const AnotherElement= () => {
return (
<React.Fragment>
<dialog>
<SomeHeavyElement/>
</dialog>
</React.Fragment>
);
};
export default AnotherElement;
SomeHeavyElement
const SomeHeavyElement= () => {
const [state1, setState1] = useState(true);
...
const [state99, setState99] = useState(false);
return (
<React.Fragment>
{/*some logic*/}
</React.Fragment>
);
};
export default SomeHeavyElement;
You have to lift state up, meaning you should define your state on top of both component (in <ParentElement>). You can't really freeze your component internal state.
Here is a minimal example:
const ParentElement= () => {
const [state1, setState1] = useState(true);
// ...
const [state99, setState99] = useState(false);
return (
<React.Fragment>
<AnotherElement state={{state1, state99}} />
<SomeHeavyElement state={{state1, state99}} />
</React.Fragment>
);
};
export default ParentElement;
const SomeHeavyElement= ({state}) => {
return (
<React.Fragment>
{/*some logic*/}
</React.Fragment>
);
};
export default SomeHeavyElement;
const AnotherElement= ({state}) => {
return (
<React.Fragment>
<dialog>
<SomeHeavyElement state={state} />
</dialog>
</React.Fragment>
);
};
export default AnotherElement;
Also, when you have a lot of useState defined, you could useReducer to centralize your component state. Also, if you want to avoid props drilling, you could define handle your state using React API context.
I have this simple Higher-Order Component that works fine but it only accepts a single component:
let VerticalSlider = (Komponent) => {
return (props) => (
<div className='slider'>
<Komponent {...props}/>
</div>
);
};
How would one rewrite the HOC to accept multiple components (unknown number) and return them as siblings (one by one under eachother) together with their respective props?
I assume this is how you'd call a HOC with multiple components as it's just a function:
VerticalSlider(MyComponent, MyOtherComponent)
I know how to accept and destruct an unknown number of "normal" props, but I'm a but lost here when it comes to passed in components.
You can do this:
import React, { Component } from 'react';
import { render } from 'react-dom';
let VerticalSlider = (...komponents) => {
return (props) => (
<div className='slider'>
{ komponents.map((K, i) => <K {...props.ps[i]} /> ) }
</div>
);
};
const Apple = props =>
<div>{ props.name }</div>;
const Orange = props =>
<div>{ props.desc }</div>;
const MyComp = VerticalSlider(Apple, Orange);
const App = () =>
<div>
Hello
<MyComp
ps={[{ name: 'apple' }, { desc: 'orange' }]} />
</div>
render(<App />, document.getElementById('root'));
But it's a little ugly and your coworkers will hate you. Keep it simple and don't overthink it. Here is a demo