change color depending on value with javascript - javascript

I'm using React and want to change text color depends on the value.
The code looks like this:
const ProductCard = (props) => {
const classes = useStyles();
useEffect(() => {
const category = document.getElementById('category');
if(props.category === "youtube"){
category.style.color="#DC143C";
}
if(props.category === "blog"){
category.style.color="#00FFFF";
}
if(props.category === "instagram"){
category.style.color="#FF88FF";
}
if(props.category === "twitter"){
category.style.color="#3366FF";
}
}, []);
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<Typography id="category" className={classes.category} component="p">
{props.category}
</Typography>
</CardContent>
</Card>
)
}
export default ProductCard
With this code, only the first value of the element is changed, the rest are not.
What I'm doing wrong?

your code only runs on mount with empty array dependency, hence any change on props won't be reflected. that's the most likely the problem here.
fwiw your code is rather complicated and it's better to avoid manipulating the DOM directly, it would much simpler if you create a dictionary to apply your style to your element like:
const colors = {
youtube: "#DC143C",
blog: "#00FFFF",
instagram: "#FF88FF",
twitter: "#3366FF",
}
const ProductCard = ({ category }) => {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<Typography id="category" style={{ color: colors[category] }} className={classes.category} component="p">
{category}
</Typography>
</CardContent>
</Card>
)
}
export default ProductCard

First - you should avoid querying dom inside react app. You can create state for holding your class or styles and then pass it to component.
Second - remove empty array from useEffect it should help.

When you get an element by id, only one element is returned.
const category = document.getElementById('category'); // referring to this line
That's because technically, ids should be unique. Only one element should have the id category on the page. So when you set the style on container, only one element is changed because it is only one element.
Additionally, ids are not scoped to a specific component. So if you have multiple ProductCard components, getElementById will always return the category of the first ProductCard component.
Solutions
I'm not familiar with your exact components, so not all of these will work for you. But at least one should.
Use a color prop
If your Typography component accepts a color or style prop, use that. Then, you're telling React "this specific component should have this color".
const colors = {
youtube: "#DC143C",
blog: "#00FFFF",
instagram: "#FF88FF",
twitter: "#3366FF",
}
const ProductCard = (props) => {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<Typography id="category" className={classes.category} component="p" style={{ color: colors[props.category] }}>
{props.category}
</Typography>
</CardContent>
</Card>
)
}
export default ProductCard
Use a wrapper component
You may be able to wrap a div around just the typography and set the text color that way. Not the best option, but it will work.
const colors = {
youtube: "#DC143C",
blog: "#00FFFF",
instagram: "#FF88FF",
twitter: "#3366FF",
}
const ProductCard = (props) => {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardContent className={classes.content}>
<div style={{ color: colors[props.category] }}>
<Typography id="category" className={classes.category} component="p" style={{ color: colors[props.category] }}>
{props.category}
</Typography>
</div>
</CardContent>
</Card>
)
}
export default ProductCard

Related

Compose components to variable in reactjs

Is it possible to compose more different parts of component to variable?
documentation
const App = () => {
let element;
element = <View>
<Text>text</Text>
</View> // --> OK
element = element + <View>
<Text>text2</Text>
</View> // --> TypeError: Cannot read properties of undefined (reading 'height')
element.push(<View>
<Text>text3</Text>
</View>); // --> TypeError: element.push is not a function
return <>
{element}
</>
}
export default App;
I use reactjs 17.0.2, typescript and "#react-pdf/renderer": "2.3.0".
Update
Based on your question here, this should work:
<Document>
<Page size="A4" orientation="landscape">
{/* -- Table LEFT: -- */}
<View>
{/* -- Table Head: -- */}
<View>
<Text>Index</Text>
<Text>Brand</Text>
<Text>Type</Text>
</View>
{/* -- Table Body: -- */}
{data?.cars?.length &&
data.cars.map(({ id, brand, type }, index) => (
<View key={`${id}-left`}>
<Text>{index + 1}</Text>
<Text>{brand || ''}</Text>
<Text>{type || ''}</Text>
</View>
))}
</View>
</Page>
<Page size="A4" orientation="landscape">
{/* -- Table RIGHT: -- */}
<View>
{/* -- Table Head: -- */}
<View>
<Text>Color</Text>
<Text>Fuel</Text>
</View>
{/* -- Table Body: -- */}
{data?.cars?.length &&
data.cars.map(({ id, color, fuel }) => (
<View key={`${id}-right`}>
<Text>{color || ''}</Text>
<Text>{fuel || ''}</Text>
</View>
))}
</View>
</Page>
</Document>
The issue seems to be with how you're handling arrays, not with rending React elements.
If you want to access the properties of the object in an array element, you can destructure the element, so instead of
data.cars.map((car, index) => (<Text>{car.color}</Text>))
you can do
data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
If you're not performing any operations on the array elements, you can use an implicit return instead of an explicit return:
// explicit return
data.cars.map(({id, brand, type, color, fuel}, index) => {
// do something else here
return (
<Text>{color}</Text>
)
});
// implicit return
data.cars.map(({id, brand, type, color, fuel}, index) => (<Text>{color}</Text>));
Also, when you're rending known text values in React, you don't need to wrap it in curly braces ({}), you can just render the text directly.
Instead of
<Text>{'color'}</Text>
you can just put
<Text>color</Text>
unless it's required by whatever library you're using. I'm not familiar with #react-pdf/renderer.
One more thing to keep in mind is that the key for list items in React should be something stable. Using array indices as keys is discouraged (see React docs).
Original answer
If you want to render an element this way, you could do something like this:
const App = () => {
let element = [];
// Each child in a list needs a unique "key" prop
element.push(<View key={someUniqueKey}>
<Text>text</Text>
</View>)
element.push(<View key={someOtherUniqueKey}>
<Text>text2</Text>
</View>)
element.push(<View key={oneMoreUniqueKey}>
<Text>text3</Text>
</View>);
return <>
{element}
</>
}
export default App;
Personally, I haven't seen anyone render components like this.
The strategy you are looking for is called conditional rendering, and there are different ways to do this depending on the situation.
For example, if you're trying to dynamically render data from an API response, you could do something like this:
const App = () => {
const { data } = fetchDataFromAPI();
return (
<>
<View>
<Text>text</Text>
</View>
{data?.text2 && (
<View>
<Text>{data.text2}</Text>
</View>
)}
{data?.text3 && (
<View>
<Text>{data.text3}</Text>
</View>
)}
</>
);
};
export default App;
You can check out the React docs for conditional rendering and rendering lists.
(Note: The above links are for the beta docs. If you prefer the classic(?) docs: conditional rendering and lists)

MUI : Out-of-range value `X` for the select component

I'm making a glossary where each of the definitions are a cards that can be flipped (CardFlip) I build an array where I send for each card, the data via props to my component "CardFlip" dealing with the actual construction of cards with my data
This is my first component sending everything :
<div>
{glossaire.map((carte, index) => (
<Fragment key={carte.dat_id}>
<CardFlip item={carte} tags={tags} />
</Fragment>
))}
</div>
First prop ,"item", contains information such as: a title, a definition, a search tag
Second prop, "tags", is the list of tags that a definition can have, a definition can have only one tag, right now only those tags are available : "Application", "Entreprise", "Technique" and "Télécom"
And here is the code for my second component (only the interesting part):
export default function CardFlip = ({ item, user, tags }) => {
// -- [Variables] --
// Flip front / back
const [isFlipped, setIsFlipped] = useState(false);
// Storage
const [titreDef, setTitreDef] = useState("");
const [definitionDef, setDefinitionDef] = useState("");
const [tagSelected, setTagSelected] = useState("");
// Flag for error
const [errorTitre, setErrorTitre] = useState(false);
const [errorDefinition, setErrorDefinition] = useState(false);
const [errorSelect, setErrorSelect] = useState(false);
console.log(item.dat_tag);
console.log(tags);
// -- [Handlers] --
// UPDATE
const handleTitre = (data) => {
setTitreDef(data);
setErrorTitre(false);
};
const handleDefinition = (data) => {
setDefinitionDef(data);
setErrorDefinition(false);
};
const handleSelect = (event) => {
const {
target: { value },
} = event;
setTagSelected(value);
setErrorSelect(false);
}
return (
<Grow in style={{ transformOrigin: "0 0 0" }} {...{ timeout: 1000 }}>
<div style={{ display: "flex", padding: "10px" }}>
<ReactCardFlip
isFlipped={isFlipped}
flipDirection="horizontal"
style={{ height: "100%" }}
>
<div
className={style.CardBack}
style={{ display: "flex", height: "100%" }}
>
<Card className={style.mainCard}>
<CardActions className={style.buttonFlipCard}>
<Tooltip title="Retour">
<IconButton
className={style.iconFlipCard}
disableRipple
onClick={() => setIsFlipped((prev) => !prev)}
>
<ChevronLeftIcon />
</IconButton>
</Tooltip>
</CardActions>
<CardContent>
<div className={style.divTagBack}>
<FormControl
sx={{width: "90%"}}
>
<InputLabel
id="SelectLabel"
sx={{display: "flex"}}
>
{<TagIcon />}
{" Tag"}
</InputLabel>
<Select
labelId="SelectLabel"
label={<TagIcon /> + " Tag"}
renderValue={(selected) => (
<Chip
onMouseDown={(event) => {
event.stopPropagation();
}}
key={selected}
label={selected}
icon={<TagIcon />}
/>
)}
defaultValue={item.dat_tag}
onChange={handleSelect}
>
{tags && tags.map((tag) => (
<MenuItem key={tag.dat_tag} value={tag.dat_tag}>
{tag.dat_tag}
</MenuItem>
))}
</Select>
</FormControl>
</div>
</CardContent>
</Card>
</div>
</ReactCardFlip>
</div>
</Grow>
);
};
When the user returns the card, he can change the title, description and tag of the chosen card.
My problem is with the Select.
In order to display the selected tag before any modification, I display the tag in defaultValue={item.dat_tag}
(Also tried with value and not defaultValue)
Then with my second prop, I build the list of my menu.
This is where I get my warning (which seems to extend the rendering time of the page considerably (Since it load / render like +100 definitions, getting a warning for every definition)
MUI: You have provided an out-of-range value Entreprise for the select component.
Consider providing a value that matches one of the available options or ''. The available values are "".
This is an example of what a console.logs told me about my props :
item.dat_tag
Entreprise
tags
0: {dat_tag: "Applicatif"}
1: {dat_tag: "Entreprise"}
2: {dat_tag: "Technique"}
3: {dat_tag: "Télécom"}
I already looked at several posts saying to put in a string variable my tag data (item.dat_tag) or to display my menu when it is not empty. No change.

How can I solve this problem 'No duplicate props allowed react/jsx-no-duplicate-props'

I am referring this post this post for finishing up a custom dialog.
But I keep getting warning No duplicate props allowed react/jsx-no-duplicate-props
And the following code is the place holds props.
How can I solve this problem ?
const DialogTitle = withStyles(styles)((props) => {
const { children, classes, onClose, ...other } = props;
return (
<MuiDialogTitle disableTypography className={classes.root} {...other}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton aria-label="close" className={classes.closeButton} onClick={onClose}>
<CloseIcon />
</IconButton>
) : null}
</MuiDialogTitle>
);
});
My guess is that other contains either className or disableTypography in it. Most probably it's className. So you should extract it and do something about it (let's say combine it with classes.root):
const { children, classes, onClose, className ...other } = props;
return (
<MuiDialogTitle disableTypography className={`${classes.root} ${className}`} {...other}>

How to know from which DOM click event is triggered

I have few Card component from material UI, each of them contain an EDIT button and there is a handler available for it, they are being added dynamically using Map traversing (In example, i have just hard coded two of them).
Now, i am trying to make the card editable in button click but not able to find out how to get to know from which Card, event is triggered and then make "Typography" of that editable to 'TextField'.
<CardContent>
<Typography>ID: '1'</Typography>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
Word of the Day
</Typography>
<Typography>Name: 'RAAM'</Typography>
<Typography>Blood group: 'AB+'</Typography>
<Typography>"Patient Ram is having bloodgroup AB+"</Typography>
</CardContent>
<CardActions>
<Button size="small" onClick={click}>
Edit
</Button>
</CardActions>
<CardContent>
Here is my codeSandbox example
CodeSandbox
The usual solution is to have the card pass back some identifying information or an object that you've passed it, since there's very little you can do with the React element.
If you want the DOM element, it's the currentTarget property of the event object that your click function receives.
Here's a simple example showing both with stand-ins for Card and its parent, in this case the Card component returns the id you pass it as a second argument to the click function:
"use strict";
const cards = Array.from(Array(5), (_, index) => ({
id: index + 1,
value: `Card ${index + 1}`
}));
function Parent() {
const click = (evt, id) => {
console.log(`evt.currentTarget.tagName = ${evt.currentTarget.tagName}, id = ${id}`);
};
return cards.map(({id, value}) =>
<Card
key={id}
value={value}
onClick={evt => click(evt, id)}
/>
);
}
function Card({value, onClick}) {
return <div onClick={onClick}>{value}</div>;
}
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
import React, { useRef } from "react";
import { makeStyles } from "#material-ui/core/styles";
import Card from "#material-ui/core/Card";
import CardActions from "#material-ui/core/CardActions";
import CardContent from "#material-ui/core/CardContent";
import Button from "#material-ui/core/Button";
import Typography from "#material-ui/core/Typography";
const useStyles = makeStyles({
root: {
minWidth: 275
},
bullet: {
display: "inline-block",
margin: "0 2px",
transform: "scale(0.8)"
},
title: {
fontSize: 14
},
pos: {
marginBottom: 12
}
});
export default function OutlinedCard() {
const refs = useRef([]);
const classes = useStyles();
const click = (event) => {
const { currentTarget: { id = "" } = {} } = event;
const clickedCard = refs.current[id]; // This is the card whose button got clicked
console.log(clickedCard);
};
const createRefs = (id, node) => (refs.current[id] = node);
return (
<Card className={classes.root} variant="outlined">
<CardContent ref={(node) => {createRefs('card_1', node)}}>
<Typography>ID: '1'</Typography>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
Word of the Day
</Typography>
<Typography>Name: 'RAAM'</Typography>
<Typography>Blood group: 'AB+'</Typography>
<Typography>"Patient Ram is having bloodgroup AB+"</Typography>
</CardContent>
<CardActions>
<Button size="small" id="card_1" onClick={click}>
Edit
</Button>
</CardActions>
<CardContent ref={(node) => {createRefs('card_2', node)}}>
<Typography>ID: '2'</Typography>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
Word of the Day
</Typography>
<Typography>Name: 'RAAM'</Typography>
<Typography>Blood group: 'AB+'</Typography>
<Typography>"Patient Ram is having bloodgroup AB+"</Typography>
</CardContent>
<CardActions>
<Button size="small" id="card_2" onClick={click}>
Edit
</Button>
</CardActions>
</Card>
);
}

is it possible to change only one attribute in the style props in material-ui

material-ui provides a way to change the style of a component, via the
style props:
<Card style={ {display:this.state.show} } >
<CardHeader
title="Some Card"
subtitle="Old"
/>
But this will remove all the previous default style for the card, is
it possible to change only one attribute in this case the 'display' attribute?
Yes. Since the style prop will accept an object, you can create an object dynamically and use that.
Example:
import objectAssign = require('object-assign');
render() {
const baseStyle = { color: 'black', fontSize: '20px' };
const cardStyle = objectAssign({}, baseStyle,
(this.state.show ? { color: 'red' } : {});
return (
<Card style={ cardStyle }>
<CardHeader ... />
</Card>
);
}

Categories

Resources