emotion style inner component - javascript

I have a base Component without theme and I wish to theme it and its children, is it possible to do with Emotion?
let's I have:
BaseComponent.jsx
const Wrapper = styled('div')``;
const Inner1 = styled('div')``;
const Inner2 = styled('div')``;
export MyBaseComponent = () => (
<Wrapper>
<Inner1 />
<Inner2 />
</Wrapper>
);
ThemedMyComponent.jsx
export ThemeMyComponent = () => {
???
return (
<MyBaseComponent ??? ??? ??? />
)
}
all my theme stuff are in the second component and I would like to complex logic and decorate the BaseComponent only inside ThemedMyComponent.
initial approach would be:
const Wrapper = styled('div')``;
const Inner1 = styled('div')``;
const Inner2 = styled('div')``;
export MyBaseComponent = (props) => (
<Wrapper className={props.wrapperClassName}>
<Inner1 className={props.inner1ClassName}/>
<Inner2 className={props.inner2ClassName}/>
</Wrapper>
);
and pass 3 className to it, which means I have to use withTheme and do a lot of cx, and not mentioning, you can't not longer do this with emotion 10. is there a more functional way to do this?
Doing this, the whole thing I would need is just css from the ClassNames render callback, which make styled unused...

Related

How to get element height and width from ReactNode?

I have a dynamic component in which I pass in children as prop.
So the props look something like:
interface Props {
...some props
children: React.ReactNode
}
export default Layout({...some props, children}: Props) {...}
I need to access the size of the children elements (height and width), in the Layout component. Note that the children are from completely different components and are non-related.
I can use the Layout component as follow:
<Layout ...some props>
<Child1 /> // I need to know the height and width of this child
<Child2 /> // as well as this child
<Child3 /> // and this child.
</Layout>
How can I do so dynamically? Do I somehow have to convert ReactNode to HTMLDivElement? Note that there is no way I can pass in an array of refs as a prop into Layout. Because that the pages which use Layout are dynamically generated.
Since many doesn't really understand what I meant by dynamically generated. It means that the pages which are using the Layout component can pass in x amount of children. The amount of children is unknown but never 0.
You can achieve this by using React.Children to dynamically build up a list of references before rendering the children. If you have access to the children element references, you can follow the below approach. If you don't then you can follow the bit at the bottom.
You have access to the children element references
If the children components pass up their element reference, you can use React.Children to loop through each child and get each element reference. Then use this to perform calculations before the children components are rendered.
i.e. This is a very simple example on how to retrieve the references and use them.
interface LayoutWrapperProps {
onMount: () => void;
}
const LayoutWrapper: React.FC<LayoutWrapperProps> = ({ onMount, children }) => {
React.useEffect(() => {
onMount();
}, [onMount]);
return <>{children}</>;
};
const Layout: React.FC = ({ children }) => {
const references = React.useRef<HTMLElement[]>([]);
React.useEffect(() => {
references.current = [];
});
function getReference(ref: HTMLElement) {
references.current = references.current.filter(Boolean).concat(ref);
}
function getHeights() {
const heights = references.current.map((ref) =>
ref?.getBoundingClientRect()
);
console.log(heights);
}
const clonedChildren = React.Children.map(children, (child) => {
return React.cloneElement(child as any, {
ref: getReference
});
});
return <LayoutWrapper onMount={getHeights}>{clonedChildren}</LayoutWrapper>;
};
If you don't have access to the children element references
If the children components aren't passing up an element as the reference, you'll have to wrap the dynamic children components in a component so we can get an element reference. i.e.
const WrappedComponent = React.forwardRef((props, ref) => {
return (
<div ref={ref}>
{props.children}
</div>
)
});
When rendering the children components, then the code above that gets the references will work:
<Layout>
<WrappedComponent>
<Child1 />
</WrappedComponent>
</Layout>
Since we don't know how your children is built, here is what I can propose you :
import React from 'react';
import { render } from 'react-dom';
const App = () => {
const el1Ref = React.useRef();
const el2Ref = React.useRef();
const [childrenValues, setChildrenValues] = React.useState([]);
React.useEffect(() => {
setChildrenValues([
el1Ref.current.getBoundingClientRect(),
el2Ref.current.getBoundingClientRect()
]);
}, []);
return (
<Parent childrenVals={childrenValues}>
<span ref={el1Ref}>
<Child value="Hello" />
</span>
<span ref={el2Ref}>
<Child value="<div>Hello<br />World</div>" />
</span>
</Parent>
);
};
const Parent = ({ children, childrenVals }) => {
React.useEffect(() => {
console.log('children values from parent = ', childrenVals);
});
return <>{children}</>;
};
const Child = ({ value }) => {
return <div dangerouslySetInnerHTML={{ __html: value }} />;
};
render(<App />, document.getElementById('root'));
And here is the repro on Stackblitz.
The idea is to manipulate how your children is built.

What is the React way of inserting an icon into another component?

I'm trying to create an WithIcon wrapper component which would insert a child (icon) into a wrapped component.
Let's say I have a button:
<Button>Add item</Button>
I want to create a component WithIcon which will be used like this:
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
Ultimately what I want to achieve is this:
<Button className="with-icon"><i className="me-2 bi bi-{icon}"></i>Add item</Button>
Notice the added className and the tag within the Button's body.
I'm trying to figure out how the WithIcon component's code should look like. What is the React way of achieving this result?
The hardest part was the rules of using the WithIcon Will we only have one ?
Will we have only it at the leftmost ? Something like that.
But if we follow your example. We can relatively write something like this for the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
return (
<>
<i className={`me-2 bi bi-${i}`}></i>
{React.cloneElement(child, { className: "with-icon" })}
</>
);
});
};
Then we can just use it the way you want it
<WithIcon i="plus"><Button>Add item</Button></WithIcon>
What we do is just looping through the children which in react is any nested jsx you throw in it (Button in our case)
You can find my fiddle here : https://codesandbox.io/s/react-font-awesome-forked-321tz?file=/src/index.js
UPDATE
So my previous answer does not fully meet the end result we want. The will need to be the main parent
The idea is still quite the same as before but here we are infering the type of the component we passed inside the WithIcon This also adds a safeguard when we passed a nested component inside the WithIcon
const WithIcon = ({ i, children }) => {
return React.Children.map(children, (child) => {
const MyType = child.type; // So we can get the Button
return (
<MyType className="with-icon">
<i className={`me-2 bi bi-${i}`}></i>
{(React.cloneElement(child, {}), [child.props.children])}
</MyType>
);
});
};
I think I'll go to sleep I'll update the rest of the explanation at later date.
See the fiddle here :
https://codesandbox.io/s/react-font-awesome-forked-y43fx?file=/src/components/WithIcon.js
Note that this code does not preserved the other props of the passed component, but you can relatively add that by adding {...child.props} at the MyComponent which is just (reflection like?) of infering the component.
Of course also have another option like HOC Enhancers to do this but that adds a bit of complexity to your how to declare your component api. So Pick whats best for ya buddy
Maybe try using a higher order component?
const withIcon = (icon, Component) => ({children, ...props}) => {
return (
<Component className="with-icon" {...props}>
<i className=`me-2 bi bi-${icon}` />
{children}
</Component>
);
}
Then the usage is
const ButtonWithIcon = withIcon("your-icon", Button);
<ButtonWithIcon>Add Item</ButtonWithIcon>
From my experience with react it usually comes down to either using a property inside the component like here (https://material-ui.com/api/button/) or higher order component like what I described.
There are two common patterns used in React for achieving this kind of composition:
Higher-Order Components
Start by defining a component for your button:
const Button = ({ className, children }) => (
<button className={className}>{children}</button>
);
Then the higher-order component can be implemented like this:
const withIcon = (Component) => ({ i, className = '', children, ...props }) => (
<Component {...props} className={`${className} with-icon`}>
<i className={`me-2 bi bi-${i}`} />
{children}
</Component>
);
Usage:
const ButtonWithIcon = withIcon(Button);
<ButtonWithIcon i="plus">Add Item</ButtonWithIcon>
Context
Start by defining the context provider for the icon:
import { createContext } from 'react';
const Icon = createContext('');
const IconProvider = ({ i, children }) => (
<Icon.Provider value={i}>{children}</Icon.Provider>
);
and then your component:
import { useContext } from 'react';
const Button = ({ className = '', children }) => {
const i = useContext(Icon);
if (i) {
className += ' with-icon';
children = (
<>
<i className={`me-2 bi bi-${i}`} />
{children}
</>
);
}
return (
<button className={className}>{children}</button>
);
};
Usage:
<IconProvider i="plus"><Button>Add Item</Button></IconProvider>

HOC for JSX element - rendering jsx with wrapped element

I want to call a ReactJS HOC to wrap a tooltip around JSX.
The call should be able like this:
withTooltip(JSX, "very nice")
Therefor I have created this function:
import React from "react";
import MUITooltip from "#material-ui/core/Tooltip";
import useStyles from "./index.styles";
const withTooltip = (Component, text: string) => (props) => {
const classes = useStyles();
return (
<MUITooltip className={classes.root} title={text}>
<Component {...props} />
</MUITooltip>
);
};
export default withTooltip;
The call:
import withTooltip from "commons/withTooltip/withTooltip";
const dialogBtn =
isOk &&
withTooltip(
<div className={classes.buttonWithLoader}>
<OpenDialogButton
variant={BaseButtonVariant.Icon}
openDialogAttributes={areas.button.openDialogAttributes}
/>
</div>,
"Very nice",
);
return (
<Fragment>
{dialogBtn}
</Fragment>
);
It says:
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it
How to solve it ?
Your HOC accepts a Component argument while you are passing in JSX. Try wrapping the JSX with a function or pass in a component which renders the Button.
However, in your case, you probably want to have control over the toolTip text in your component. If this is the case, I would not use a HOC for this, but rather a wrapping Component.
function WithTooltip({ classes, text, children }) {
return (
<MUITooltip className={classes.root} title={text}>
{children}
</MUITooltip>
);
}
export default WithTooltip;
const dialogBtn = isOk && (
<WithTooltip text="Very nice">
<div className={classes.buttonWithLoader}>
<OpenDialogButton
variant={BaseButtonVariant.Icon}
openDialogAttributes={areas.button.openDialogAttributes}
/>
</div>
</WithTooltip>
);

Clone and rename a component

I have a component that is used often and for many porpuses - <Box />.
I'd like to rename it to declare it's functional meaning in the code, something along these lines:
import Box from '../reusable-components/Box'
const Wrapper = Box
const Card = Box
const TopSection = Box
// Or like this?
// const [Wrapper, Card, TopSection] = cloneComponent(Box)
function MyComponent() {
return (
<Wrapper>
<TopSection style={{display: 'flex'}}>
<Card>Item A</Card>
<Card>Item B</Card>
<Card>Item C</Card>
</TopSection>
</Wrapper>
)
}
If its a react component, there is no point "cloning" it. Since When you use it in , it will create a new instance. In fact I am not clear why you need to rename it at all, but here is how you can do it
import Wrapper from '../reusable-components/Box'
import Card from '../reusable-components/Box'
import TopSection from '../reusable-components/Box'
//Or
const [Wrapper, Card, TopSection] = [Box,Box,Box]
function MyComponent() {
return (
<Wrapper>
<TopSection style={{display: 'flex'}}>
<Card>Item A</Card>
<Card>Item B</Card>
<Card>Item C</Card>
</TopSection>
</Wrapper>
)
}

How to get a specific child in a React component?

In the simple example below, I'm looking for a way to get for example CardBody child.
const Card = ({children}) => {
const cardHeadChild = ...;
const cardBodyChild = ...;
return (
<div>
{cardHeadChild}
{cardBodyChild}
</div>
)
}
const CardHead = () => <div>Head</div>
const CardBody = () => <div>Body</div>
// Usage:
<Card>
<CardHead>
<CardBody>
</Card>
I cannot get by index (eg: React.Children.toArray(children)[1]) because children are optionals.
I tried something like this:
React.Children.forEach(children, child => {
if(child.type.name === 'CardBody') cardBodyChild = child
// or
if(child.type.displayName === 'CardBody') cardBodyChild = child
..
})
but it doesn't work when component are wrapped in HOC.
Any solution ?
Function name shouldn't be used in production client-side code because function names are mangled when the application is minified. The same applies to displayName - unless it was set explicitly. Also notice that primary use of displayName and name is debugging.
Children can be identified by React element type. If the purpose is to output optional children in specified order, this can be done similarly to this answer:
Optional head:
{props.children.find(({ type }) => type === CardHead)}
Optional body:
{props.children.find(({ type }) => type === CardBody)}
It's expected that children should be exactly CardHead and CardBody stateless components. If there's a need to enhance their functionality with other components, CardHead and CardBody should wrap these components:
const CardHead = props => <div>
Head
{props.children}
</div>
...
<Card>
<CardHead><SomeComponent/></CardHead>
</Card>

Categories

Resources