react-select dropdown opens inside modal - javascript

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.

Related

Scroll into a div that is hidden initially in react

Im building a web chat app in next.js and i have a emoji picker button that when its clicked the menu of emojis appear.The thing is that in order to the user sees the menu of the emojis he has to scroll down.I have tried scrollIntoView() but it doesnt seem to work,possibly im doing something wrong.
import {Picker} from "emoji-mart";
const pickerRef = useRef()
const[showEmojis,setShowEmojis]=useState(false);
useEffect(() => {
if(showEmojis) {
pickerRef.current.scrollIntoView(true)
}
} , [showEmojis])
return(
<EmoticonContainer >
{showEmojis && (<Picker ref={pickerRef} id="picker" style={{width: '100%'}} onSelect={addEmoji}/>)}
</EmoticonContainer>
)
const EmoticonContainer=styled.div`
display:flex;
flex-direction:column;
`;
I tried this code but it doesnt seem to work.It gives me this error: TypeError: pickerRef.current.scrollIntoView is not a function
Hey try adding a div below the picker and scroll to that div because sometimes
those third party libraries come with there css and you could not override them
import {Picker} from "emoji-mart";
const pickerRef = useRef()
const[showEmojis,setShowEmojis]=useState(false);
useEffect(() => {
pickerRef.current.scrollIntoView(true)
} , [showEmojis])
return(
<>
<EmoticonContainer >
<Picker id="picker" style={{width: '100%'}} onSelect={addEmoji}/>
</EmoticonContainer>
<div ref={emojiRef}></div>
</>
)
const EmoticonContainer=styled.div`
display:flex;
flex-direction:column;
`;
Try this and see it will scroll the way you need
Since it is hidden in the beginning then pickerRef.current should return undefined which would cause that error since undefined doesn't have the method .scrollIntoView. To fix this you could do ?.
pickerRef.current?.scrollIntoView()
see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
The optional chaining operator (?.) enables you to read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid.

React Material-UI checkbox onChange event does not fire

Trying to use Material UI checkbox. Pretty simple one might think? Well the checkbox doesn't toggle. Turns out the onChange event is not fired even internally to the component (I put logs in the node_modules package).
<Checkbox
checked={this.state.isTrue}
onChange={e => {
console.log(e.target.checked);
this.setState({isTrue: e.target.checked});
}} />
Pretty simple, right? But the console.log never fires. I can hack around it by putting an onClick event handler on the component and toggling the state manually, but that is silly. Anyone have a clue?
The API is at https://material-ui.com/api/checkbox/#checkbox. Not rocket science.
The issue might come from the structure of your component as provided code is perfectly fine, here is a working exemple you can try on codesandbox.io.
Compare with your code and try to find differences, but isolating a specific element might be a good way to realise the issue might come from somewhere else.
import React, { Component } from "react";
import { render } from "react-dom";
import Checkbox from "material-ui/Checkbox";
class App extends Component {
constructor() {
super();
this.state = {
isTrue: false
};
}
render() {
return (
<div>
<Checkbox
checked={this.state.isTrue}
onChange={e => {
console.log(e.target.checked);
this.setState({ isTrue: e.target.checked });
}}
/>
</div>
);
}
}
render(<App />, document.getElementById("root"));
In many cases, Material UI Checkbox onChange event is not working.
I suggest to save your time and use onClick event instead.
It will work always. Checkbox usually have a boolean value.
<Checkbox
checked={this.state.isTrue}
onClick={() => this.setState({isTrue: !this.state.isTrue})}
/>
Your code looks fine, maybe something else is wrong somewhere. However, for a quick POC, you can refer to this link.
This is the same forked from material UI official docs demo so you could relate easily.
Additionally, you can compare the code with given below
import React from 'react';
import Checkbox from '#material-ui/core/Checkbox';
export default function Checkboxes() {
const [isTrue, setIsTrue] = React.useState(false);
return (
<div>
<Checkbox
checked={isTrue}
onChange={e=> {
console.log("target checked? - ", e.target.checked);
setIsTrue(e.target.checked)
}}
value="checkedA"
inputProps={{
'aria-label': 'primary checkbox',
}}
/>
</div>
);
}
Here are screenshots for verification
References: https://material-ui.com/components/checkboxes/
In the Material UI (in version 5.0.0-beta.4) the onChange event is called when the input[type= "checkbox"] is clicked (this element is transparent, but clicking on it is important). Perhaps the problem is that your styles change input[type= "checkbox"].
P.S. I had a problem with the styles input[type= "checkbox"] { width: 0; height: 0;} were prioritized and the mouse click clicked on the usual element, and not on input[type= "checkbox"].
P.P.S. Look at the styles through the developer tools for working options from the documentation and for your own version, you will find the difference.
If the above does not work for you, check your css, especially if you have global CSS. Chrome dev tools (Elements section) will help here. Look for tags, classes, and especially elements in the CSS. A few pointers below:
label {
css1: value1,
}
&.checked {
css1: value1,
}
&.disabled {
css1: value1
}
If you find any of this, they could easily override the mui css.
I just had this trouble, for the checkbox onChange you have to check on the "checked" (event.target.checked) variable, not the "value"

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.

render simple HTML with react.cloneElement

React.cloneElement() always require first parameter as react component which should be passed as children in props.
Is there are way to pass a simple HTML node as a children. Please refer the code below for better understanding of my issue:
Dialog.jsx (Common component):
return (
<div className="app-dialog-jsx" ref={(ele) => this.ele = ele}>
{this.state.show && React.cloneElement(this.props.children, {
contentStyle: {
height: 400,
overflowY: 'auto',
overflowX: 'hidden'
},
method1: this. method1,
method2: this. method2
})}
</div>
);
now I can not pass:
<Dialog
ref={(dialog)=>this.dialog=dialog}
method1={()=>console.log(1)}
method2 ={()=>console.log(1)}
>
<h4>somethign</h4>
</Dialog>
H4 needs to be a react component otherwise it will not set the props in cloneElement. How can I send simple HTML here, any help?
Detail about why your fiddle is not working as expected.
See the code here:
{this.props.show && React.cloneElement(this.props.children, {
contentStyle: {
color:'red'
}
})}
Issue is in case of Custom Component like CCC, contentStyle will get passed as props and you are using it like this:
style={this.props.contentStyle}
That means at the end style will be applied on div not contentStyle. But in case of div, contentStyle will get applied and that will not change anything because div expect style not contentStyle.
To solve your problem rename contentStyle to style at all the places.
Check this working fiddle.
The best link that can describe the answer is here:
https://reactjs.org/warnings/unknown-prop.html
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
To fix it we should split the props before rendering. like:
render(){
const {children, addCustomProps, ...props} = this.props;
return(<div {...props}>{children}</div>);
}
To avoid the warning, we should pass only those props to the DOM , which can be recognized as a HTML attribute or React attribute like className.

How to Get: Component Width After Render in React

I'm trying to create a general purpose component, that I can reuse in other applications. I need to know the width of the component after render so that I can modify the content of the component.
I've been trying to use the different life cycles in react without success.
componentDidUpdate() {
console.log('width', this.element.offsetWidth);
}
render() {
return (
<div ref={(element) => {this.element = element }} />
);
}
When I try this I get the width of the screen, but if I change the size of the window, I get the width of the component. See the Chrome Log:
ComponentDidMount executes before render so this.element is undefined.
I've also attempted to use different libraries from npm to solve this without luck.
Futher information: The component has to work inside a Bootstrap column, at different widths.
render() {
<Row>
<Col sm={3} />
<MyComponent />
</Col>
<Col sm={9} />
<MyComponent />
</Col>
<Row>
}
Clarification I do not want to resize the window, and I apologize for not being clear. The only reason for me to mention the resizing is that when the DOM has been created and I resize, I get the correct value in offsetWidth. I'm looking for a solution where I get the correct value without resizing. Either a post render function call, listeners, some react magic, or other solutions. My problem is my lack of knowledge with the virtual vs. real DOM.
I was unable to solve this problem with the answers given here. I only got the width of the browser window and not the component within. After some research, it looks like I'm having a chicken or the egg problem with the render. After some more research, I found react-sizeme that solves the issue.
import React, { Component } from 'react';
import sizeMe from 'react-sizeme';
class MyComponent extends Component {
render() {
const { width } = this.props.size;
return (
<div style={{
width: '100%',
backgroundColor: '#eee',
textAlign: 'center'
}}>
<span>My width is: {Math.floor(width)}px</span>
</div>
);
}
}
export default sizeMe()(MyComponent);
Which will produce the following when it first renders
If you need to hold component width state you can do something like this:
componentDidMount(){
this.boundingBox = this.element.getBoundingClientRect();
this.setState({
width:this.boundingBox.width
});
Observable.fromEvent(this.element,"resize")
.subscribe(
() => {
this.boundingBox = this.element.getBoundingClientRect();
this.setState({
width:this.boundingBox.width
});
}
);
};
You can replace Observable with event listener.
Alternatively you can update bounding box attached to class and derive state from it somewhere else.
componentDidUpdate(){
this.boundingBox = this.element.getBoundingClientRect();
};
Whereas this is not an answer to your question directly, it's a solution to your problem that does not add resizing and dirty logic inside of your components.
I'd recommend something like https://github.com/digidem/react-dimensions - which is a HOC wrapper and will listen to global resize events and send you props - containerWidth and containerHeight - I tend to use it a lot when working with SVG, canvas and data grids in react that need to remain responsive and need to know the element's size.
As for lifecycles - things like componentDidUpdate may not behave the way you think it should. Mount is a one-off. Read this comment - https://github.com/facebook/react/issues/2659#issuecomment-66165159
This "SizeMe" component helped me dynamically resize my d3 chart, thanks.
In case it may help someone, this my code from the calling Parent component:
import { SizeMe } from 'react-sizeme'
:
<SizeMe monitorWidth>
{({ size }) => (
<MyChartComponent
divChart = "my_chart_div"
chartWidth ={parseInt(size.width)}
someProps ={my_obj.some_data}
/>
)}
</SizeMe>
With the CSS:
#my_chart_div {
width: 100%;
}

Categories

Resources