How can I setState from inside a render function in react - javascript

i know you are supposed to keep render functions pure but I have a special case where I need to pass some values and update the state inside a render function but i am getting the following error message:
Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state
Code:
import Carousel from 'react-elastic-carousel';
import top from '../images/top.png';
import ArrowRight from '../images/arrowRight.svg';
import React, {Component} from 'react';
class contentSlider extends Component {
state = {
disabled: '',
leftArrow: false,
rightArrow: true
}
sliderData = [
{
title: 'POLO',
typeOfcontent: 'Mens T-Shirt',
rrp: '£105',
ourPrice: '£55',
salePrice: '£45',
image: top
},
{
title: 'POLO',
typeOfcontent: 'Mens T-Shirt',
rrp: '£105',
ourPrice: '£55',
salePrice: '£45',
image: top
},
{
title: 'POLO',
typeOfcontent: 'Mens T-Shirt',
rrp: '£105',
ourPrice: '£55',
salePrice: '£45',
image: top
},
{
title: 'POLO',
typeOfcontent: 'Mens T-Shirt',
rrp: '£105',
ourPrice: '£55',
salePrice: '£45',
image: top
},
{
title: 'POLO',
typeOfcontent: 'Mens T-Shirt',
rrp: '£105',
ourPrice: '£55',
salePrice: '£45',
image: top
},
{
title: 'POLO',
typeOfcontent: 'Mens T-Shirt',
rrp: '£105',
ourPrice: '£55',
salePrice: '£45',
image: top
},
{
title: 'POLO',
typeOfcontent: 'Mens T-Shirt',
rrp: '£105',
ourPrice: '£55',
salePrice: '£45',
image: top
}
]
breakPoints = [
{ width: 2, itemsToShow: 2, itemsToScroll: 2 },
{ width: 550, itemsToShow: 3, itemsToScroll: 3},
{ width: 850, itemsToShow: 4, itemsToScroll: 3 },
{ width: 970, itemsToShow: 4, itemsToScroll: 3 },
{ width: 1150, itemsToShow: 5, itemsToScroll: 3 },
]
setDirection = (slideDirection) => {
switch(slideDirection) {
case "next":
this.carousel.slideNext();
let slideNext = document.getElementById('slider-move');
if(slideNext.classList.contains('test-right')) {
slideNext.classList.remove('test-right');
slideNext.classList.add('test-left');
}
break;
case "previous":
this.carousel.slidePrev();
let slidePrevious = document.getElementById('slider-move');
if(slidePrevious.classList.contains('test-left')) {
slidePrevious.classList.remove('test-left');
slidePrevious.classList.add('test-right');
}
break;
}
}
getAmountOfPages = (pages, activePage ) => {
console.log(activePage)
let firstItem = pages[0];
let [lastItem] = pages.slice(-1);
if(firstItem === activePage) {
this.setState({
leftArrow: false,
rightArrow: true,
})
} else if(lastItem === activePage) {
this.setState({
leftArrow: true,
rightArrow: false,
})
} else {
this.setState({
leftArrow: true,
rightArrow: false,
})
}
// get first item in the array and compare it to the active page
// get the last element in the array and compare it to the active page
}
render() {
return (
<div className="content-slider-wrapper">
<div className="content-slider-title">
<span>PRODUCTS OF THE WEEK</span>
</div>
<div className={`${this.sliderData.length === 5 ? 'd-xl-none' : ''} arrow-container`}>
<img onClick={() => this.setDirection("previous")} className="arrow-left" src={ArrowRight} />
<img onClick={() => this.setDirection("next")} src={ArrowRight} />
</div>
<div className={`${this.sliderData.length === 5 ? 'mt-xl-5' : ''} content-slider-container`}>
<div className="test-right" id="slider-move">
<Carousel
ref={ref => (this.carousel = ref)}
breakPoints={this.breakPoints}
disableArrowsOnEnd={true}
renderPagination={({ pages, activePage, onClick }) => {
this.getAmountOfPages(pages, activePage);
return (
<div className={`${this.sliderData.length === 5 ? 'd-xl-none' : ''} black-slider-container`}>
{pages.map(page => {
const isActivePage = activePage === page
return (
<div className={isActivePage ? 'black-slider' : 'blank-slider'}
key={page}
onClick={() => onClick(page)}
active={isActivePage}
/>
)
})}
</div>
)
}}
>
{this.sliderData.map((item, index) => (
<div key={index} className="carousel-item-container">
<div className="carousel-image-container">
<img src={top} />
</div>
<div className="carousel-text-container">
<ul>
<li className="carousel-text-container-title">{item.title}</li>
<li className="carousel-text-container-text">{item.typeOfProduct}</li>
<li className="carousel-text-container-text line-through">RRP {item.rrp}</li>
<li className="carousel-text-container-text line-through">Our Price: {item.ourPrice}</li>
<li className="carousel-text-container-text">Sale Price: {item.salePrice}</li>
</ul>
</div>
</div>
))}
</Carousel>
</div>
</div>
</div>
)
}
}
export default contentSlider;
I need to call this function
this.getAmountOfPages(pages, activePage);
which is inside my render to update the state when needed however it dont work any ideas....

I am Brazilian and therefore I speak Portuguese, but I will use the translator to try to help you.
By calling this function the way you are trying to do it, you would fall into an infinite loop. Look:
You update the state inside the render () method;
The render () method is called due to the state update;
The render method updates the state;
all over again
I've already used this library, so I'll show you how I did it:
<Carousel
itemsToShow={7}
renderArrow={renderArrow}
enableMouseSwipe={false}
pagination={false}
breakPoints={[
{
width: 450,
itemsToShow: 3,
enableSwipe: true,
enableMouseSwipe: true,
itemsToScroll: 3,
renderArrow: () => <div />,
},
{
width: 500,
itemsToShow: 3,
},
{ width: 620, itemsToShow: 4 },
{ width: 720, itemsToShow: 5 },
{ width: 850, itemsToShow: 6 },
{ width: 1150, itemsToShow: 7 },
]}
>
{products &&
products.map((p) => (
<div key={p.docId} className="products-prod">
<img
onClick={() => addProductCart(p)}
alt={p.descricao}
src={`data:image/png;base64,${p.thumbnail}`}
className={`shadow2 ${
!cart.includes(p.docId) && adding.includes(p.docId)
? `pulse`
: ``
}`}
/>
<section onClick={() => addProductCart(p)}>
{cart.includes(p.docId) ? (
<img src={ICONS.shoppingFilled} />
) : (
<img src={ICONS.shoppingOutlined} />
)}
</section>
<article>
<h5>R$</h5>
{p.preco && formatCash(p.preco, true)}
</article>
</div>
))}
</Carousel>
And:
const renderArrow = ({ onClick, type, isEdge: noMoreItems }) => (
<Button
onClick={onClick}
style={{
margin: "auto 0",
borderRadius: "100px",
background: COLORS.primary,
borderColor: COLORS.primary,
width: "30px",
minWidth: "30px",
height: "30px",
minHeight: "30px",
padding: 0,
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
type="primary"
>
{type === consts.PREV ? (
<CaretLeftOutlined style={{ margin: "2px 2px 0 0" }} />
) : (
<CaretRightOutlined style={{ margin: "2px 0 0 2px" }} />
)}
</Button>
);

Related

Trouble mapping an Array of data to Images in React

I have a map that looks like this
I am using .map() to try to produce them as images like so:
{theLinks.map((item, indx) => (
<img key={indx} src={item.src} alt={item.label} />
))}
Nothing is getting returned, if I mess with it I can get a single img to return with no valid source and the alt is "unknown".
make sure to add your mapped array theLinks after or inside a return function,
for example, this will NOT work :
export default function App() {
const theLinks = [
{ lable: "Daily Mix", src: "https://flif.info/example-images/fish.png" },
{ lable: "Legit", src: "https://flif.info/example-images/fish.png" },
{ lable: "SCL", src: "https://flif.info/example-images/fish.png" }
];
{theLinks.map((item, indx) => (
<img
style={{ width: "50%", border: "1px solid #ccc" }}
key={indx}
src={item.src}
alt={item.label}
/>
))}
}
SOLUTION 1
this will work (renders the mapped array and other elements too):
export default function App() {
const theLinks = [
{ lable: "Daily Mix", src: "https://flif.info/example-images/fish.png" },
{ lable: "Legit", src: "https://flif.info/example-images/fish.png" },
{ lable: "SCL", src: "https://flif.info/example-images/fish.png" }
];
return (
<>
<h1> thanks Phil for your suggestion! </h1>
{theLinks.map((item, indx) => (
<img
style={{ width: "50%", border: "1px solid #ccc" }}
key={indx}
src={item.src}
alt={item.label}
/>
))}
;
</>
);
}
SOLUTION 2
this will work (renders only the mapped array)
export default function App() {
const theLinks = [
{ lable: "Daily Mix", src: "https://flif.info/example-images/fish.png" },
{ lable: "Legit", src: "https://flif.info/example-images/fish.png" },
{ lable: "SCL", src: "https://flif.info/example-images/fish.png" }
];
return theLinks.map((item, indx) => (
<img
style={{ width: "50%", border: "1px solid #ccc" }}
key={indx}
src={item.src}
alt={item.label}
/>
));
}
Try this
{theLinks.map((item, indx) => { return ( <img key={indx} src={item.src} alt={item.label} /> ); })}

React Flow: How to remove an element?

I'm having issues with deleting elements in react-flow via a button.
I can delete elements fine using Backspace but the button only works for the first delete and after that it brings back the deleted node.
New to using react-flow and can't put my finger on the problem here. Is the state not getting changed some how?
Below is the code I use for react flow
CodeSandbox here.
import React, { useState, useCallback, useEffect } from "react";
import ReactFlow, {
removeElements,
addEdge,
Background,
} from "react-flow-renderer";
const onLoad = (reactFlowInstance) => {
reactFlowInstance.fitView();
console.log(reactFlowInstance.getElements());
};
const StackFlow = () => {
const initialElements = [
{
id: "0",
position: { x: 0, y: -100 },
sourcePosition: "bottom",
style: {
width: 100,
fontSize: 11,
color: "white",
background: "#6ec9c0",
},
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("0")
}
>
Del
</button> <br /> <br />
<strong>Models</strong>
</>
),
},
},
{
id: "1",
position: { x: 100, y: 50 },
sourcePosition: "bottom",
targetPosition: "top",
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("1")
}
>
Del
</button> <br /> <br />
Model: <strong>1</strong> <br />
ID: 1
</>
),
},
},
{
id: "2",
position: { x: 150, y: 250 },
sourcePosition: "bottom",
targetPosition: "top",
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("2")
}
>
Del
</button> <br /> <br />
Model 1: <strong>subModel1</strong> <br />
ID: 2
</>
),
},
},
{
id: "3",
position: { x: 250, y: 250 },
sourcePosition: "bottom",
targetPosition: "top",
data: {
label: (
<>
<button
className="w-md h-md border-2 border-black p-2"
onClick={() =>
remModelData("3")
}
>
Del
</button> <br /> <br />
Model 1: <strong>subModel2</strong> <br />
ID: 3
</>
),
},
},
{
id: "0-1",
type: "step",
source: "0",
target: "1"
},
{
id: "1-2",
type: "step",
source: "1",
target: "2"
},
{
id: "1-3",
type: "step",
source: "1",
target: "3"
},
];
const [elements, setElements] = useState(initialElements);
const onElementsRemove = useCallback(
(elementsToRemove) =>
setElements((els) => removeElements(elementsToRemove, els)),
[]
);
const onConnect = (params) => setElements((els) => addEdge(params, els));
const [reactflowInstance, setReactflowInstance] = useState(null);
useEffect(() => {
if (reactflowInstance && elements.length > 0) {
reactflowInstance.fitView();
}
}, [reactflowInstance, elements.length]);
const remModelData = useCallback((id) => {
let arr = elements
var index = arr.indexOf(id);
if (index > -1) {
arr.splice(index, 1);
}
arr = arr.filter(function (obj) {
return obj.id !== id;
});
console.log(arr);
setElements(arr);
}, []);
return (
<ReactFlow
elements={elements}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onLoad={onLoad}
snapToGrid={true}
snapGrid={[15, 15]}
>
<Background color="#aaa" gap={16} />
</ReactFlow>
);
};
function Home() {
return (
<>
<div className="w-full mx-auto justify-center items-center flex">
<div
className="flex mt-10 flex-row items-center content-center justify-center max-h-5xl max-w-5xl py-2"
style={{ flex: 1, width: 1000, height: 800, borderWidth: 2 }}
>
<StackFlow />
</div>
</div>
</>
);
}
export default Home;
I was able to accomplish the end result I wanted by creating this function:
const deleteNode = (id) => {
setElements((els) => removeElements([elements[id]], els));
};
And passing this into the OnClick:
onClick={() => deleteNode(0)}
Changing 0 for what ever index the element you wish to remove from the array is
As from version 10 Elements has been replaced with Nodes and Edges.
I could achieve the following using the function below.
const deleteNodeById = (id) => {
flowInstance.setNodes((nds) => nds.filter((node) => node.id !== id))
}
And passing this function to my delete button (In my case mui delete icon)
<DeleteOutlined
onClick={() => deleteNodeById(data.id)}
style={{ color: '#FF0000' }}
/>
As of version 11.2 onwards, you can use
reactFlowInstance.deleteElements({ nodes: nodesToDelete, edges: edgesToDelete });
The advantage is that method will fire "onNodesDelete" & "onEdgesDelete" events so that you can reuse your handlers.
Recall that these events are also fired if you press the "backspace" button.

React setting multiple states dynamically

Im learning React and currently playing with passing data around.
I have info for 5 cards stored in state like this:
state = {
cards: [
{ id: 1, name: "p1", value: "Prize 1", imageSrc: "/path/to/image", bgColor: { background: "#FF6384", border: "4px solid #FF6384" } },
{ id: 2, name: "p2", value: "Prize 2", imageSrc: "/path/to/image", bgColor: { background: "#4BC0C0", border: "4px solid #4BC0C0" } },
{ id: 3, name: "p3", value: "Prize 3", imageSrc: "/path/to/image", bgColor: { background: "#FFCE56", border: "4px solid #FFCE56" }},
{ id: 4, name: "p4", value: "Prize 4", imageSrc: "/path/to/image", bgColor: { background: "#67bd42", border: "4px solid #67bd42" } },
{ id: 5, name: "p5", value: "Prize 5", imageSrc: "/path/to/image", bgColor: { background: "#c931f7", border: "4px solid #c931f7" }}
],
flipped: null,
};
I am displaying them with an iteration, like this:
<div className="prize-cards-inner">
{this.state.cards.map((card) => {
return (
<section key={card.id} className="prize-card-container">
<div
className={`prize-card ${
this.state.flipped === card ? "flipped" : ""
}`}
onClick={() => this.clickHandler(card)}
>
<div className="front">
<div className="card-name-div" style={ card.bgColor }>
{card.value}
</div>
<div className="prize-image-div" >
<img className="prize-image" src={card.imageSrc} alt="test" />
</div>
<div className="slot-name-div" style={card.bgColor}>
<p> {card.name}</p>
</div>
</div>
<div className="back">Prize</div>
</div>
</section>
);
})}
</div>
and handling card clicks with this:
clickHandler = (card) => {
if (this.state.flipped) {
return;
}
this.setState({
flipped: card,
***Set another state for all other cards (how do I say "set the state of all cards that aren't this card?")***
});
};
I would like to set the state for all other cards aside from the clicked card at the same time I set the state for the clicked card.
I have no clue how to do what I want, so I have marked the part I am unsure about with asterisks. Thanks in advance for your time.
I think you are looking for this:
clickHandler(card) {
if (this.state.flipped) {
return;
}
this.setState((state) => ({
flipped: card,
cards: state.cards.map((c) => (
// If the card we are mapping is not the flipped card add someOtherState to it
c === card ? c : { ...c, someOtherState: 'some value'};
)),
});
};
I'd recommend you start using functional components with React hooks right away. They provide a much nicer developer experience

How to setState on Object item within array

I want to update state of key heart in the array's objects when the heart icon pressed it changes to red so for this I'm using react native icons and i'm using heart and hearto to switch when click on it
here is the code:
state = {
localAdversiment: [
{
title: "Ecloninear 871",
image: require("../../assets/images/truck_image.png"),
year: "2015",
type: "Truck",
status: "new",
price: "$ 2000",
heart: "hearto"
}
Here it function which is called when heart icon pressed
handleFavourite = index => {
const { heart } = this.state.localAdversiment[index];
this.setState(
{
heart: "heart"
}
);
};
here is the heart icon code
<TouchableOpacity onPress={() => this.handleFavourite(index)}>
<Icon
name={item.heart}
type={"AntDesign"}
style={{ fontSize: 18 }}
/>
</TouchableOpacity>
kindly help me how to update heart as heart instead of hearto when clicked
You can do it easily by following approach
state = {
localAdversiment: [
{
id: 0,
title: "Ecloninear 871",
image: require("../../assets/images/truck_image.png"),
year: "2015",
type: "Truck",
status: "new",
price: "$ 2000",
heart: "hearto",
selected: false
}
}
now in onPress do this
handleFavourite = (item) => {
const { id } = item;
this.setState({
localAdvertisement: this.state.localAdvertisement.map((item) => {
if(item.id === id){
return {
...item,
selected: !item.selected
}
}
return item
})
})
};
Now render like this
<TouchableOpacity onPress={() => this.handleFavourite(item)}>
<Icon
name={item.selected ? "heart" : 'hearto'}
type={"AntDesign"}
style={{ fontSize: 18 }}
/>
</TouchableOpacity>
Hope it will help you
Edit this function as follows:
handleFavourite = index => {
let updatedlocalAdversimentStates = this.state.localAdversiment;
updatedlocalAdversimentStates[index].heart = "heart";
this.setState(
{
localAdversiment: updatedlocalAdversimentStates
}
);
};

React-Spring: Trail inside UseTransition map not working

I'm a newbie trying to use React Spring in my React (16.8) project. I made a little carousel which works, but I want to add a credit at the bottom of each slide which should stagger in. A credit consist of two items and the second should animate in a tad slower than the first.
For this I want to use Trail but while the contents of the Trail renders, the animation does not fire.
This is my code so far:
const slides = [
{
id: 0,
desktopImage: "http://lorempixel.com/400/200/",
title: "foo",
studio: "bar"
},
{
id: 1,
desktopImage: "http://lorempixel.com/400/200/",
title: "foo",
studio: "bar"
},
{
id: 2,
desktopImage: "http://lorempixel.com/400/200/",
title: "foo",
studio: "bar"
}
];
const [slideIndex, set] = useState(0);
useEffect(
() =>
void setInterval(() => set(state => (state + 1) % slides.length), 4000),
[]
);
const transitions = useTransition(slides[slideIndex], item => item.id, {
from: { opacity: 0, left: -10 },
enter: { opacity: 1, left: -100 },
leave: { opacity: 1, left: -100 },
config: { friction: 25, duration: 4000 }
});
return (
<div className="header">
{transitions.map(({ item, props, key }) => {
let bgImg = item.desktopImage;
const creditArray = [item.title, item.studio];
return (
<div key={key}>
<animated.div
className="bg"
style={{ ...props, backgroundImage: `url(${bgImg})` }}
/>
<div className="imageCredit">
{
<Trail
key={key}
items={creditArray}
keys={key}
from={{ opacity: 0, transform: "translate3d(-100, 0, 0)" }}
to={{ opacity: 1, transform: "translate3d(0, 0, 0)" }}
>
{item => props => <div className="h4">{item}</div>}
</Trail>
}
</div>
</div>
);
})}
</div>
);
Figured it out, I forgot to pass downn the actual style down to the rendered element.
so instead of :
<Trail
key={key}
items={creditArray}
keys={key}
from={{ opacity: 0, transform: "translate3d(-100, 0, 0)" }}
to={{ opacity: 1, transform: "translate3d(0, 0, 0)" }}>
{item => props => <div className="h4">{item}</div>}
</Trail>
I needed to do:
<Trail
items={creditArray}
keys={item => item + key}
from={{opacity: 0, transform: 'translateX(100px)'}}
to={{opacity: 1, transform: 'translateX(0)'}}>
{item => style => (
<animated.div style={{ ...style }} className="h4">
{item}
</animated.div>
)}
</Trail>

Categories