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"
Related
the same newbie here.
I have the code which working as intended, all I want to do is add && with state variable (boolean) to make a text have line-through decoration while clicked (using crossedItemOnClick)
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
function Para(props) {
const {id, className, info, state} = props;
return (
<p onClick={props.crossedItemOnClick} id={id} className={className} style={{textDecorationLine: 'line-through'}}>{info}</p>
)
}
export default Para;
My whole page disappears if I change it to:
<p onClick={props.crossedItemOnClick} id={id} className={className} style={state && {textDecorationLine: 'line-through'}}>{info}</p>
I'd love to know what I'm doing wrong and why the page completely disappears. And of course explanation to learn if you'd be kind to.
Much thanks!
style attribute takes object as value in react. Correct way will be:
<p onClick={props.crossedItemOnClick} id={id} className={className} style={state ? {textDecorationLine: 'line-through'}:{}}>{info}</p>
assuming you have to apply the style when state is true.
That is because style prop expect object of type CSSProperties, and in case when your condition is false you will end up with something like style={false} which will cause your app to crash since you provided HTML element with invalid styling.
Easiest solution is to just rewrite that part to style={state ? {...someStyle} : {}}
Your approach is mostly correct, however, your condition should be applied on the style property directly like this if you want to use &&
style={{textDecorationLine: state && 'line-through'}}
Edit:
I figured it out & posted answer below.
Original Question
I am trying to create a completely compartmentalized web application within a shadow-dom and I've been using Antd components and ran into the issue where Antd is appending drop-down options into the body tag instead of as a child of the element that React is rendering into.
To isolate this issue I've removed everything outside of just React.render & a single Antd element which still does the same thing.
I then used a different component, "react-date-picker", which works how I had hoped Antd did, where the component renders as a child of the div specified for react.
Unfortunately, Antd rendering to the body of the HTML instead of as a child makes using shadow-root pointless.
Essentially my question is:
Is this Antd's normal functionality? If not then what might I be screwing up to have this happen? If so then is there a built-in Antd option that I'm missing that will render Antd options as child elements? If that option doesn't exist within their libraries, is there a way for me to force Antd to render as a child of my shadow-root node?
Here is what I'm using to render the Antd DatePicker component:
import ReactDOM from 'react-dom';
import React from 'react';
import DatePicker from 'antd/lib/date-picker';
ReactDOM.render(<DatePicker/>, document.getElementById('entry-fields'));
Before clicking on the Antd date picker:
After clicking on it, drop down options are appended to <body> and not <div id="entry-fields>:
Here is what I'm using to render the react-date-picker component to demonstrate the functionality I expected / need:
import ReactDOM from 'react-dom';
import React from 'react';
import DatePicker from "react-datepicker";
class Example extends React.Component {
state = {
startDate: new Date()
};
handleChange = (date: any) => {
this.setState({
startDate: date
});
};
render() {
return (
<DatePicker
selected={this.state.startDate}
onChange={this.handleChange}
/>
);
}
}
ReactDOM.render(<Example/>, document.getElementById('entry-fields'));
Before clicking on the react-date-picker date picker:
After clicking on the react-date-picker date picker (the drop down options are appended as children of the element react is rendered onto):
Basically I expected Antd to render its options encapsulated within the React rendered into <div></div> but it is instead appending elements on the <body></body>.
I'm relatively inexperienced in the web-dev domain and have resorted to asking a question here after way too much time trying to find the answer on my own. I am getting extremely frustrated in web-dev in general where any question seems to yield hundreds of irrelevant medium blog posts that are not useful in any capacity... assuming that it's not just me not knowing what to search for yet to find the answers I need which could very well be the case.
Really appreciate any help in advance.
Not sure how I managed to miss this but Antd has a parameter called "getCalendarContainer" which if left blank will render options into the body of the document but if supplied with the correct parameters will render the child elements into the container of your choosing.
Going off this example: https://react-component.github.io/calendar/examples/getCalendarContainer.html
I got it working by adding this function to my component:
getCalendarContainer()
{
return this.d || document.getElementById('calendarContainer');
}
and adding this to the component in JSX:
<div id="calendarContainer" ref={n => (this.d = n as HTMLDivElement)} >
<DatePicker onChange={EntryFields.onDateChange} getCalendarContainer={this.getCalendarContainer}/>
</div>
and initializing the div tag to reference it in the component's constructor like this:
private d: HTMLDivElement;
constructor(props: any)
{
super(props);
this.d = document.createElement("div");
...
It's also worth noting that the above will not work immediately when using shadow-DOM since you need to access the node that the shadow-DOM is a child to and then use getElementById().
Something along these lines (but probably better written I hope lol)
getContainer() {
let elem = null;
let shadowContainer = document.getElementById('entryFieldsShadow') as HTMLInputElement;
if (shadowContainer != null) {
let shadowDom = shadowContainer.firstElementChild;
if (shadowDom != null) {
let shadowRoot = shadowDom.shadowRoot;
if (shadowRoot != null) {
elem = shadowRoot.getElementById("entryFieldsContainer")
}
}
}
return elem || this.d;
}
where the JSX with react-shadow's shadow root is included looks like this:
return (
<div id="entryFieldsShadow">
<root.div>
<div>
<div id="entryFieldsContainer"/>
<style type="text/css"> #import "static/1.css"; </style>
<Layout>
<Content>
{this.RowCols()}
</Content>
</Layout>
</div>
</root.div>
</div>
)
This solve my problems
<DatePicker
{...}
getCalendarContainer={triggerNode => triggerNode.parentNode}
/>
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.
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.
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%;
}