How to properly join tailwind css classes using clsx? - reactjs

I am trying to join tailwind classes and apply them to a button using clsx. One class is applied by default 'rounded-none' and the other one is passed in as a prop
const Button = ({
children, ...props
}): JSX.Element => {
return (
<ADButton
className={clsx('rounded-none', props.className)}
{...props}
>
{children}
</ADButton>
);
};
Let's say I have added padding-top: 0px; to the button like shown below
<Button
color="primary"
className="pt-0"
>
{t('btn.add')}
</Button>
The joined className should look like 'rounded-none pt-0'. If no className prop is passed, then just apply ‘rounded-none’
The problem am having right now is ‘rounded-none’ only gets applied to buttons without className prop. On buttons with className prop, only className prop gets applied but not ‘rounded-none’. How can I fix this so that both classes are joined and applied to the button?

You're having this issue because the default prop of ADButton is set to className and the extra prop you're passing to the Button component is set to className as well. In effect, you are overriding the default className with your newly passed className prop. If there are are 2 similarly named props, React will choose the one that is declared later in the event of a conflict.
So this:
<ADButton
className={clsx('rounded-none', 'pt-0')}
// I am declared later so I win
className='pt-0'
>
{children}
</ADButton>
becomes:
<ADButton
className="pt-0"
>
{children}
</ADButton>
Here's one solution:
const Button = ({
children, ...props
}) => {
const { classNameDestructured = "", ...rest } = props;
return (
<ADButton
className={clsx('rounded-none', classNameDestructured)}
{...rest}
>
{children}
</ADButton>
);
};
You destructure props and set a default for classNameDestructured. This allows you to declare Button without additional props:
<Button>
{t('btn.add')}
</Button>
Then you pass classNameDestructured as an argument to your clsx() function. Your additional classes are then joined and applied to the button.
This works because className isn't declared twice as a prop in ADButton anymore so we've eliminated the overriding conflict.

You could use a merge of clsx and twMerge twMerge to efficiently merge Tailwind CSS classes in JS without style conflict.
Be carefull to dont override your previous classes
import clsx, { ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export const clsxm = (...classes: ClassValue[]) => twMerge(clsx(...classes))

Related

Defining Components in React

I am creating a react website in which many pages have a specific button that should look the same for all. Should this button be its own component? If so, how would I specify the onClick events to be different for each button if it is a component?
Yes, it should be its own component.
Create it in a separate file so you can import them where you need to use.
The component should receive a onClick prop that you pass to the internal button tag.
See this example: https://reactjs.org/docs/components-and-props.html#composing-components
export const Button = ({ label, onClick, disabled }) => {
return (
<button
onClick={onClick}
disabled={disabled}
>
{label}
</button>
)
}
and then you can export this component inside any of the components you want, and pass in values like onClick and label to make it more dynamic
export const DemoFunction () => {
const onClickHandler = () => {}
return (
<Button label="Click" onClick={onClickHandler} />
)
}

`Popover` (as React component) with `OverlayTrigger`

I'm trying to create rich React component as popover content.
If I use example with simple const popover (https://react-bootstrap.netlify.app/components/overlays/#examples-1) everything works fine.
Problem
But custom react component fails to position itself. It appears on top left of the screen
const MyPopover = React.forwardRef((props, ref) => {
return <Popover ref={ref} placement={"bottom"}>
<Popover.Header as="h3">
<Form.Control></Form.Control>
</Popover.Header>
<Popover.Body>
<strong>Holy guacamole!</strong> Check this info.
</Popover.Body>
</Popover>
})
const PopoverChooser = ({children, container}) => {
const _refTarget = useRef(null)
return <>
<OverlayTrigger
trigger="click"
placement="bottom"
overlay={<MyPopover ref={_refTarget}/>}
target={_refTarget.current}
>
{children}
</OverlayTrigger>
</>
}
export default PopoverChooser;
As you can see, I'v tried to use ref's, but it's doesn't help.
Question
How can it link popover to target button (in image as dropdown button and in code as {children}).
Or should I position MyPopover manually (by checking button ID and assigning position.. etc.?)
Completely different approach to dig in..?
Your approach to forward the ref was right. What you actually forgot is to also inject props. According to the documentation:
The and components do not position themselves.
Instead the (or ) components, inject ref and
style props.
https://react-bootstrap.netlify.app/components/overlays/#overview
So what you need to do is to spread the props like this:
const MyPopover = React.forwardRef((props, ref) => {
return (
<Popover ref={ref} {...props}>
https://codesandbox.io/s/trusting-sid-0050g9

Using Stateful React classes in typescipt

I am trying to create a Stateful class in which you can call methods such as createHeaderButton() where after calling it would update the state and re-render with these new updates in the component.
Im using Material-UI and so most of their styling utilizes Reacts hook API which of course classes cant use. Ive tried to get around this by using;
export default withStyles(useStyles)(HeaderBar)
Which exports the class separately with the Styles(withStyles(useStyles) useStyles as the defined styles) And the class(HeaderBar). Now the only issue is that i need to access the styles in my class. Ive found a JS example online that wont work for me because of the strong typed syntax of TS. Additionally When initializing my Class component in other places i try to get the ref=(ref:any)=>{} And with that call the create button methods when i get a response from my server, Which doesnt work because of this new way of exporting the class component!
Thanks for the help, Heres my component class: https://pastebin.pl/view/944070c7
And where i try to call it: https://pastebin.com/PVxhKFHJ
My personal opinion is that you should convert HeaderBar to a function component. The reason that it needs to be a class right now is so you can use a ref to call a class method to modify the buttons. But this is not a good design to begin with. Refs should be avoided in cases where you can use props instead. In this case, you can pass down the buttons as a prop. I think the cleanest way to pass them down is by using the special children prop.
Let's create a BarButton component to externalize the rendering of each button. This is basically your this.state.barButtons.forEach callback, but we are moving it outside of the HeaderBar component to keep our code flexible since the button doesn't depend on the HeaderBar (the header bar depends on the buttons).
What is a bar button and what does it need? It needs to have a label text and a callback function which we will call on click. I also allowed it to pass through any valid props of the material-ui Button component. Note that we could have used children instead of label and that's just down to personal preference.
You defined your ButtonState as a callback which takes the HTMLButtonElement as a prop, but none of the buttons shown here use this prop at all. But I did leave this be to keep your options open so that you have the possibility of using the button in the callback if you need it. Using e.currentTarget instead of e.target gets the right type for the element.
import Button, {ButtonProps as MaterialButtonProps} from "#material-ui/core/Button";
type ButtonState = (button: HTMLButtonElement) => void;
type BarButtonProps = {
label: string;
callback: ButtonState;
} & Omit<MaterialButtonProps, 'onClick'>
const BarButton = ({ label, callback, ...props }: BarButtonProps) => {
return (
<Button
color="inherit" // place first so it can be overwritten by props
onClick={(e) => callback(e.currentTarget)}
{...props}
>
{label}
</Button>
);
};
Our HeaderBar becomes a lot simpler. We need to render the home page button, and the rest of the buttons will come from props.childen. If we define the type of HeaderBar as FunctionComponent that includes children in the props (through a PropsWithChildren<T> type which you can also use directly).
Since it's now a function component, we can get the CSS classes from a material-ui hook.
const useStyles = makeStyles({
root: {
flexGrow: 1
},
menuButton: {
marginRight: 0
},
title: {
flexGrow: 1
}
});
const HeaderBar: FunctionComponent = ({ children }) => {
const classes = useStyles();
return (
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<HeaderMenu classes={classes} />
<Typography variant="h6" className={classes.title}>
<BarButton
callback={() => renderModule(<HomePage />)}
style={{ color: "white" }}
label="Sundt Memes"
/>
</Typography>
{children}
</Toolbar>
</AppBar>
</div>
);
};
Nothing up to this point has used state at all, BarButton and HeaderBar are purely for rendering. But we do need to determine whether to display "Log In" or "Log Out" based on the current login state.
I had said in my comment that the buttons would need to be stateful in the Layout component, but in fact we can just use state to store an isLoggedIn boolean flag which we get from the response of AuthVerifier (this could be made into its own hook). We decide which buttons to show based on this isLoggedIn state.
I don't know what this handle prop is all about, so I haven't optimized this at all. If this is tied to renderModule, we could use a state in Layout to store the contents, and pass down a setContents method to be called by the buttons instead of renderModule.
interface LayoutProp {
handle: ReactElement<any, any>;
}
export default function Layout(props: LayoutProp) {
// use a state to respond to an asynchronous response from AuthVerifier
// could start with a third state of null or undefined when we haven't gotten a response yet
const [isLoggedIn, setIsLoggedIn] = useState(false);
// You might want to put this inside a useEffect but I'm not sure when this
// needs to be re-run. On every re-render or just once?
AuthVerifier.verifySession((res) => setIsLoggedIn(res._isAuthenticated));
return (
<div>
<HeaderBar>
{isLoggedIn ? (
<BarButton
label="Log Out"
callback={() => new CookieManager("session").setCookie("")}
/>
) : (
<>
<BarButton
label="Log In"
callback={() => renderModule(<LogInPage />)}
/>
<BarButton
label="Sign Up"
callback={() => renderModule(<SignUpPage />)}
/>
</>
)}
</HeaderBar>
{props.handle}
</div>
);
}
I believe that this rewrite will allow you to use the material-ui styles that you want as well as improving code style, but I haven't actually been able to test it since it relies on so many other pieces of your app. So let me know if you have issues.

Why can't I use the className attribute for some React.Components?

The CSS style class works here:
<span className="cake">Cake</span>
However, this fails (well, no style changes picked up in browser):
<Nav.Link href="/xyz" className="cake">Cake</Nav.Link>
Why does the className atrtibute only work sometimes?
How should you add custom style to <Nav.Link>s, for example right justify only the Logoff one?
Whether that style is picked up by the browser or not depends on how Nav.Link is defined.
For example, if it's defined like this (using styled-components):
const NavLink = styled.div`
/* some styles */
`;
it will work, because all props are passed to div.
However, if it's defined like this:
const NavLink = ({ href, children }) => {
return <a href={href}>children</a>;
};
it will swallow className.
Generally, a good practice is passing all extra props up the hierarchy like this:
const NavLink = ({ href, children, ...rest }) => {
return <a href={href} {...rest}>children</a>;
};
But we never know how it's defined. (You'd better check the definition in node_modules.)

css-loader css modules rules of nested component

Lets say I use scss modules in the following way
A.jsx:
import styles from './A.module.scss';
import B from '../B/B.jsx';
const A = props => {
return <div className={styles.a}>
<B/>
</div>;
};
export default A;
B.jsx:
import './B.scss';
const B = props => {
return <div className="b">
</div>;
};
export default B;
and inside A.module.scss I override B's css rules as follows:
.a {
.b {
width: 500;
height: 500;
background-color: red;
}
}
then the styles I override for the B instance inside A are not passes to it, obviously because it's using css modules. How can I make the css rules of B applies only in A and not globally?
I thought maybe I have to pass custom className props for every component I want to achieve this, so for this instance pass custom className prop to B and pass it like so:
<B className={styles.b}/>
and in B.jsx just apply the custom class to the component.
Not my favourite solution, would rather not adding custom class but use just custom selector .a .b as this defines the relationship of every B which is under A I want to target, but I'm wondering if there's a simpler solution like I'm looking for?
Also I'm not sure the className prop is a viable solution for when B has several nodes nested in it and I'm trying to target a nested node inside it with my css rules. I can't just have for every component I have a className prop for every node in it.
Here's an example illustrating this situation:
B.jsx:
import './B.scss';
const B = props => {
return <div className="b">
<div className"b-inner1">
<div className"b-inner2">
<div className"b-inner3">
</div>
</div>
</div>
</div>;
};
export default B;
Here for example I wish to style the node with the className b-inner3 which might be just a node that is there for helping with the basic layout of the B's component and I do not wish to be able users of this component pass className to this node.
Each component (like any other function) has some abstractions over implementation details, it also has an API, this API is a contract between you and the consumer of your component.
If you are not exposing a className or style prop, that means you are basically saying "this component will not guaranty to always keep the same structure or public class names in the DOM". If anyone will override your classes, he/she are taking a risk and may face a breaking change.
If you do want to give the consumers the ability to override or add styling, you have 2 (or 3) main options as i see it:
Accept style and className props for each node you want to allow
styling:
const MyComponent = (props) => (
<div className={`${styles.myStyle1} ${props.publicStyle1}`}>
<div className={`${styles.myStyle2} ${props.publicStyle2}`}>
<button
className={`${styles.button}
${props.publicStyleButton}`}
>
Click Me
</button>
</div>
</div>
);
Yes, this can get messy and may look like a confusing API to
consume and maintain.
Expose hardcoded class names:
const MyComponent = (props) => (
<div className={`${styles.myStyle1} public-style-1`}>
<div className={`${styles.myStyle2} public-style-2`}>
<button
className={`${styles.button} public-style-button`}
>
Click Me
</button>
</div>
</div>
);
This way, your contract says you will always keep those public class names (or a breaking change version)
Another approach which is not directly related to styling, usually
referred as "Compound Components", you can mix it with one of
the approaches above:
const MyButton = ({ text, onClick, ...rest }) => (
<button
onClick={onClick}
className={`${styles.button}`}
{...rest}
>
{text}
</button>
)
const MyComponent = (props) => (
<div className={`${styles.myStyle1} public-style-1`}>
{children}
</div>
);
MyComponent.Button = MyButton;
/* usage... */
<MyComponent>
<MyComponent.Button
text="Click Me"
className="consumer-class"
style={{color: 'green'}}
/>
</MyComponent>
This will let you break your component into smaller pieces and give
more control to your consumers.

Resources