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

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?

Related

How to target a nested class in a different sheet with JSS?

I'm not actually sure if i am using the proper terminology. Basically i would like to have a file with a component such as:
//Foo.tsx
const useStyles = createUseStyles({
foo:{
background:'red'
}
})
const Foo = ()=>{
const classes = useStyles()
return <div className={classes.foo}/>
}
I would like to override the .foo selector, but none of the syntax worked for me:
// Bar.tsx
const useStyles = createUseStyles({
bar:{
'& $foo':{ //<-- what should i use here, if there is anything that would work at all?
background:'blue!important'
}
}
})
const Bar = ()=>{
const classes = useStyles()
return (
<div className={classes.bar}>
<Foo/>
</div>
)
}
As per documentation given here you can try something as following :
const useStyles = createUseStyles({
bar:{
'& .foo':{ //<-- what should i use here, if there is anything that would work at all?
background:'blue!important'
}
}
})
I don't know it will work or not but you can try dot(.) instead of dollar($)

React: Design pattern that uses a Ref to set style of the parent component

I would like to ask if this is a sensible component design pattern in React.
Let's say I have the following components, App, ContentContainer and WithBlueBackground. The idea is to use a Ref to set a blue background on the ContentContainer with the WithBlueBackground component.
The simplified code would look something like this.
// App.js
export function App() => {
const contentContainerRef = useRef();
return (
<ContentContainer contentContainerRef={contentContainerRef}>
<WithBlueBackground contentContainerRef={contentContainerRef}>
</WithBlueBackground>
</ContentContainer>
)
}
// ContentContainer
export function ContentContainer(props) => {
return (
<div ref={props.contentContainerRef}>
// Some content
</div>
)
}
// WithBlueBackground
export function ContentContainer(props) => {
useEffect(() => {
if (props.containerRef && props.contentContainerRef.current) {
props.contentContainerRef.current.style.backgroundColor = 'blue';
}
}, [props.contentContainerRef])
return <>{ props.children }</>;
}
This way if I want to have a green background in the content container I can create a new component that sets this style without the ContentContainer having to know about this. This increases the composability of the code which is promote in the react docs.
Nevertheless, passing the refs is a bit ugly.
My question is, is this a sensible pattern and if not is there another way to achieve what I am trying to do here.
If it is a direct child, you could just pass an update function around:
// ContentContainer
export function ContentContainer(props) {
const [backgroundColor, setColor] = React.useState("white");
return (
<div style={{ backgroundColor }}>
<ChildComponent setColor={color => setColor(color)}>// Some content</ChildComponent>;
</div>
);
}
// WithBlueBackground
export function ChildComponent(props) {
React.useEffect(() => {
props.setColor("blue");
}, []);
return <>{props.children}</>;
}
If it is deeper nested, you could use the context API. The same principle applies.enter link description here

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);

Images Rerendering inside Styled Component when Chrome Dev Tools is open

This is a bit of a strange one and not sure why it's happening exactly.
When the component mounts, I call a function that in my application makes an HTTP request to get an array of Objects. Then I update 3 states within a map method.
enquiries - Which is just the response from the HTTP request
activeProperty - Which defines which object id is current active
channelDetails - parses some of the response data to be used as a prop to pass down to a child component.
const [enquiries, setEnquiries] = useState({ loading: true });
const [activeProperty, setActiveProperty] = useState();
const [channelDetails, setChannelDetails] = useState([]);
const getChannels = async () => {
// In my actual project,this is an http request and I filter responses
const response = await Enquiries;
const channelDetailsCopy = [...channelDetails];
setEnquiries(
response.map((e, i) => {
const { property } = e;
if (property) {
const { id } = property;
let tempActiveProperty;
if (i === 0 && !activeProperty) {
tempActiveProperty = id;
setActiveProperty(tempActiveProperty);
}
}
channelDetailsCopy.push(getChannelDetails(e));
return e;
})
);
setChannelDetails(channelDetailsCopy);
};
useEffect(() => {
getChannels();
}, []);
Then I return a child component ChannelList that uses styled components to add styles to the element and renders child elements.
const ChannelList = ({ children, listHeight }) => {
const ChannelListDiv = styled.div`
height: ${listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
return <ChannelListDiv className={"ChannelList"}>{children}</ChannelListDiv>;
};
Inside ChannelList component I map over the enquiries state and render the ChannelListItem component which has an assigned key on the index of the object within the array, and accepts the channelDetails state and an onClick handler.
return (
<>
{enquiries &&
enquiries.length > 0 &&
!enquiries.loading &&
channelDetails.length > 0 ? (
<ChannelList listHeight={"380px"}>
{enquiries.map((enquiry, i) => {
return (
<ChannelListItem
key={i}
details={channelDetails[i]}
activeProperty={activeProperty}
setActiveProperty={id => setActiveProperty(id)}
/>
);
})}
</ChannelList>
) : (
"loading..."
)}
</>
);
In the ChannelListItem component I render two images from the details prop based on the channelDetails state
const ChannelListItem = ({ details, setActiveProperty, activeProperty }) => {
const handleClick = () => {
setActiveProperty(details.propId);
};
return (
<div onClick={() => handleClick()} className={`ChannelListItem`}>
<div className={"ChannelListItemAvatarHeads"}>
<div
className={
"ChannelListItemAvatarHeads-prop ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.propertyImage})`
}}
/>
<div
className={
"ChannelListItemAvatarHeads-agent ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.receiverLogo})`
}}
/>
</div>
{activeProperty === details.propId ? <div>active</div> : null}
</div>
);
};
Now, the issue comes whenever the chrome dev tools window is open and you click on the different ChannelListItems the images blink/rerender. I had thought that the diff algorithm would have kicked in here and not rerendered the images as they are the same images?
But it seems that styled-components adds a new class every time you click on a ChannelListItem, so it rerenders the image. But ONLY when the develop tools window is open?
Why is this? Is there a way around this?
I can use inline styles instead of styled-components and it works as expected, though I wanted to see if there was a way around this without removing styled-components
I have a CODESANDBOX to check for yourselves
If you re-activate cache in devtool on network tab the issue disappear.
So the question becomes why the browser refetch the image when cache is disabled ;)
It is simply because the dom change so browser re-render it as you mentioned it the class change.
So the class change because the componetn change.
You create a new component at every render.
A simple fix:
import React from "react";
import styled from "styled-components";
const ChannelListDiv = styled.div`
height: ${props => props.listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
const ChannelList = ({ children, listHeight }) => {
return <ChannelListDiv listHeight={listHeight} className={"ChannelList"}>{children}</ChannelListDiv>;
};
export default ChannelList;
I think it has to do with this setting to disable cache (see red marking in image)
Hope this helps.

React: Get a list of tags by attribute values

I am trying to make a text which displays some information upon mouse hover. For example, I have three tags with following information
<div class="body main-seq" style="display: inline;">
<span prob="67.8">
Foo
</span>
<span prob="67.8;34.6">
Bar
</span>
<span prob="67.8;34.6;52.7">
Hello
</span>
</div>
On a browser, it will look something like this
FooBarHello
Basically, when user hovers a mouse on first bit of the text (the one that corresponds to "Bar"), I want to bold all the span tags that contain "34.6" in its "prob" attribute. In this case, it would have to bold "BarHello", but leave "Foo" as it is.
After doing some Google search, this task seems pretty trivial in Javascript or jQuery, and can be done by doing something like so,
$("span[prob*='34.6']") (along with onMouseOver event or something similar)
Find an element in DOM based on an attribute value
However, I've seen many posts saying I should absolutely try to avoid using jQuery in React because React and jQuery has conflicting philosophy (React renders DOM every time the data changes whereas jQuery directly manipulates DOM). Please correct me if I am wrong though.
So my question is, how can I achieve this in React?
You could perhaps do something like this in React:
import React, { useState } from 'react';
function MyComponent(props) {
const [ isHovered, setIsHovered ] = useState(false);
const onMouseEnter = () => {
setIsHovered(true);
}
const onMouseLeave = () => {
setIsHovered(false);
}
const spans = [
{ text: 'Foo', probs: [67.8] },
{ text: 'Bar', probs: [67.8, 34.6] },
{ text: 'Hello', probs: [67.8, 34.6, 52.7] }
];
return (
<div
class="body main-seq"
style="display: inline;"
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{spans.map(({ text, probs }) => {
const isBold = isHovered && probs.includes(34.6);
return (
<span style={{ fontWeight: isBold ? 'bold' : 'normal'; }}>
{text}
</span>
);
}}
</div>
);
}
I hope this helps.
I think you're approaching this the wrong way, but it's a great exercise in "thinking in React".
You basically have these span tags that store number values. Each of them have the same responsibilities:
Store a list of number values
When clicked, set the new "criteria" values
let the others know that they should check if they should be bold or not
So let's call this component SpanComponent. We want a structure like this:
export const SpanComponent = ({ values, activeValues, setActiveValues, children }) => {
const isBold = checkForMatchingValues(values, activeValues) // returns true or false
const onMouseEnter = event => {
setActiveValues(values)
}
return (
<span style={{ fontWeight: isBold ? 'bold' : 'normal' }} onMouseEnter={onMouseEnter}>{children}</span>
)
}
Then in our main component we can manage these as so:
export const MainComponent = () => {
const [activeValues, setActiveValues] = useState([]) // empty array as default value
return (
<SpanComponent values={[68.8]} activeValues={activeValues} setActiveValues={setActiveValues}/>
<SpanComponent values={[68.8, 34.6]} activeValues={activeValues} setActiveValues={setActiveValues}/>
<SpanComponent values={[68.8, 34.6, 52.7]} activeValues={activeValues} setActiveValues={setActiveValues}/>
)
}

Resources