Making whole card clickable in Reactstrap - javascript

I'm trying to create a card that, when clicked, performs an action.
I've managed to make this work by adding a button to the card, which is bound to an event handler, and works as expected.
I'm trying to get the whole card to work with the same event handler, as opposed to using the button, but I can't seem to get this to work as I would expect.
const SiteCard = props => {
const { site, siteSelectedCallback } = props;
return (
<Card onClick={siteSelectedCallback} className="card-item">
<CardBody>
<CardTitle>{site.name}</CardTitle>
<CardText className="text-muted">{site.address}</CardText>
<Button color="primary" className="float-right" value={site.id}>
CHOOSE ME
</Button>
</CardBody>
</Card>
);
};
I've tried wrapping it in an <a> tag, but that also doesn't work.
With the example, I'd expect the card to be clickable, but actually the button still works with the event handler. I've also tried removing the button, but that doesn't make the card clickable.

Note that adding onClick on the Card component is enough to make it clickable. Changing the cursor through style makes it more obvious to the user.
<Card onClick={onClick} style={{ cursor: "pointer" }}>
<CardBody>This is a clickable card.</CardBody>
</Card>
Wrapping the card with an a tag will also work, though, it won't have the pointer cursor without a href which can be changed easily with CSS.
const SiteCard = ({ site, siteSelectedCallback }) => (
<a style={{ cursor: 'pointer' }} onClick={siteSelectedCallback}>
<Card className="card-item">
<CardBody>
<CardTitle>{site.name}</CardTitle>
<CardText className="text-muted">{site.address}</CardText>
</CardBody>
</Card>
</a>
);
Tested it just now with a console.log, so if that doesn't work, it's because the callback isn't working as you're expecting it to.
Another way would be to make the Card an a tag by passing a tag prop.
<Card tag="a" onClick={siteSelectedCallback} style={{ cursor: "pointer" }}>
All the options available are clearly defined in the source of the reactstrap's Card component.
I also tested with a button inside the Card without any problems.

In case anyone arrives here for the same question, but with react-bootstrap's Card, the solution is very similar. However, instead of using the tag property, you need to use as.
<Card as="a" onClick={siteSelectedCallback} style={{ cursor: "pointer" }}>

Related

toggle contentEditable on and off inside React function?

I have a react function that puts out a div. This div is a draggable, and it should also be editable. I have tried using contentEditable, which makes the div editable when set to 'true', but then i can no longer drag the item, only edit it.
Is there a way to make the div "editability" to toggle on and off, for example using an onclick to turn contentEditable to 'on', and a doubleClick to turn contentEditable to 'true', enabling the dragging?
return (
<div
onDoubleClick contentEditable={true} // needs to be toggleable, in some way
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
{item.content};
</div>
)
This is how you would do it with hooks. I assume you are using them.
const [contentEditable, setContentEditable] = React.useState(false)
return (
<div
onDoubleClick={()=> setContentEditable(!contentEditable)}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
{item.content};
</div>
)

prevent remount when component is wrapped in parent

I have a component that may or may not be rendered in a wrapper component:
class Video extends Component {
state = { isFullscreen: false }
render () {
const { isFullscreen } = this.state
return (
<View>
{isFullscreen ? (
<Modal>
<VideoView />
</Modal>
) : (
<VideoView />
)}
<Button title='inline' onPress={() => this.setState({ isFullscreen: false })} />
<Button title='fullscreen' onPress={() => this.setState({ isFullscreen: true })} />
</View>
)
}
}
Every time I press inline or fullscreen, the <VideoView /> remounts. This makes it hard to add initialization logic to the componentWillMount method. I can do other checks in my application to make sure it works well, but it feels better to re-use the component that already exists.
Is there a way to recycle the <VideoView />?
Ps My project is in React Native, so I used some syntax / component from RN, but I assume the question goes for normal React projects as well
Try wrapping the component with a wrapper and now toggle the styles for this wrapper based on state change. I guess that should work.
Also if you are imminent on reusing the modal component only, what you can do is define some CSS under a new className on the modal Component that alters the required styling to give an effect that the modal is toggled. Then add remove the className based on the state change.

react-select dropdown opens inside modal

I have a custom modal where I have 2 react-select components inside. The modal body is ready to auto scroll in case the content exceeds its size, but the react-select components dropdown open inside the modal with this overflow, which is what i am not looking for. Without overflow, it works fine.
I'm working with CSS Modules.
<div className={styles.modalBody}>
{this.props.children}
</div>
.modalBody {
padding: padding;
flex: 1 1 auto;
height: 45vh;
max-height: 100%;
overflow: auto;
}
<Select
id={this.props.id}
className={styles[this.props.selectType ? this.props.selectType : 'selectWhite']}
classNamePrefix="select"
name={this.props.name}
value={selectedOption ? selectedOption : this.props.value}
placeholder={this.props.placeholder}
onChange={this.onChange}
options={this.props.options}
isDisabled={this.props.disabled}
isSearchable={false}/>
How can I fix this?
Thank you! :)
You want to look at the menuPortalTarget prop. There's a topic on this in the Advanced documentation, specifically with a modal example provided. Something like:
<Select
{...otherProps}
menuPortalTarget={document.body} />
You can use Menu Position as fixed by setting the prop,which in turn makes your dropdown as position fixed as
<Select menuPosition="fixed" />
A full example of how to display the select menu in a modal (I added comments at important lines and a step-by-step explanation below):
import React, { useRef } from 'react';
import Select from 'react-select';
import { Dialog } from '#mui/material';
const MyModal: React.FC = () => {
const ref = useRef<HTMLDivElement>(null);
return (
<Dialog
// ...
ref={ref} // stores a ref to the DOM node of the modal component
>
<Select
// ...
menuPortalTarget={ref.current} // pass the ref to `Select` to portal the select menu to the given DOM node
styles={{
menuPortal: defaultStyles => ({
...defaultStyles,
paddingBottom: '10px', // style the menu when it's portalled into the DOM node given to `menuPortalTarget`
}),
}}
/>
</Dialog>
);
};
export default MyModal;
Step-by-step explanation:
I create a ref with the React.useRef hook.
I'm getting a reference of the Dialog component (which in this example is a MUI dialog component (but could be any other Modal component) via ref={ref}
I pass that ref to react-select's Select component via menuPortalTarget={ref.current}.
I had to customize the placement of the menu via paddingBottom: '10px', because the menu would appear a bit too high in my case. You might have to adjust this differently.
Further comments:
document.body didn't do the trick for me. The menu appeared behind the modal for me in this case.
Unfortunately the react-select portaling docs do not contain any example code, so I hope this example is helpful to you.

Conditionally activate Material UI tooltip?

I have the following React component using Material UI:
const MyButton = ({ warningText }) => (
<Tooltip title={warningText}>
<Button>Do action</Button>
</Tooltip>
)
Currently, this shows an empty tooltip when warningText is undefined. Instead I would like to show no tooltip at all. Is there a way to conditionally surpress the tooltip in these cases?
Off course I could just use an if statement to not render the tooltip component, but this would lead to rather ugly code in my opinion.
Should be
<Tooltip title={warningText == null ? "" : warningText}>
<Button>Do action</Button>
</Tooltip>
the docs say that it won't be displayed if the string length is zero.
https://material-ui.com/api/tooltip/
Tooltip title. Zero-length titles string are never displayed.
If you're looking to manually play around for customization, you can try to use the following solution:
As per the documentation, you can use the open prop and mouse events to handle it manually.
In the following scenario, we will use state to set showing the tooltip when we enter the mouse over the element, and we will also use text && to assert that text has a value, this will prevent the tooltip from showing when text is undefined.
const [showTooltip, setShowTooltip] = useState(false);
<Tooltip
open={text && showTooltip}
onMouseEnter={() => { setShowTooltip(true) }}
onMouseLeave={() => { setShowTooltip(false) }}
placement="top" title={text}
>
<div>
{text}
</div>
</Tooltip>
Note, the mui-tooltip is not a perfect component to begin with and is not very straight forward, this solution works for me but might not work in your situation as is, I will try to put it out, you can try to make it work on your end.
If it doesn't work for you, please leave a comment and I'll try to help.
You should take a look at https://material-ui.com/api/tooltip/
There are options like
disableFocusListener
disableHoverListener
disableTouchListener
interactive
I think interactive={true} should fit your needs best
<Tooltip title={warningText} interactive={!warningText}>...</Tooltip>

How to add style - like margin - to react component?

So, expect two simple components that I have built:
import {Input} from 'semantic-ui-react';
import {Select} from 'semantic-ui-react';
const CategoriesDropdown = ({categories, onCategorySelected, selectedCategory}) => {
const handleChange = (e, {value})=>{
onCategorySelected(value);
};
return (
<Select placeholder="Select category" search options={categories} onChange={handleChange} value={selectedCategory} />
);
};
const IdentifiersInput = ({identifiers, onIdentifiersChanged}) => {
return (
<Input placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Nothing fancy so far.
But now, I am building another component that displays those two in a flexbox row:
<Box>
<CategoriesDropdown categories={categories} selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}/>
<IdentifiersInput identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
</Box>
Unfortunately they are both displayed right next to each other without any margin in between.
Usually, I would just add a margin-left style to the second element, but because it is a React component, that doesn't work. Using style={{marginLeft: '20px'}} doesn't work as well, because the IdentifiersInput component doesn't use it.
I know that I can fix it by doing this: <Input style={style} ... inside the IdentifiersInput component.
However, this seems to be a very tedious way of achieving this goal. Basically, I have to add this to every single component I am writing.
I clearly must be missing something here. How am I supposed to apply such layout CSS properties to React components?
I think I understand.
1) Applying CSS directly to React Components does not work--I can confirm that.
2) Passing props down to the low level elements is tedious, confirmed but viable.
Notice hasMargin prop:
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
hasMargin
/>
</Box>
Possible input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
return (
<Input
className={className}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
style={hasMargin ? ({ marginLeft: '0.8rem' }) : ({})}
/>
);
};
NOTE: I do not like style as much as I like adding an additional class because classes can be adjusted via media queries:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className, hasMargin }) => {
const inputPosition = hasMargin ? `${className} margin-sm` : className
return (
<Input
className={inputPosition}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
};
If you find inputPosition too verbose as shown above:
className={hasMargin ? `${className} margin-sm` : className}
3) You could accomplish it using a divider Component, sacreligious yet rapidly effective
<Box>
<CategoriesDropdown
categories={categories}
selectedCategory={selectedCategoryId}
onCategorySelected={this.selectCategory}
/>
<div className="divider" />
<IdentifiersInput
identifiers={identifiers}
onIdentifiersChanged={this.changeIdentifiers}
/>
</Box>
You can use media queries and control padding at any breakpoints if desired.
4) CSS pseudo-elements or pseudo-classes, I don't see any mention of them in answers so far.
MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes
CSS Tricks: https://css-tricks.com/pseudo-class-selectors/
Usually, when you have a random collection of DOM elements, you can calculate a way using CSS to wrangle them into the correct position. The list of available pseudo-classes is in that MDN link. It honestly helps to just look at them and reason about potential combinations.
My current issue is I don't know what is in <Box /> other than it probably has a div with display: flex; on it. If all we have to go on is that and the div is called <div className="Box">, maybe some CSS like this will fix it:
.Box {
display: flex;
}
.Box:first-child {
margin-right: 0.8rem;
}
This is why it is extremely important to know exactly what the surrounding elements will or can be, and exactly which CSS classes/IDs are nearby. We are basically trying to hook into something and correctly identify the left child in Box and add margin to the right of it, or target the right child and add margin to the left of it (or depending on everything, target both and split the additional margin onto both).
Remember there is also ::before and ::after. You are welcome to get creative and find a solution that involves position:relative and position: absolute and adds no markup.
I will leave my answer like that for now, because I think either you already thought about pseudo-selectors, or you will quickly find something that works :)
That or the divider is actually quite viable. The fact you can use media queries alleviates you from concern of future management or scalability of the components. I would not say the same about <div style={{}} />.
As your component specializes another single component it would be a good practice to pass any props your wrapper does not care for to the wrapped component. Otherwise you will loose the ability to use the api of the original <Input>component including passing styles to it:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, ...props}) = (
<Input
{...props}
placeholder="Enter identifiers..."
value={identifiers}
onChange={onIdentifiersChanged}
/>
);
There may be valid cases where you explicitly want to prevent users to be able to pass props to the wrapped component but that does not look like one of those to me.
I clearly must be missing something here. How am I supposed to apply
such layout CSS properties to React components?
You did not miss something. A react component has no generic way to be styled because it is no DOM element. It can have a very complicated and nested DOM representation or no representation at all. So at some point you as the designer of the component have to decided where the styles, ids and class names should be applied. In your case it is as easy as passing these props down and let the <Input> and <Select>component decide. I find that to be quite elegant rather than tedious.
I see several ways to do it, but the easiest I see would be to pass a className to IdentifiersInput like so:
<IdentifiersInput className="marginLeft" identifiers={identifiers} onIdentifiersChanged={this.changeIdentifiers}/>
Inside IdentifiersInput I would just set that class to the Input:
const IdentifiersInput = ({identifiers, onIdentifiersChanged, className}) => {
return (
<Input className={className} placeholder="Enter identifiers..." value={identifiers} onChange={onIdentifiersChanged}/>
);
};
Semantic UI's Input element can receive a className prop.
I would then just use CSS or SCSS to add styles to that particular class. In this case, the margin you want.

Categories

Resources