I have the following component which map over an array and display a set of buttons which they render specific content:
export const Bookings = ({bookings}) => {
const [selectedBooking, setSelectedBooking] = useState(false);
const handleSelectedBooking = (id, destination) => {}
const handleToggleButton = () => {
setSelectedBooking(!selectedBooking)
}
return(
<div>
{
bookings.map(booking => (
<button
className={selectedBooking ? 'selectedBooking' : 'notSelectedBooking'}
onClick={() => {
handleSelectedBooking(booking.id, booking.destination)
handleToggleButton()
}}
>
{booking.destination}
</button>
))
}
</div>
)
}
Where I have these styles already defined but somehow the styles are not applied, did I miss anything?
You have a typo in your ternary operator. It should be selectedBooking instead of selectedBoking.
Did you import styles file in the current file, where you write your component?
Moreover, I recommend you using modules to set styles in your component. How this works: you name your css file like this MyStyles.module.css (.module is obligatory) and then import in React component file these styles like import styles from './MyStyles.module.css'. Then you set styles in jsx code like this: className={selectedBoking ? styles.selectedBooking : styles.notSelectedBooking}.
This approach makes classnames unique across all the project even though they have the same names in different css files.
Related
What is the best way to achieve this behavior along with React + TypeScript?
import { Button, Card } from 'src/components';
const Page = () => (
<div>
<Card mx3 p3 flex justifyContentEnd>
/* Card content */
</Card>
<Button my2 mx3>
Login
</Button>
</div>
);
For instance, mx3 will add 16px margin horizontally, my2 will add 8px margin vertically, etc., similar to how the Bootstrap framework uses classes to apply utility styles easily.
I have looked through a few component libraries with this sort of behavior in order to find a suitable solution; however, I find most do not have strong typing support. Examples are RNUILib, NativeBase, Magnus UI, etc.
You can declare your props like that:
const styles = ['mx3', 'p3', 'flex'] as const
type Styles = Record<typeof styles[number], boolean>;
Then use them like this:
type CardProps = Styles & {
other: 'props here'
}
Now, this should work:
<Card mx3 p3 flex />
You can get applied props like this:
const values = Object.entries(props).map(([key, value]) => value ? key : null).filter(Boolean)
If you see the source code of react-bootstrap, they have mapped the boolean to some CSS class using a util function of classnames package. You can do the same:
...
...
<Component
{...buttonProps}
{...props}
ref={ref}
className={classNames(
className,
prefix,
active && 'active',
variant && `${prefix}-${variant}`,
size && `${prefix}-${size}`,
props.href && props.disabled && 'disabled',
)}
/>
...
...
I use React + styled and my question is about the following code
<MobileButton
onClick={props.handleMobileDropdownElementClicked}
padding={isSmallDevice ? 1 : 0}
>
Name
</MobileButton>
Is there a way to generalise this component to write like this without having props and onclick()? like the following code.
<MobileButton>
Name
</MobileButton>
I was just curious if there is things in react or styled component that could do that if we are repeating the same component with the same props/onclick function.
Thank you
Sure, you can make a new component that doesn't require said props. But you'll still have to pass the props when creating that component initially.
For example:
const YourComponent = (props) => {
const NewMobileButton = (newProps) => {
return (
<MobileButton
onClick={props.handleMobileDropdownElementClicked}
padding={isSmallDevice ? 1 : 0}
>
{newProps.children}
</MobileButton>
)
}
return (
<>
<NewMobileButton>1</NewMobileButton>
<NewMobileButton>2</NewMobileButton>
</>
)
}
Problem
Using React-Markdown I can fully use my custom built components. But this is with specific pre-built keywords in the markdown. Like paragraph or images. That works PERFECTLY. But the problem is that these seem to all be pre-built words/conditions like paragraphs, headers, or images.
I can't find a way to add something new key word in my markdown like "CustomComponent" to be used. That's all I need at this point ><
This works just fine for me to make the markdown's image into a custom "footer" component I made elsewhere. I know it's ridiculous but it works. But I have no idea how to make this renderer accept/create a new keyword like "emoji" or "customComponent" or "somethingSilly".
let body =
`![Fullstack React](https://dzxbosgk90qga.cloudfront.net/fit-in/504x658/n/20190131015240478_fullstack-react-cover-medium%402x.png)`;
const renderers = {
image: () => <Footer/>
};
<ReactMarkdown source={body} renderers={renderers} />;
Some past work I did:
Some documentation:
https://reposhub.com/react/miscellaneous/rexxars-react-markdown.html
https://github.com/rexxars/commonmark-react-renderer/blob/master/src/commonmark-react-renderer.js#L50
Examples:
https://codesandbox.io/s/react-markdown-with-custom-renderers-961l3?from-embed=&file=/src/App.js
But nothing indicates how I can use "CustomComponent" to indicate to inject a custom component.
Use Case / Background
I'm trying to retrieve an article from my database that is formatted like so in markdown (basically a giant string). I'm using regular react with typescript and redux-- this is the only portion of my application that needs this.
"
# Title
## Here is a subtitle
Some text
<CustomComponentIMade/>
Even more text after.
<CustomComponentIMade/>
"
I know its most likely a little late for your purposes, but I've managed to solve this issue using a custom remark component.
Essentially you'll need to use the remark-directive plugin as well as a small custom remark plugin (I got this plugin straight from the remark-directive docs)
Then in react markdown you can specify the plugins, custom renderers and custom tags for eg.
import React from 'react'
import ReactMarkdown from 'react-markdown'
import {render} from 'react-dom'
import directive from 'remark-directive'
import { MyCustomComponent } from './MyCustomComponent'
import { visit } from "unist-util-visit"
import { h } from "hastscript/html.js"
// react markdown components list
const components = {
image: () => <Footer/>,
myTag: MyCustomComponent
}
// remark plugin to add a custom tag to the AST
function htmlDirectives() {
return transform
function transform(tree) {
visit(tree, ['textDirective', 'leafDirective', 'containerDirective'], ondirective)
}
function ondirective(node) {
var data = node.data || (node.data = {})
var hast = h(node.name, node.attributes)
data.hName = hast.tagname
data.hProperties = hast.properties
}
}
render(
<ReactMarkdown components={components} remarkPlugins={[directive, htmlDirectives]}>
Some markdown with a :myTag[custom directive]{title="My custom tag"}
</ReactMarkdown>,
document.body
)
So in your markdown wherever you have something like :myTag[...]{...attributes} you should render the MyCustomComponent with attributes as props.
Sorry I haven't tested the code, but hopefully it communicates the gist of things, if you need a working example let me know and I'll do my best to set one up.
I have tried this way and it worked for me
import CustomNextImage from '#commonComponentsDependent/CustomNextImage';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { FC } from 'react';
import ReactMarkdown from 'react-markdown';
import { Options } from 'react-markdown/lib/ast-to-react';
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
import remarkGfm from 'remark-gfm';
const SyntaxHighlighterDynamic = dynamic(() => import('./SyntaxHighlighter'));
import classes from './styles.module.css';
interface Props {
content: string;
}
type TCustomComponents = Options['components'];
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
const MdToHTML: FC<Props> = ({ content }) => {
const customComponents: TCustomComponents = {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
img(image) {
if (!image.src) return <></>;
return (
<div className={classes['img-container']}>
<CustomNextImage
src={image.src}
alt={image.alt}
/>
</div>
);
},
a({ href, children, node }) {
if (!href) return <></>;
if (
href.startsWith('/') ||
href.startsWith('https://lognmaze.com')
) {
return (
<Link href={href} prefetch={false} passHref>
<a>{children}</a>
</Link>
);
}
return (
<a
href={href}
target='_blank'
rel='noopener noreferrer'
>
{children}
</a>
);
},
code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighterDynamic language={match[1]} PreTag='div' {...props}>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighterDynamic>
) : (
<code className={className} {...props} data-code-inline='true'>
{children}
</code>
);
},
};
return (
<ReactMarkdown
components={customComponents}
remarkPlugins={[remarkGfm]}
>
{content}
</ReactMarkdown>
);
};
export default MdToHTML;
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>
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} .../>