How can I dynamically change property value in an object in Material UI? - reactjs

I have property named listItem in useStyles defined outside of TodoListItem like this:
const useStyles = makeStyles(theme => ({
listItem: {
textDecoration: 'none'
}
})
)
const TodoListItem({checked}) => {
const classes = useStyles();
return <ListItemText className={classes.listItem} primary={text}/>
};
Then I wanted to change the status of textDecoration based on variable named checked.
And I tried to directly use checked variable in useStyles but does not work as it is out of scope.
textDecoration: checked ? 'line-through' : 'none'
In this case, how should I pass checked variable into useStyles to make the ternary operator working?

CSS properties in makeStyles support custom props for dynamic styling:
const useStyles = makeStyles(theme => ({
listItem: {
textDecoration: ({checked}) => checked ? 'line-through' : 'none'
}
}));
const Component = props => {
const classes = useStyles({checked: props.checked});
...
}

You can pass props, state, and whatever you want as a param to the style hook.
And it has better readability since we can understand whether the style hook is using other params or not quickly.
const useStyles = checked =>
makeStyles(theme => ({
listItem: {
textDecoration: checked ? 'line-through' : 'none'
}
}));
const classes = useStyles(checked)();

I think that the most easy way to achieve it is to use style instead classname and manipulate everything that will use logic outside big style object
<ListItemText style={[classes.listItem,{textDecoration: checked ? 'line-through' : 'none'}]} primary={text}/>

Related

How can I use a variable value for class name when using makeStyles?

In my React app, I have a component which takes in some data. Depending on the value of that data, I want to show the component with a different coloured background.
The styles are defined as:
const useStyles = makeStyles((theme) => ({
class1: {
backgroundColor: "red",
},
class2: {
backgroundColor: "pink",
},
}));
The component is:
const MyBox = ({ data }) => {
let classes = useStyles();
let innerClassName;
if (data.value) {
innerClassName = "class1";
} else {
innerClassName = "class2";
}
return (
<div className={innerClassName}>
Content goes here
</div>
);
};
export default MyBox;
However, this gives the component a class of "class1" or "class2", which doesn't get picked up by makeStyles. I also tried <div className={classes.innerClassName}> but then it looks for a class called 'innerClassName' which obviously it can't find.
I think I need to use some kind of variable string within <div className={????}> but I've tried various template literal strings and none of them have worked. What should I be doing?

How to useStyles based on props inside a breakpoint with material-ui

Let's say I have a very simple props interface that specifics a boolean property. Now, in my useStyles, I want to change how that style is rendered based on both the conditional property AND a breakpoint. Here's a very simple example:
interface Props {
isError: boolean;
}
const useStyles = makeStyles<Theme, Props>(theme => ({
box: ({ isError}) => ({
backgroundColor: isError? 'red' : 'green',
[theme.breakpoints.up('md')]: {
backgroundColor: isError ? 'maroon' : 'teal',
}
}),
}));
When I'm under the md breakpoint, this works as expected; but as soon as I go over the md breakpoint, the color doesn't change. How come?
Here's a demo on StackBlitz that demonstrates the problem.
In working up the example for this question, I figured out the problem. The property value for creating styles based on a breakpoint, also needs to be a function:
const useStyles = makeStyles<Theme, Props>(theme => ({
box: (outerProps) => ({
backgroundColor: outerProps.isError ? 'red' : 'green',
[theme.breakpoints.up('md')]: (innerProps) => ({
backgroundColor: innerProps.isError ? 'maroon' : 'aqua',
}),
})
}));
Here's an updated StackBlitz showing the working example.

Unable to use "this" in makeStyle

I would like to create a dynamic background image depending on my props. So for that I wanted to make a react style and give it the picture stored in my state but I can't use this.state.pictures in it and I don't know why.
class DisplayArtist extends Component {
state = {
name : this.props.Info.artists.items[0].name,
followers: this.props.Info.artists.items[0].followers.total,
genres: this.props.Info.artists.items[0].genres,
picture: this.props.Info.artists.items[0].images[0]
}
useStyles = makeStyles({
root: {
backgroundImage={this.state.pictures}
}
});
makeStyles is better used in a functional component, rather than a class component.
using makeStyes inside a function component causes the style to be recreated on every render. I don't recommend doing it that way.
The recommended approach is to use inline styles for dynamic background images
e.g. style={{ backgroundImage: artist.images[0] }}
Converting to Functional Component
const DisplayArtist = (props) => {
const [ artist, setArtist ] = useState(null);
useEffect(() => {
//do your own checks on the props here.
const { name, total, genres, images } = props.Info.artists.items[0]
setArtist({name, total, genres, images});
},[props])
return ( <div style={{ width: '200px', height:'200px', backgroundImage: artist.images[0] }} /> )
}
export default DisplayArtist
You can use Functional Component and use useStyles and pass the state you want to it
const useStyles = makeStyles(() => ({
root: {
backgroundImage:({ picture }) => ( picture )
}
}))
function DisplayArtist({Info}) {
const [picture, setPicture] = useState()
const classes = useStyles({picture})
}
Use Higher-order component API Higher-Order-Component-Api
const styles = theme => ({
root: {
backgroundImage={this.state.pictures}
}
});
class DisplayArtist extends Component {
state = {
name : this.props.Info.artists.items[0].name,
followers: this.props.Info.artists.items[0].followers.total,
genres: this.props.Info.artists.items[0].genres,
picture: this.props.Info.artists.items[0].images[0]
}
}
export default withStyles(styles)(DisplayArtist);

Why is useImperitaveHandle hook used in React?

I am not able to find a use Case for useImperativeHandle Hook. Trying to Google and understand I came across a code sandbox showing an example of why the useImperitaveHandle Hook would be used. Here is the link to the codesandbox
https://codesandbox.io/s/useimperativehandle-example-forked-illie?file=/src/App.js
I modified the code to get it working without the useImperitaveHandle in the codesandbox link below. Can someone explain why the hook would be used as I believe that code can be written without it to provide the exact same functionality.
https://codesandbox.io/s/useimperativehandle-example-forked-2cjdc?file=/src/App.js
I found an example where this would be used. According to my understanding it will be mainly needed if you need to write some custom functionality for a library and will need some of the library's in built features.
In the example I will provide, I will write a custom CellEditor for the library ag-grid(Table library) because I want to select the value of the cell in the table using Material UI's autocomplete. Below is the code
import React, { useState, forwardRef, useImperativeHandle } from "react";
import MuiTextField from "#material-ui/core/TextField";
import MuiAutocomplete from "#material-ui/lab/Autocomplete";
const AutocompleteEditor = forwardRef(
({ fieldToSave, fieldToShow, textFieldProps, options, ...props }, ref) => {
const [value, setValue] = useState("");
useImperativeHandle(ref, () => {
return {
getValue: () => {
return value;
},
afterGuiAttached: () => {
setValue(props.value);
},
};
});
const tranformValue = (value, fieldtosave) =>
Array.isArray(value)
? value.map((v) => v[fieldtosave] || v)
: value[fieldtosave];
function onChangeHandler(e, value) {
setValue(value ? tranformValue(value, fieldToSave) : null);
}
return (
<MuiAutocomplete
style={{ padding: "0 10px" }}
options={options}
getOptionLabel={(item) => {
return typeof item === "string" || typeof item === "number"
? props.options.find((i) => i[fieldToSave] === item)[fieldToShow]
: item[fieldToShow];
}}
getOptionSelected={(item, current) => {
return item[fieldToSave] === current;
}}
value={value}
onChange={onChangeHandler}
disableClearable
renderInput={(params) => (
<MuiTextField
{...params}
{...textFieldProps}
style={{ padding: "5px 0" }}
placeholder={"Select " + props.column.colId}
/>
)}
/>
);
}
);
export default AutocompleteEditor;
IGNORE THE AUTOCOMPLETE PART IF ITS CONFUSING, ITS NOT IMPORTANT FOR UNDERSTANDING useImperativeHandler
So To explain what the code does. The value of the above component is set by Autocomplete by the user, but ag-grid table also needs the value as it needs to update the value in corresponding cell.
It uses a ref internally that it passes to the customCellEditor. The useImperativeHook then tells ag-grid to use the value from the state of the component whenever getValue is called (it is called when the cell needs to display the value)

React component won't take onChange event

Obligatory "new to react" paragraph here. I have this rating component I got from material-ui and i'm trying to send the value to a database.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Rating from '#material-ui/lab/Rating';
import Box from '#material-ui/core/Box';
const labels = {
0.5: 'Worst of the Worst',
1: 'Bad',
1.5: 'Poor',
2: 'Passable',
2.5: 'Ok',
3: 'Good',
3.5: 'Damn Good',
4: 'Great',
4.5: 'Love',
5: 'Perfection',
};
const useStyles = makeStyles({
root: {
width: 200,
display: 'flex',
alignItems: 'center',
},
});
export default function HoverRating(props) {
const [value, setValue] = React.useState(2);
const [hover, setHover] = React.useState(-1);
const classes = useStyles();
const onRatingChange = (event) => {
console.log(event.target.value)
props.reduxDispatch ({ type: "RATING_CHANGE", value: event.target.value
})
}
return (
<div className={classes.root}>
<Rating
name="hover-feedback"
value={value}
defaultValue={0}
precision={0.5}
size="large"
onChange={(event, newValue) => {
setValue(newValue);
console.log("your newValue is " + newValue)
}}
onChangeActive={(event, newHover) => {
setHover(newHover);
}}
{ onRatingChange }
/>
<br/>
{value !== null && <Box ml={2}>{labels[hover !== -1 ? hover : value]}</Box>}
</div>
);
}
It doesn't like something about my onRatingChange function. I've moved it all over the place and it's still throwing errors. I just really don't understand the issue. I'm mostly getting-
"./src/components/Rating.js
Line 54:11: Parsing error: Unexpected token, expected "..."
I've been at this for hours and I salvation.
Change your code from:
{ onRatingChange }
to:
onRatingChange={onRatingChange}
and change your file extension from .js to .jsx because you are using the JSX syntax
First, you appear the be storing the rating in two places: in your local state (with React.useState), and from the looks of your onRatingChange function, in a Redux store somewhere. It would be a good idea to pick one, and use that.
As for the direct answer to your question, your syntax is wrong. You're writing your Rating component in the following way:
<Rating
// ...
onChange={(event, newValue) => {
setValue(newValue);
console.log("your newValue is " + newValue)
}}
// ...
{ onRatingChange }
/>
The Rating component expects an onChange prop. I assume you want your onRatingChange function to be called when the rating changes. As such, you'd write:
<Rating
// ...
onChange={onRatingChange}
/>
The complication here though is that you're trying to register two different handlers for the rating change event. The bottom line is, decide on one, and then pass that as a callback function to the onChange prop.

Resources