Adding sx prop to custom component - javascript

I'm using MUI v5, together with Gatsby Image. I'm hoping to keep my styling syntax consistency across the application so I tried to add the sx prop to GatsbyImage.
This is what I've tried:
<Box
component={GatsbyImage}
sx={/* my sx styles */}
/>
This does what I want. Yet, I noticed the sx prop somehow gets passed to the img. This ends up getting a <img sx=[object Object]/> in my HTML.
Although this doesn't really affect my application in anyways, I'm wondering are there better ways to achieve this?

You can use the styled function to add the sx prop to your custom component like this:
import { styled } from "#mui/material/styles";
const ComponentWithSx = styled(YourCustomComponent)();
<ComponentWithSx
sx={{
width: 30,
height: 30,
backgroundColor: "primary.dark"
}}
/>
When you pass the GatsbyImage to the component prop of Box, GatsbyImage is used as the root component inside Box, and the sx object is passed to the DOM element:
function Box(props) {
const { component, ...other /* other includes sx prop */ } = props;
return <component {...other} />;
});
If you want to use Box you need to create a wrapper component to filter the sx prop:
function MyGatsbyImage({ sx, ...props }) {
return <GatsbyImage {...props} />;
}

For anyone trying to style a custom component, I did it this way:
Use the materialUI styled function, like so:
import { styled } from '#mui/material/styles';
import Star from '../components/Star'; //my custom component
[...]
const StarStyled = styled(Star)({ position: 'absolute', right: 5, bottom: 5 });
Then, in your render function, call your newly-styled component:
<StarStyled />
Now, your custom component (in this case, the component "Star") will receive a className prop, so you can use the prop like so:
const Star = ({className}) => {
return (
<span className={className}>
...whatever else you want your component to do
</span>
);
};
export default Star;

Related

Tooltip spacing is messed up when placed inside a parent in a view in mui 5

A little context here:
I have created a custom tooltipIconButton component that has a customIconButton component that receives a bunch of props.
const ToolTipIconButton = (props) => {
const {
placement='top',
toolTipTitle,
isDisabled=false,
...otherProps
} = props;
return (
<Tooltip
arrow
data-testid='testing-stage'
placement={placement}
title={isDisabled ? '' : toolTipTitle}
>
<span>
<CustomIconButton
isDisabled={isDisabled}
{...otherProps}
/>
</span>
</Tooltip>
);
};
now I am using this component in a couple of views some of them appear fine, but some of them appear like this i.e without spacing.
I figured it has something to do with the parent component, cause wherever this tooltipIconButton component has a parent let's say a
<div><tooltipIconButton/></div> or <Grid><tooltipIconButton/></Grid> , the padding is removed.

How to change state of a component when the component is scrolled in view?

Suppose I have a web structure as follows:
<Header />
<Component1 />
<Component2 />
<Component3 />
<Component4 />
Every component has a state as [component1Active, setComponent1Active] = useState(false)
Now, at the start Component1 is in view.
What I want is when component2 is scrolled in view, then I want to setComponent2Active = true and component1 as false.
I tried using useEffect, but it does not work as the states of all components are set as true during loading.
I am using functional components
Please help, any suggestions will be much appreciated.
Thanks in advance
There is a react library on NPM called 'react-in-viewport'
https://github.com/roderickhsiao/react-in-viewport
Here is an example.
import handleViewport from 'react-in-viewport';
const Block = ({ inViewport, forwardedRef } ) => {
const color = inViewport ? '#217ac0' : '#ff9800';
const text = inViewport ? 'In viewport' : 'Not in viewport';
return (
<div className="viewport-block" ref={forwardedRef}>
<h3>{ text }</h3>
<div style={{ width: '400px', height: '300px', background: color }} />
</div>
);
};
const ViewportBlock = handleViewport(Block, /** options: {}, config: {} **/);
const App= () => (
<div>
<div style={{ height: '100vh' }}>
<h2>Scroll down to make component in viewport</h2>
</div>
<ViewportBlock onEnterViewport={() => console.log('enter')} onLeaveViewport={() => console.log('leave')} />
</div>
)
Using const newComponent = handleViewport(<YourComponent/>) you can create any component into one that has the {inViewport, forwardedRef} props, on the containing element of your component set the ref attribute equal to forwardRef then you can use the inViewport prop to determine if it is in view. You can also use the onEnterViewport and onLeaveViewport attributes of the component created by handleViewport() to know the visibility of the component.
You can add the library to your project through either of the cli commands
npm install --save react-in-viewport
yarn add react-in-viewport
You could also do this yourself by accessing the document DOM with a listener on the scroll event and checking scrollHeight of the page and comparing it to the scrollHeights of each component. Using the library makes the implementation much less of a headache.

How to style a nested functional component using styled components

I have a button with the following props - variant, loading, and disabled. Plus, I have a button group that accepts buttons as children and gaps them with 20px. Something like this:
Technically speaking, I have two components here. A <Button /> and a <ButtonGroup />. This would be achievable by writing:
const Button = styled.button`
// css implementation
:disabled {
opacity: 0.5;
}
`;
const ButtonGroup = styled.button`
// css implementation
${Button} + ${Button} {
margin-inline-start: 20px;
// PS - I'm aware I could use the `gap` property, but I'm not specifically talking about this example, but in general.
}
`;
// Usage
<ButtonGroup>
<Button ... />
<Button ... />
</ButtonGroup>
The last thing and the main issue here is to implement the loading state of the button. Or in general, adding extra logic to the styled component. So the "best" way I know of is to create a new functional component and then wrap it inside another styled. Something like this:
// Button.tsx
const StyledButton = styled.buton`...`;
const Button = (props) => {
return (
<StyledButton className={props.className}>
{props.loading && <LoadingSpinner />}
{props.children}
</StyledButton>
);
}
export default styled(Button)``; // It's needed for for nested styling.
...
// ButtonGroup.tsx
const ButtonGroup = styled.button`
// css implementation
${Button} + ${Button} {
margin-inline-start: 20px;
// PS - I'm aware I could use the `gap` property, but I'm not specifically talking about this example, but in general.
}
`;
It will work, of course, but I'm not sure if it's the best way. Currently, as you can see, I did it by calling styled component -> function component -> styled component for the simplest component. I'm not sure how it will scale with my other components, especially naming these components.
So my question is, is there a better, cleaner, simpler way of doing this?
I don't see a reason for three components, a pattern that works for me is using dot notation:
const StyledButton = styled.button``;
const Button = (props) => {
return (
<StyledButton className={props.className}>
{props.loading && <LoadingSpinner />}
{props.children}
</StyledButton>
);
};
Button.Styled = StyledButton;
export default Button;
In this way, you have a pattern where Component.Styled (if available) will always hold the runtime CSS-in-JS object which you can target.
Then in ButtonGroup implementation:
import { Button } from "#components";
// You can target the className
const ButtonGroup = styled.div`
${Button.Styled} { ... }
`;
// You can apply styles
styled(Button)
// You can use the component
<Button />
// Or extend style etc
<OtherButton as={Button.Styled} .../>

Passing React State Between Imported Components

I am trying to pass state from parent to child using React, however both components are imported and therefor the state variables of the parent component are not declared.
I have two components both exported from the same file. The first component is a wrapper for the second. This component has a useEffect function which find its height and width and set these values to hook state.
export const TooltipWrapper = ({ children, ariaLabel, ...props }) => {
const [width, setWidth] = React.useState(0);
const [height, setHeight] = React.useState(0);
const ref = React.useRef(null);
React.useEffect(() => {
if (ref.current && ref.current.getBoundingClientRect().width) {
setWidth(ref.current.getBoundingClientRect().width);
}
if (ref.current && ref.current.getBoundingClientRect().height) {
setHeight(ref.current.getBoundingClientRect().height);
}
});
return <TooltipDiv>{children}</TooltipDiv>;
The next component which is exported from the same file looks like this
export const Tooltip = ({
ariaLabel,
icon,
iconDescription,
text,
modifiers,
wrapperWidth,
}) => {
return (
<TooltipContainer
aria-label={ariaLabel}
width={wrapperWidth}
>
<TooltipArrow data-testid="tooltip-arrow" modifiers={modifiers} />
<TooltipLabel
aria-label={ariaLabel}
>
{text}
</TooltipLabel>
</TooltipContainer>
);
};
The component Tooltip is expecting a prop wrapperWidth. This is where I want to pass in the width hook value from the TooltipWrapper component.
Both components are imported into my App component
import React from "react";
import { GlobalStyle } from "./pattern-library/utils";
import { Tooltip, TooltipWrapper } from "./pattern-library/components/";
function App() {
return (
<div className="App">
<div style={{ padding: "2rem", position: "relative" }}>
<TooltipWrapper>
<button style={{ position: "relative" }}>click </button>
<Tooltip
modifiers={["right"]}
text="changing width"
wrapperWidth={width}
/>
</TooltipWrapper>
</div>
</div>
);
}
Here I am told that width is not defined, which I expect since I'm not declaring width in this file.
Does anyone have an idea of how I can access the width and height state value for the parent component within the App file?
Render Props could work:
Add a renderTooltip prop to <TooltipWrapper>:
<TooltipWrapper renderTooltip={({ width }) => <Tooltip ...existing wrapperWidth={width} />}>
<button style={{ position: 'relative' }}>click</button>
</TooltipWrapper>
NB. ...existing is just the other props you are using with Tooltip
And then update the return of <TooltipWrapper>:
return (
<TooltipDiv>
{children}
props.renderTooltip({ width });
</TooltipDiv>
);

How to write a wrapper around a material UI component using React JS?

I am using Material UI next library and currently I am using List component. Since the library is in beta, lot of its parameter names get changed. To solve this I am planning to write a wrapper around the required components so that things wont break. My list component :
<List dense>
<List className={classes.myListStyles}>
<ListItem disableGutters/>
</List>
</List>
How should I write the wrapper for the List(say myListWrapper) and ListItem so that the wrapper component can handle props and pass them to the actual MUI list component inside?
I had worked on MUI wrappers, writing my own library for a project. The implementation we are focusing, is to pass the props to inner/actual-MUI component from the our wrapper component. with manipulation. In case of wrapping props for abstraction.
Following is my approach to the solution:
import { List as MaterialList } from 'material-ui/List';
import { React } from 'react';
import { ListItem as MaterialListI } from 'material-ui/ListItem';
class List extends MaterialList {
constructor(props){
const propsToPass = {
prop1 : change(props.prop1),
...props
}
super(propsToPass);
}
};
class ListItem extends MaterialListItem {
const propsToPass = {
prop1 : change(props.prop1),
prop2 : change(props.prop2),
...props
}
super(propsToPass);
}
};
class App extends React.Component {
render () {
return (
<List prop='value' >
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
</List>
)
}
};
Above code will allow following things to do with your component:
You can use the props with exact names, as used in Material UI.
You can manipulate/change/transform/reshape you props passed from outside.
If props to you wrapper components are passed with exactly same names as MUI is using, they will directly be sent to the inner component. (... operator.)
You can use Component with exact same name as material is using to avoid confusion.
Code is written according to advance JSX and JavaScript ES6 standards.
You have a space to manipulate your props to pass into the MUI Components.
You can also implement type checking using proptypes.
You can ask for any confusion/query.
You can write it like this:
const MyList = props => (
<List
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</List>
)
const MyListItem = props => (
<ListItem
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</ListItem>
)
Now you need to use MyList and MyListItem, decide the prop names for these component (as per your convenient), and inside these component map those values to actual Material-UI component properties.
Note:
If you are using the same prop names (same name as material-ui component expect) for your component then you can write like this also:
const MyList = ({children, ...rest}) => <div {...rest}>{children}</div>
const MyListItem = ({children, ...rest}) => <p {...rest}>{children}</p>
Check this example:
const A = props => <div>{props.children}</div>
const B = props => <p>{props.children}</p>
ReactDOM.render(
<A>
<A>
<B>Hello</B>
</A>
</A>,
document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app' />

Categories

Resources