Dynamic className manipulation in React - javascript

I am havving a problem with dynamic class change in my react component. In a simple slider i want to display only selected image.
My component looks like this:
import React, { useState } from 'react';
import './Slider.scss';
import SliderImage from './SliderImage';
import photo1 from '../../../Images/photo1.jpg';
import photo2 from '../../../Images/photo2.jpg';
import photo3 from '../../../Images/photo3.jpg';
import photo4 from '../../../Images/photo4.jpg';
import photo5 from '../../../Images/photo5.jpg';
function Slider() {
let slideArr = [
{
id: 1,
src: photo1,
text: 'Far far away, behind the word mountains',
alt: 'wedding_photo',
},
{
id: 2,
src: photo2,
text: 'Far far away, behind the word mountains',
alt: 'wedding_photo',
},
{
id: 3,
src: photo3,
text: 'Far far away, behind the word mountains',
alt: 'wedding_photo',
},
{
id: 4,
src: photo4,
text: 'Far far away, behind the word mountains',
alt: 'wedding_photo',
},
{
id: 5,
src: photo5,
text: 'Far far away, behind the word mountains',
alt: 'wedding_photo',
},
];
const [x, setX] = useState(0);
const goLeft = () => {
console.log(x);
x === 0 ? setX(-100 * (slideArr.length - 1)) : setX(x + 100);
};
const goRight = () => {
console.log(x);
x === -100 * (slideArr.length - 1) ? setX(0) : setX(x - 100);
};
return (
<div className='Slider'>
{slideArr.map((item) => {
return (
<div
className={`Slider__Slide + ${item.id - 1 === x ? 'active' : ''}`}
key={item.id}
style={{ transform: `translateX(${x}%)` }}>
<SliderImage
classTxt={'Slider__Slide-txt'}
classImg={'Slider__Slide-img'}
src={item.src}
text={item.text}
alt={item.alt}
/>
</div>
);
})}
<button id='goLeft' onClick={goLeft}>
left
</button>
<button id='goRight' onClick={goRight}>
right
</button>
</div>
);
}
export default Slider;
So i have array with photos, that have ids. Based on Id I am showing an image in SliderImage component. This part works good. Images are changing, but I am unable to dynamicly change classNames
Here i want to change class
className={`Slider__Slide + ${item.id - 1 === x ? 'active' : ''}`}
but onClick is not adding active to new image.
Thanks for any help

You are using string literals in a way as if you were concatenating two strings. Everything inside the back ticks will be as it is if they are not variables.
className={`Slider__Slide + ${item.id - 1 === x ? 'active' : ''}`}
Here you are using the plus sign and it will be classname + active when it is evaluated what you want to do is basically remove the plus sign.
className={`Slider__Slide ${item.id - 1 === x ? 'active' : ''}`}
I would suggest you to use the classnames library for such use cases when you have to do a conditional chaining for classes. The following example demonstrates how it would be let's say our item object looks like the following and x is defined in a way that it would evaluate to true.
const item = {
id: 3
};
const x = 2;
const classes = classNames({ firstClassName: true, bar: item.id - 1 === x });
console.log(classes) // => 'className active'

This is fundamentally a string concatenation question. The JSX expression you have has spaces and a + as text inside a template literal. Either use + with string literals or use a template literal, but not both.
Using string literals:
className={"Slider__Slide " + (item.id - 1 === x ? "active" : "")}
Using a template literal:
className={`Slider__Slide ${item.id - 1 === x ? "active" : ""}`}

I'll suggest two things
Change the meaning of 'x' to be the current id instead of the position
Replace the "+" from string template, since it's resulting into a literal "+" Slider__Slide + ${item.id - 1 === x ? 'active' : ''} => "Slider__Slide + active" or "Slider__Slide + "
const [x, setX] = useState(0);
const goLeft = () => {
console.log(x);
x === 0 ? setX(slideArr.length - 1) : setX(x);
};
const goRight = () => {
console.log(x);
x === (slideArr.length - 1) ? setX(0) : setX(x);
};
on return
<div
className={`Slider__Slide${item.id - 1 === x ? 'active' : ''}`}
key={item.id}
style={{ transform: `translateX(${x * (-100}%)` }}>
<SliderImage
classTxt={'Slider__Slide-txt'}
classImg={'Slider__Slide-img'}
src={item.src}
text={item.text}
alt={item.alt}
/>
</div>

Related

How to use useSprings?

I'm trying to create an animated text char by char to bounce from right to left, but I need to add a different delay to each char.
So far the animation looks nice the problem is that the delay is not updating for each char.
This is my code:
import React from "react";
import { useSprings, animated } from "react-spring"
import s from '../../styles/pages/home.module.scss'
// HERE CREATE AN ARRAY CHAR BY CHAR INCLUDING " ", FROM A STRING
let textString = "Random text for this example."
let textArray = Array.from(textString)
const HeroText = () => {
const springs = useSprings(
textArray.length,
textArray.map((item, i)=> ({
id : i,
from : {opacity: 0, translateX : '1000px'},
to: {opacity: 1, translateX : '0px'},
delay: (0.5 + i / 10),
config: { mass: 4, tension: 200, friction: 30}
}))
)
let elements = springs.map((spring, i) => {
console.log(spring)
return(
<animated.span key={i} style={{...spring}}>
{textArray[i] === ' ' ? <span> </span> : textArray[i]}
</animated.span>
)
})
return(
<div className={s.heroText}>
<h1 className={"my-heading divided-heading"}>
{elements}
</h1>
</div>
)
}
export default HeroText
On the console.log(spring), I can actually see that all the objects have different "delay" values, but on render they all animate at the same time, so it does not look like the text is animated char by char.
I have read the react-spring documentation but I did not find it to be very helpful, so if someone can help me understand what I'm missing I would be glad.
Thanks!
so after allot of research I found out that the best way to do what I was trying to do was to use "useTransition".
This was the final code, working properly.
import React from "react";
import { animated, useTransition } from "react-spring"
import s from '../../styles/pages/home.module.scss'
// HERE CREATE AN ARRAY CHAR BY CHAR INCLUDING " ", FROM A STRING
let textString = "Hi, I'm Stephane Ribeiro. Want to como on a full stack journey trough my mind?!"
let textArray = Array.from(textString)
let objArray = textArray.map((item, i) => {
return ({
char : item,
key: i
})
})
const HeroText = () => {
const tranConfig = {
from : {opacity: 0, translateX : '1000px'},
enter: {opacity: 1, translateX : '0px'},
config: { mass: 4, tension: 200, friction: 30},
trail: 50
}
const transition = useTransition(objArray, tranConfig)
let elements = transition((values, item) => {
return(
<animated.span key={item.key} style={values}>
{item.char === ' ' ? <span> </span> : item.char}
</animated.span>
)
})
return(
<div className={s.heroText}>
<h1 className={"my-heading divided-heading"}>
{firstElements}
</h1>
</div>
)
}
export default HeroText
How did you apply different delay time for each char. I have seen your last code there is no different delay time

Read/Show more or less of html content or normal text using single reusable React JS component

When I was working with HTML content displayed with Read more and Read less(Show more or Show less) on my webpage, I struggled a lot googling to get a quick solution. Unfortunately, I failed to collect a single source of the solution. I started writing my own solution and decided to share my own solution if it helps someone.
At first, You need to install "html-truncate"
npm i html-truncate
import React, {useState, useCallback, useEffect} from 'react';
import truncate from "truncate-html";
const ReadMoreMaster = (props) => {
let {byWords,parentClass, readLess, readMore, length, ellipsis, spaceBefore} = props;
const [showMore, setShowMore] = useState(false);
truncate.setup({
byWords: !!byWords,
ellipsis: ellipsis ? ellipsis : '',
})
const [state, setState] = useState({
showRealData: false,
realData: props.children,
truncatedData: truncate(props.children, length ? length : 2)
});
const handleShowData = useCallback(() => {
setState(prevState => ({
...prevState,
showRealData: !prevState.showRealData
}));
}, [setState]);
useEffect(() => {
if(byWords && props.children) {
const textDetails = props.children.replace(/<[^>]+>/g, '');
const arr = textDetails.split(" ");
if(arr.length > length) {
setShowMore(true);
}
}
}, [byWords])
return (
<div className={parentClass}>
<div
dangerouslySetInnerHTML={{
__html: `${
!state.showRealData ? state.truncatedData : state.realData
}`
}}
/>
{spaceBefore === false ? '' : ' '}
{
showMore &&
<a href={void (0)} className="read-more" onClick={handleShowData}>
{!state.showRealData ? readMore ? readMore : 'Read More' : readLess ? readLess : 'Read Less'}
</a>
}
</div>
);
};
export default ReadMoreMaster;
Now call the component like below:
<ReadMoreMaster
byWords={true}
length={150}
ellipsis="..."
>
<p>your content with or without HTML tag</p>
</ReadMoreMaster>
Supported props are:
// ...Supported Props...
// parentClass="test" || default value : null
// byWords={true} || default value : false
// ellipsis="..." || default value : null
// length={5} || default value : 2
// readMore="See More" || default value : Read More
// readLess="See less" || default value : Read Less
// spaceBefore={false} || default value : true
All done. Enjoy!!!

Can I change the order of li elements conditionally in React

I have a list with side menu options, some of them are normal categories, some of them are static, here is my code:
//const type = the type is either categories or static
{items.map((item, index) => {
return (
<li>
{type !== 'categories' ? (
<> Static </>
) : (
<> Category </>
)}
</li>
);
})}
So in the items that are of type static I have a variable that comes from the back end which is called display, its value could be either 'top' or 'bottom', what I want to do is, if the type is equal to 'static', to move the order of the li to the value item.display has, for example:
<li>----</li> //type static //item.display: top
<li>++++</li> //type static //item.display: bottom
<li>""""</li> //type categories //item.display doesn't exist here
<li>::::</li> //type categories //item.display doesn't exist here
In the example above the li filled with the symbol '+' has a value of display: bottom,
so it should go to the bottom of the list and the list should now look like that:
<li>----</li> //type static //item.display: top
<li>""""</li> //type categories //item.display doesn't exist here
<li>::::</li> //type categories //item.display doesn't exist here
<li>++++</li> //type static //item.display: bottom
How can I implement that logic, I already tried doing it with inline styles like so:
<li style={{order: item.display === "top" ? items.length.toString() : "1"}}>
I am not even sure if this is a viable HTML, but it didn't work anyway, what are your suggestions for solving this problem?
You can control the order using items.sort() before you map it into virtual dom nodes.
Example:
import React from 'react'
function App() {
let items = [
{ text: '----', type: 'static', display: 'top' },
{ text: '++++', type: 'static', display: 'bottom' },
{ text: '""""', type: 'categories' },
{ text: '::::', type: 'categories' },
]
return (
<ul>
{items
.sort((a, b) =>
a.display === b.display
? 0
: a.display === 'top'
? -1
: a.display === 'bottom'
? 1
: 0,
)
.map(item => (
<li>
{item.type !== 'categories' ? (
<> Static </>
) : (
<> Category </>
)}
</li>
))}
</ul>
)
}
export default App
output:
<ul>
<li>----</li>
<li>""""</li>
<li>::::</li>
<li>++++</li>
</ul>
A possible solution would be that you have a mapping from string to number and a little helper function
const displayMap = {
top: 0,
default: 1,
bottom: 2
};
const compareDisplay(a,b){
let disp1 = a.display ?? 'default';
let disp2 = b.display ?? 'default';
return displayMap[disp1] > displayMap[disp2] ? 1 : -1;
}
in case if display is not defined, it will take default, which is mapped to the middle value
In case display also contains other values than top or bottom, you need to add a bit of logic to the helper function
you can use this mapping, to sort your objects before mapping
{
items.sort((a,b) => compareDisplay(a,b)).map((item, index) => {
return (
<li>
{
type !== 'categories' ?
(<> Static </>) :
(<> Category </>)
}
</li>
);
})}

React re-rendering every cell of the table

I have a really big rerender problem in every cell of the table because of the handleClick function even used useCallback.
My cell component like; (wrapped with React.memo())
<StyledTableCell onContextMenu={() => handleClick()} style={{ backgroundColor: `${(flash && data) ? 'yellow' : color}`, transition: `${flash ? 'none' : '1s'}` }}>
<div className='cell-size' >
{data}
</div>
</StyledTableCell >
export const MemoizedDataCell = React.memo(DataCell)
and this cell goes to the upper level like ;
<MemoizedDataCell handleClick={() => onCellClick(selectedOrders, i, Boolean(val))} data={val} key={colName + i} color={cellColor(i, z)} />
And finally, in the top parent, I use this function for the child ;
const onCellClick = React.useCallback((myOrder: any, i: number, isFilled: boolean) => {
setCanDeal(isFilled)
let side = i < 3 ? 'bids' : 'asks'
console.log(myOrder)
if (i < 0) {
return null
}
if (!myOrder.asks && !myOrder.bids) {
const detail = {
instrName: myOrder.instName, maturity: myOrder.maturity, side: side.charAt(0).toUpperCase() + side.slice(1, -1), contract: `${myOrder.instName} ${myOrder.maturity}`,
price: '0', quantity: '0', totalQuantity: '0', tradableQuantity: '0'
}
return setOrderDetail(detail)
}
let orderID = myOrder[`${side}`][0]?.OrderID
let instrName = myOrder[`${side}`][0]?.InstName
let maturity = myOrder[`${side}`][0]?.FirstSequenceItemName
let contract = `${instrName} ${maturity}`
let broker = myOrder[`${side}`][0]?.Broker
let price = myOrder[`${side}`][0]?.Price
let quantity = myOrder[`${side}`][0]?.Volume
let timeInForce = myOrder[`${side}`][0]?.OrderType.replace(/([A-Z])/g, ' $1').trim()
let lastUpdTime = new Date(myOrder[`${side}`][0]?.DateTime).toLocaleString()
let tradableQ = '-'
let totalQ = '-'
let priceStat = myOrder[`${side}`][0]?.Status
side = side.charAt(0).toUpperCase() + side.slice(1, -1)
const detail = {
orderID: orderID, instrName: instrName, maturity: maturity, side: side, contract: contract,
price: price, priceStatus: priceStat,
quantity: quantity, totalQuantity: totalQ,
tradableQuantity: tradableQ, broker: broker, lastUpdate: lastUpdTime, timeInForce: timeInForce
}
setOrderDetail(detail)
}, [])
Any help ll be appreciated. Thank you.
How are you memoizing your Cell ?
If you use memo or useMemo, it does a === comparison with each previous props
And as you're doing:
<MemoizedDataCell handleClick={() => onCellClick(selectedOrders, i, Boolean(val))} .. />
You are creating a new function at each render for handleClick, so MemoizedDataCell will be re-rendered.
So you need to memoize this function
const _handleClick = useCallback(() => onCellClick(...), []) // With good values in dependency array
<MemoizedDataCell handleClick ={_handleClick} />
This way, you're passing as prop the same function reference at each render
Also note that doing something like:
<Component style={{ backgroundColor: ... }} />
Will also re-renders every time as you are creating a new Object each time

Persistent Victory Charts VictoryVoronoiContainer tooltips on click

I'm implementing a combination VictoryLine and VictoryScatter chart with Victory-Charts using VictoryVoronoiContainer for displaying values at the mouse hover location. I need to make these hover details persist on click at multiple locations.
Example of just hover data: https://formidable.com/open-source/victory/docs/victory-voronoi-container/
Specifically, if a user clicks while a given popup is active, using VoronoiDimension='x' in this case, the hover popup details remain visible at their X coordinate. In a perfect world, any number of these would be visible.
Using events (https://formidable.com/open-source/victory/guides/events/). I can sort of fake it on a scatter point click (https://jsfiddle.net/kn6v9357/3/) but with the voronoidimension hover it's difficult to see the points and you have to be awfully precise with the click. Plus, when there's overlap you only trigger a click on the top layer, so overlapping data isn't shown on what persists.
Any suggestions or ideas?
Code for VictoryScatter events in from the jsFiddle (note this doesn't do what I want) with just a scatter component to keep it simple:
<VictoryChart
containerComponent = { <VictoryVoronoiContainer
voronoiDimension="x"
//voronoiBlacklist={['Scatter0','Scatter1','Scatter2',]}
labels={
(d) => {
return (
`${d.market}: ${d.metric1}`
)
}
}
/> }
>
<VictoryAxis
style = {{tickLabels: {fontSize: 8,angle: -15}}}
label = 'Date'
/>
<VictoryAxis dependentAxis
label = {this.state.metric === 'metric1' ? 'Metric 1' : 'Metric 2'}
style = {{tickLabels: {fontSize: 8}, axisLabel: {padding: 40}}}
axisLabelComponent = { <VictoryLabel style = {{fontSize: 12}} dx = {20} /> }
/>
{ this.viewablePlaces.map((place, i) =>
<VictoryGroup
animate = {{easing: "cubic",duration: 500,onLoad: {duration: 0}}}
key = {place + String(i) + 'Group'}
data = {this.get_data(place)}
x = {(d) => moment(d.date).format('MMM YY')}
y = {this.state.metric === 'metric1' ? 'metric1' : 'metric2'}
>
<VictoryScatter
key = {place + String(i) + 'Scatter'}
name = {"Scatter"+i}
style = {{
data: {fill: "#455A64",cursor: "pointer"},
labels: {fontSize: 12,padding: 2}
}}
size = {2.5}
labels={(d) => d.market + ': ' + String(d.metric2)}
labelComponent = {
<VictoryTooltip
orientation = {"top"}
pointerLength = {5}
pointerWidth = {3}
cornerRadius = {3}
/>
}
events={[
{
target: "data",
eventHandlers: {
onMouseOver: (props) => {
return [
{
target: "labels",
mutation: {active: true}
}
];
},
onMouseOut: (props) => {
return [
{
target: "labels",
mutation: (props) => props.name === 'clicked' ? {name: 'clicked', active: true} : null
}
];
},
onClick: (props) => {
return [
{
target: "data",
mutation: (props) => props.style.fill === 'orange' ?
null : {style: Object.assign({}, props.style, {fill: 'orange'}), size: 3.5}
},
{
target: "labels",
mutation: (props) => props.name === 'clicked' ?
null : {name: 'clicked', active: true}
}
];
}
}
}
]}
/>
</VictoryGroup>
)}
</VictoryChart>
Edit:
Here's another example with persistent points, but I need to find a way to also make the labels persist.
https://codesandbox.io/s/oq1w9xj8q6

Categories

Resources