Is string comparison the right way to do dynamic styling for variants in React-JSS? - reactjs

I have a Sandbox here:
https://codesandbox.io/s/p2wy9pp8lx
I have some dynamic styling like this:
const styles = {
fooDisplay: props => ({
display: props.variant === "foo" ? "block" : "none"
}),
}
For a class like:
const Something = ({ classes, children, variant }) => {
return (
<div className={classes.someThing}>
<div> variant is : {variant}</div>
<div className={classes.fooDisplay}>I only display if variant is foo</div>
</div>
);
};
This does what I want.
Is using string comparison the right way to achieve this though? Or would this be bad for performance?

Best practice in my opinion is to export variant constants on each element that you can reference whenever you import the element, an example would look like:
<Button variant={Button.Variant.PRIMARY}> This is a primary button </Button>
And on Button you can do a check using the same constants this.props.variant === Variant.Primary
There's no real performance issue with string comparison, it's just a weakly typed way of getting to the same solution, and looks a little bit messier. This method means there's no room for error and it's very clear what the intent is.
If you need a bit more code to understand what I mean let me know

Related

How to solve the problem of a card being bookmarked by page using react, typescript and mui?

When clicking on the card's favorite icon, I need to favorite only one card at a time, however, this does not happen. For example, if I click on the first card on any page, all other first cards are bookmarked. So I have the problem of cards being favorited per page and I want only what I clicked to be favorited.
To solve the problem in App.tsx -> CardAction I put the following code.
<Button size="small">
<FavoriteIcon
onClick={() => changeColorFavorite(id)}
style={{ color: iconFavorite[id] ? "red" : "gray" }}
Favorite
</Button>
I declared a useState by looping through the data(.json file) and setting false as the initial value for each card.
const [iconFavorite, setIconFavorite] = useState(
[...Array(data.length).keys()].map((i) => false)
);
I also declared a function to change the state between true or false
const changeColorFavorite = (id: number) => {
const newIconFavorite = iconFavorite.slice();
newIconFavorite[id] = !iconFavorite[id];
setIconFavorite(newIconFavorite);
};
How to solve this problem? Can someone help me please? Follow the code in codesandbox.
Code codesandbox here
Why it happened
The id passed to changeColorFavorite is actually the index of the array being mapped, hence the same order of card is marked as favorite on every page.
The function you used for the this map:
{_DATA.currentData().map((item, id) =>
It defines id as the index of item in this array. The actual value you are looking for should be item.id.
More about map()
Solution
Instead, pass item.id should get the desired result:
onClick={() => changeColorFavorite(item.id!)}
(I have to add ! to override type check here, but you can refactor the data type later to make sure id is mandatory and is never undefined)
On top of that, the onClick is currently on your FavoriteIcon, but you might want to move it to the Button so that it does not just trigger by clicking on the icon:
<Button size="small" onClick={() => changeColorFavorite(item.id!)}>
<FavoriteIcon style={{ color: iconFavorite[item.id!] ? "red" : "gray" }} />
Favorite
</Button>
Hopefully this achieves the desired result without major changes.
That being said, I would suggest that the implementation of favorites could use some reconsideration in the future, if you need to expand more feature for those cards for example.
Small refactor
It seems that you're defining an array of false like this, and an error message occurs:
const [iconFavorite, setIconFavorite] = useState(
[...Array(data.length).keys()].map((i) => false)
);
Despite that there might be better solution than handling these states as an array of Boolean, at this moment you could refactor the above code into this:
const [iconFavorite, setIconFavorite] = useState([...data].fill(false))
It does the same but cleaner and without error syntax.
More about fill()
Hope this will help!

How to implement the following in the react js way

enter image description here
Hi I am trying to change an image onWheel event and so far the code works in a bad way, can anyone suggest ways to improve. I am trying to recreate the zoom out/in effect as seen on the mrpops.ua/en website. When the wheel event triggers the count increases or decrease on direction. Is there maybe a way to use the number count to do some fancy math instead of using it to change animation name.
Edit
If as you say in the comments you have 6 different animations, one way of implementing that would be:
const Contact = () => {
const [count, setCount] = useState(0)
/* Code for updating the counter */
const handleWheel = ({ deltaY }) => {
setCount((currentCount) =>
deltaY > 0 ? currentCount + 1 : currentCount - 1
);
};
useEffect(() => {
console.log(count);
}, [count]);
// I suggest to place all the possible animations in an object, and then retrieve them with object[1], etc. You could also use switch, but this option is cleaner IMO.
const style = {
1: "animation 1"
2: "animation 2"
...
6: "animation 6"
}
return (
<header onWheel={handleWheel}>
<img className="header-img" style={{animation: style[count]}}>
</img>
</header>
)}
Original answer
I'll suggest the following to "translate" the screenshot code into actual React code:
const Contact = ({ counter }) => (
<header>
<img className="header-img" style={{animation: counter === 1 ? "scaleUp 2s ease forwards" : "scaleUp2 2s ease forwards"}}>
</img>
</header>
)
Mind that with React you access style as an object, this is why I'm using {} first to let React know I'll insert JS in HTML, and second because it's a object like: {animation: ""}
Another option, without editing the node style directly, is with conditional rendering. Personally, I prefer this one more:
const Contact = ({ counter }) => (
<header>
// We use classes with BEM to apply the specific animations
{counter === 1 && <img className="header-img header-img--one"></img>}
{counter === 2 && <img className="header-img header-img--two"></img>}
</header>
)
About the wheel effect, since you are using a console.logs I'm not sure how do you intend to implement that. However, I'll share a SO link that might help you with that:
Mouse wheel events in Reactjs
It is preferable to do as suggested in this post (use onWheel or onScroll) rather than inserting event listeners directly.
The same way, it is also preferable to completely avoid query selectors and any other way of imperative coding (if you are unfamiliar with the terms, Google differences between imperative and declarative programming).

How to pass custom class name on react and use scss?

I am trying to create a react component that has the class name as props being passed,
I have managed sending the props part successfully, however when I try to set the scss for the updated class name, I could not find a way to fix that, all of your input is appreciated.
Code of the component
Code of injecting the custom style class
Styles of the Variation
Output
Output of the Variation Class
Output of the Stylesheet
not sure what I am missing to connect all of them.
Thanks in advance.
As mentioned by Phil, your SCSS should be using &.secondary as the selector.
As for the difference in your scss class IDs and them not matching when you pass ${variant} to the className, your issue is that you are passing a raw string as the variant to the className and are using CSS modules for your SCSS (this is what handles namespacing them/adding the unique characters). To fix this, you need to use the imported style rather than a raw string for the className.
The way I usually address this is with an enum as the prop for different variants, or if there are only two a boolean, and then apply the imported style accordingly. A quick example of this, using your code would be:
const SectionTitle: FC<SectionTitleProps> = ({ title, isSecondary = false }) => (
<h3
className={`${styles.SectionTitle}${isSecondary ? styles.secondary : ''}`}
...
>
...
</h3>
);
I also find the classnames library helpful for this. It allows you to achieve the above with something I find a bit more readable, such as:
<h3
className={classNames(styles.SectionTitle, { [styles.secondary]: isSecondary } )}
...
>
...
</h3>
EDIT: Also including an example using classnames with an enum for different variants:
enum TitleVarient {
Default,
Secondary,
Accent,
}
const SectionTitle: FC<SectionTitleProps> = ({
title,
variant = TitleVarient.Default,
}) => (
<h3
className={classNames(styles.SectionTitle, {
[styles.secondary]: variant === TitleVarient.Secondary,
[styles.accent]: variant === TitleVarient.Accent,
})}
...
>
...
</h3>
);

How can I use a parameter variable's literal value inside a Javascript function in React JS?

I am creating a form using React JS and I want some of the sections in the form to be dynamically added/removed using +/- button(s). I have created a custom Hook "inputs" which is a collection (object) of the data input from all the sections in the form. inputs contains an array for a section that I want to be dynamically added/removed. e.g., workInfos is one such section:
const [inputs, setInputs] = useState({workInfos: [],
educationInfos : [],
basicInfo : {},
skillInfo : {}});
I have created a button to add the workInfo section dynamically and assigned a function "handleDynamicAddition" to its onClick property:
<Button className = "button" variant="contained" color="secondary" style = {buttonStyle} onClick = {props.handleDynamicAddition()} >+</Button>
handleDynamicAddition function looks like:
const handleDynamicAddition = () => {
setInputs((inputs) => ({...inputs, workInfos : [...inputs.workInfos, {}]}));
}
This works fine as long as I use it for just one section.
I want to use the same function for all the sections that I want to add/remove dynamically, say, educationInfos.
For different buttons, I can pass the specific section that I want to add dynamically using that button, as a parameter to handleDynamicAddition but how do I use this parameter's actual value inside setInputs?
Just off the top of my head, you try something like this
Button:
<Button
className="button"
variant="contained"
color="secondary"
style={buttonStyle}
name={/*one of the objects keys as a string, like: "workInfos", "educationInfos", etc.*/}
onClick={handleDynamicAddition}
>
+
</Button>
Handle:
const handleDynamicAddition = (evt) => {
const {name} = evt.currentTarget;
setInputs((inputs) => ({
...inputs,
[name]: [...inputs[name], {}]
}));
};
This pulls the "name" attribute from the clicked button and uses that to help assign "added sections" to the inputs object.
Kinda want to add this. If the form is pretty complicated, you're probably going to want to split things up. It's nice to have everything be "all in one place" but this sorta code should only really be used with primitive values like: [name]: value where value is a string or number. If you try to force this kind of thing to work with "objects inside of objects inside of arrays" it's going to become really unreadable compared to if you just broke things down and split them up.
Also, you might want to use a useReducer here, or even maybe look into Redux, instead of a useState. Depending on how complicated the application is going to get.

Im trying to build a menu that has collapsible options, but the dom does not update even when state data updates

const [arrayOfQuestions, setArrayOfQuestions] = useState([
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
])
const toggleItemOpenAndClose = (e) => {
let array = arrayOfQuestions
array[e.target.id].hidden = !array[e.target.id].hidden
setArrayOfQuestions(array)
}
return (
<div>
<Layout
bgImage={metaData.heroImage.childImageSharp.fluid}
header='Frequently Asked Questions'>
<div className='page-container'>
<div className="content-container">
{
arrayOfQuestions.map((question,i) => {
return (
<div id={i} key={`id${i}`} onClick={toggleItemOpenAndClose} className='block-container'>
<div id={i} className='white smallerheader'>
{question.q}
</div>
{
question.hidden ?
null :
<div id={i} className='white paragraph'>
<br/>
{question.a}
</div>
}
</div>
)
})
}
</div>
</div>
</Layout>
</div>
)
}
Im using Gatsby and react hooks.
Im trying to build a collapsible menu (an faq) sort of like a menu, so that when you click on one of the options, it expands and shows you the answer associated with the question. However, I'm having trouble making it work in real time. whenever i click on the option, my data updates, but the dom itself doesnt update when i click on the option. then, when i change something in the code, the app updates (hot reloader?) and then the dom updates. As far as i understand it, when i change state in real time, the dom should also update in real time, but I can't understand why its not in this case. Has anyone experienced something like this before?
You should not be updating state directly. This should be your toggle code
const toggleItemOpenAndClose = (e) => {
// This will create a new array and with all new objects to preserve immutability
let newArray = arrayOfQuestions.map(x => {
return {... x}
}
newArray[e.target.id].hidden = !newArray[e.target.id].hidden
setArrayOfQuestions(newArray)
}
Make a copy arrayOfQuestions like so,
let array = [...arrayOfQuestions]
Why ?
What you're updating is an object property but it's still belongs to the same array ref. When you spread over the array, you will unpack the object references in your array and pack them in a new array due to those [] and then setting this new array will actually trigger the render.
In case you want to make copy of the objects as well in your array, use map as suggested by #Doug Hill which I think looks better.
But in your case simply spreading will also work for now. Things get messy when there are nesting depths of objects and then consider using libraries which supply you with deep cloning utilities.

Resources