The AnswerScreen component is loading a set of MyButton components. I want to change the style (basically the backgroundColor of the MyButton that was click. The code doesn't work as intended. I see that on a class component I would be able to do it with this.className but in a functional component I cannot use this (although I have the key). Any tips?
import React from 'react';
import classes from './AnswerScreen.module.css';
import MyButton from '../../../../utils/MyButton';
const AnswerScreen = (props) => {
const buttonClickHandler = (index, value) => {
if (props.myValue !== value) {
classes.AnswerScreenButton = {
fontSize: '3rem',
cursor: 'pointer',
backgroundColor: 'red'
}
}
}
return (
<div className={classes.AnswerScreen}>
{props.buttons.map((value, index) => {
return <MyButton
className={classes.AnswerScreenButton}
clickHandler={() => buttonClickHandler(index, value)}
key={index}
num = {value} />
})}
</div>
);
}
export default AnswerScreen;
I assume you want to keep track of which button was pressed in the end? I suggest you add a state variable to keep track of the selected button. You can use that state variable to set the correct classname of the button. Add the class AnswerScreenButtonSelected that contains css for a selected button. I removed your usage of index, they should be avoided when setting keys in mapped arrays in React.
import React from 'react';
import classes from './AnswerScreen.module.css';
import MyButton from '../../../../utils/MyButton';
const AnswerScreen = (props) => {
const [selectedButton, setSelectedButton] = useState(null);
return (
<div className={classes.AnswerScreen}>
{props.buttons.map( value =>
<MyButton
key={value}
className={selectedButton === value ? classes.AnswerScreenButtonSelected : classes.AnswerScreenButton}
clickHandler={() => setSelectedButton(value)}
num = {value} />
)}
</div>
);
}
export default AnswerScreen;
Related
I wanted to change the style of an item with one click in a list and remove it if I click another item
I did like this but when I click on the second it doesn’t change to the first
const ref = useRef();
const handleClick = () => {
if (ref.current.style.backgroundColor) {
ref.current.style.backgroundColor = '';
ref.current.style.color = '';
} else {
ref.current.style.backgroundColor = 'green';
ref.current.style.color = 'white';
}
};
<Card ref={ref} elevation={6} style={{ marginBottom: "5px"}} onClick={()=>{ handleClick()}} >
<CardContent style={{ height: "10px" }}>
<Typography >
{user}
</Typography>
</CardContent>
</Card>
);
};
any help please!
I like using a package like classnames ( yarn add classnames or npm i classnames) to apply conditional styling through classes rather than having to inline the element's styling directly.
You could pass the selected attribute to your Card component using React's useState (or Redux) and then apply conditional styling to selected cards (i.e. <Card selected />).
Component.js
import { useState } from 'react';
import { useSelector } from 'react-redux';
import Card from './Card';
const Component = () => {
const [selectedId, setSelectedId] = useState(null);
const items = useSelector(state => state.items);
const handleClick = (id) => {
setSelectedId(id);
};
return items.map(({id}) =>
<Card
key={id}
onClick={() => handleClick(id)}
selected={id === selectedId}
>
...
</Card>
);
};
export default Component;
Card.js
import { classNames } from 'classnames';
const Card = ({ children, onClick, selected = false }) => (
<div
className={
classNames('Card', {
'Card--Selected': selected
})
}
onClick={onClick}
>
{ children }
</div>
);
export default Card;
Card.scss
.Card {
// Card styling...
&--Selected {
// Selected card styling...
}
}
I am trying to programatically hide a div when a button is clicked.
Therefore I have written the following code, but it is not working (Div is still there):
import React, {useState} from 'react';
import './Button.css';
export function Button() {
const [click, setClick] = useState(false);
const hideDiv = () => {
var display = 'block';
if (click){
display = 'none';
setClick(false);
}else{
display = 'block';
setClick(true);
}
return ([
document.getElementById('main').style.display = {display}
])
};
return(
<button className='btn' onClick={() => setClick(true), hideDiv}>Some button!</button>
);
};
I "debugged" the application with an alert and the correct display is coming, but it seems that I am missing something when applying it to the div style.
What am I missing here?
Try this approach,
export function Button() {
const [display, setDisplay] = useState("block");
const hideDiv = () => {
setDisplay(display === "none" ? "block" : "none");
};
return (
<>
{display === "block" && <div>MY DIV</div>}
<button className="btn" onClick={hideDiv}>
Some button!
</button>
</>
);
}
CODESANDBOX - https://codesandbox.io/s/wizardly-water-kf0md?file=/src/App.js
As #dai commented, you shouldn't interact directly with the DOM inside React (because it actually renders a virtual one).
Try using states instead, for example:
import React, {useState} from 'react';
import './Button.css';
export function Button() {
const [display, setDisplay] = useState(true);
const hideDiv = () => {
setDisplay(!display);
};
return (
<>
{display && (<div id='main'></div>)}
<button className='btn' onClick={hideDiv}>Some button!</button>
<>
);
};
If the div you want to add is an other component, you should use contexts instead.
For a Material-UI Button component, I would like to have the "focus" styling look the same as "focusVisible" styling. Meaning I want it to have the same ripple effect visible if the button was focused programatically or with the mouse as if the button was focused with the tab key.
A sort-of workaround I have found is to call dispatchEvent(new window.Event("keydown")) on the element before it is focused, causing keyboard to be the last input type used. This will have the effect of making the button look the way I want UNTIL the onMouseLeave event (from MUI <ButtonBase/>) or another mouse event is fired, causing the visible focus to disappear.
I have figured out how to change the focus styling of the component like this:
import React from "react"
import { withStyles } from "#material-ui/core/styles"
import Button from "#material-ui/core/Button"
const styles = {
root: {
'&:focus': {
border: "3px solid #000000"
}
}
}
const CustomButtonRaw = React.forwardRef((props, ref) => {
const { classes, ...rest } = props
return <Button classes={{root: classes.root}} {...rest} ref={ref}/>
}
const CustomButton = withStyles(styles, { name: "CustomButton" })(CustomButtonRaw)
export default CustomButton
So, I can apply some style to the button when it is in "focus" state. (For ex. I applied a border). But I am missing how to get the styles to apply. I have tried putting the className 'Mui-visibleFocus' on the Button but that did not seem to have an effect. Is there some way to get the styles that would be applied if the Button was in visibleFocus state?
ButtonBase (which Button delegates to) has an action prop which provides the ability to set the button's focus-visible state.
ButtonBase leverages the useImperativeHandle hook for this. To leverage it, you pass a ref into the action prop and then you can later call actionRef.current.focusVisible().
However, this by itself is not sufficient, because there are several mouse and touch events that ButtonBase listens to in order to start/stop the ripple. If you use the disableTouchRipple prop, it prevents ButtonBase from trying to start/stop the ripple based on those events.
Unfortunately disableTouchRipple prevents click and touch animations on the button. These can be restored by adding another TouchRipple element explicitly that you control. My example below shows handling onMouseDown and onMouseUp as a proof-of-concept, but an ideal solution would deal with all the different events that ButtonBase handles.
Here's a working example:
import React from "react";
import Button from "#material-ui/core/Button";
import TouchRipple from "#material-ui/core/ButtonBase/TouchRipple";
const FocusRippleButton = React.forwardRef(function FocusRippleButton(
{ onFocus, onMouseDown, onMouseUp, children, ...other },
ref
) {
const actionRef = React.useRef();
const rippleRef = React.useRef();
const handleFocus = (event) => {
actionRef.current.focusVisible();
if (onFocus) {
onFocus(event);
}
};
const handleMouseUp = (event) => {
rippleRef.current.stop(event);
if (onMouseUp) {
onMouseUp(event);
}
};
const handleMouseDown = (event) => {
rippleRef.current.start(event);
if (onMouseDown) {
onMouseDown(event);
}
};
return (
<Button
ref={ref}
action={actionRef}
disableTouchRipple
onFocus={handleFocus}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
{...other}
>
{children}
<TouchRipple ref={rippleRef} />
</Button>
);
});
export default function App() {
return (
<div className="App">
<FocusRippleButton variant="contained" color="primary">
Button 1
</FocusRippleButton>
<br />
<br />
<FocusRippleButton
variant="contained"
color="primary"
onFocus={() => console.log("Some extra onFocus functionality")}
>
Button 2
</FocusRippleButton>
</div>
);
}
We can create a reference to the action of the material-ui Button component and use the action reference within useLayoutEffect to achieve the ripple effect
import React, { createRef, useLayoutEffect } from "react";
import Button from "#material-ui/core/Button";
function FocusButton(props) {
const { handleClose } = props;
const actionRef = createRef();
useLayoutEffect(() => {
if (actionRef.current) {
actionRef.current.focusVisible();
}
}, []);
return (
<Button action={actionRef} onClick={handleClose}>
Ok
</Button>
);
}
The above FocusButton can be used as a replacement to Button or simply you can add a reference and call the focusVisible() in the trigger method
Eg:
const buttonRef = createRef();
const handleButton2Click = () => {
buttonRef.current.focusVisible();
};
.
.
.
.
<Button action={buttonRef} variant="outlined">
Button 1
</Button>
<Button variant="outlined" color="primary" onClick={handleButton2Click}>
Button 2
</Button>
You can find the demo in this link
Consider a component that renders a button and says this button should have a red background and a yellow text color. Also there exists a Parent component that uses this child but says, the yellow color is fine, but I want the background color to be green.
withStyles
No problem using the old withStyles.
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import { Button } from "#material-ui/core";
const parentStyles = {
root: {
background: "green"
}
};
const childStyles = {
root: {
background: "red"
},
label: {
color: "yellow"
}
};
const ChildWithStyles = withStyles(childStyles)(({ classes }) => {
return <Button classes={classes}>Button in Child withStyles</Button>;
});
const ParentWithStyles = withStyles(parentStyles)(({ classes }) => {
return <ChildWithStyles classes={classes} />;
});
export default ParentWithStyles;
https://codesandbox.io/s/passing-classes-using-withstyles-w17xs?file=/demo.tsx
makeStyles/useStyles
Let's try the makeStyles/useStyles instead and follow the guide Overriding styles - classes prop on material-ui.com.
import React from "react";
import { makeStyles } from "#material-ui/styles";
import { Button } from "#material-ui/core";
const parentStyles = {
root: {
background: "green"
}
};
const childStyles = {
root: {
background: "red"
},
label: {
color: "yellow"
}
};
// useStyles variant does NOT let me override classes
const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);
const ChildUseStyles = ({ classes: classesOverride }) => {
const classes = useChildStyles({ classes: classesOverride });
return (
<>
<Button classes={classes}>Button1 in Child useStyles</Button>
<Button classes={classesOverride}>Button2 in Child useStyles</Button>
</>
);
};
const AnotherChildUseStyles = props => {
const classes = useChildStyles(props);
return (
<>
<Button classes={classes}>Button3 in Child useStyles</Button>
</>
);
};
const ParentUseStyles = () => {
const classes = useParentStyles();
return <>
<ChildUseStyles classes={classes} />
<AnotherChildUseStyles classes={classes} />
</>
};
export default ParentUseStyles;
https://codesandbox.io/s/passing-classes-using-usestyles-6x5hf?file=/demo.tsx
There seems no way to get the desired effect that I got using withStyles. A few questions, considering I still want the same effect (green button yellow text) using some method of classes overriding (which seemed to make sense to me before).
How is my understanding wrong about how to pass classes as means to override parts of them using useStyles?
How should I approach it alternatively?
And if I'm using the wrong approach, why is material-ui still giving me a warning when the parent has something in the styles that the child doesn't have?
the key something provided to the classes prop is not implemented in [Child]
Is the migration from the old approach (withStyles) vs the new approach documented somewhere?
Btw, I'm aware of this solution but that seems cumbersome when you have too much you want to override.
const useStyles = makeStyles({
root: {
backgroundColor: 'red',
color: props => props.color, // <-- this
},
});
function MyComponent(props) {
const classes = useStyles(props);
return <div className={classes.root} />;
}
withStyles has very little functionality in it. It is almost solely a wrapper to provide an HOC interface to makeStyles / useStyles. So all of the functionality from withStyles is still available with makeStyles.
The reason you aren't getting the desired effect is simply because of order of execution.
Instead of:
const useParentStyles = makeStyles(parentStyles);
const useChildStyles = makeStyles(childStyles);
you should have:
const useChildStyles = makeStyles(childStyles);
const useParentStyles = makeStyles(parentStyles);
The order in which makeStyles is called determines the order of the corresponding style sheets in the <head> and when specificity is otherwise the same, that order determines which styles win (later styles win over earlier styles). It is harder to get that order wrong using withStyles since the wrapper that you are using to override something else will generally be defined after the thing it wraps. With multiple calls to makeStyles it is easier to do an arbitrary order that doesn't necessarily put the overrides after the base styles they should impact.
The key to understanding this is to recognize that you aren't really passing in overrides, but rather a set of classes to be merged with the new classes. If childClasses.root === 'child_root_1' and parentClasses.root === 'parent_root_1', then the merged result is mergedClasses.root === 'child_root_1 parent_root_1' meaning any elements that have their className set to mergedClasses.root are receiving both CSS classes. The end result (as far as what overrides what) is fully determined by CSS specificity of the styles in the two classes.
Related answers:
Material UI v4 makeStyles exported from a single file doesn't retain the styles on refresh
Internal implementation of "makeStyles" in React Material-UI?
In Material-ui 4.11.x while creating styles using makeStyles wrap the enclosing styles with createStyles, and this style will have highest priority than the default one.
const useStyles = makeStyles((theme: Theme) =>
createStyles({
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: '#fff',
},
}),
);
You could try removing the createStyles and see the difference.
code source from https://material-ui.com/components/backdrop/
One way to achieve this using withStyles is the following and can be helpful to override css classes.
Supposing that you want to override a class called ".myclass" which contains "position: absolute;":
import { withStyles } from '#material-ui/styles';
const styles = {
"#global": {
".myClass": {
position: "relative",
}
}
};
const TestComponent = (props) => (
<>
<SomeComponent {...props}>
</>
);
export default withStyles(styles)(TestComponent);
After doing this, you override the definition of .myClass defined on <SomeComponent/> to be "position: relative;".
I'm trying to create an expandable list component. When I click on a list item,
I want the icon to the right of it to change. Right now, when a list item is tapped,
each icon is changed. I only want icon of that particular item to change. Been
thinking about it for 20min and figured I'd reach out for help . Thanks!
import { List, ListItem } from 'react-native-elements';
export default class ExpandingList extends Component {
constructor(props){
super(props)
this.state = {
visibleItems: false
}
}
toggleMenu(){
this.setState({
visibleItems: !this.state.visibleItems
})
};
render() {
const list = "list 1, list 2, list 3";
return (
<View>
<List>
{
list.map((item, i) => (
<ListItem
onPress={ () => this.toggleMenu() }
key={i}
rightIcon={this.state.visibleItems ? iconRight : iconDown}
title={item} />
))
}
</List
</View
)
}
}
You are doing a boolean value and all list items are looking at the same value. To achieve what you want you need to pass a unique value, in this situation i am using the index but ideally you will have a unique identifier besides the index.
Below should be able to acheive what you are looking for
import { List, ListItem } from 'react-native-elements';
export default class ExpandingList extends Component {
constructor(props){
super(props)
this.state = {
visibleItems: null
}
}
toggleMenu(itemIndex){
this.setState({
visibleItems: itemIndex
})
};
render() {
const list = "list 1, list 2, list 3";
return (
<View>
<List>
{
list.map((item, i) => (
<ListItem
onPress={ () => this.toggleMenu(i) }
key={i}
rightIcon={this.state.visibleItems === i ? iconRight : iconDown}
title={item} />
))
}
</List
</View
)
}
}
Note: I am assuming you have this rendering already but for other people. The list const is a comma separated string and string do not have a map function.
Also the code above does not take into account deselecting a list item that was already selected. That can be done by checking the value in the state and if it is the same reseting the value back to null.
Here's a way to do it also:
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = {
visibleItem: 0
};
select = i => {
this.setState({
visibleItem: i
});
};
render() {
const items = [1, 2, 3, 4];
return (
<React.Fragment>
{items.map((v, i) => {
return (
<div
style={{ color: this.state.visibleItem === i ? "red" : "black" }}
onClick={() => this.select(i)}
>
{v}
</div>
);
})}
</React.Fragment>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working example here.