Array of components in react does not render properly when updated - arrays

I am trying to build a component containing an array of components in react, giving a new size to each component in array according to total number of elements. However the view does not update all the components. Only newly added component is updated with size even though i rebuild the array each time.
class StampArea extends React.Component{
constructor(props){
super(props);
this.state = {updateView: true};
this.getStampSize = this.getStampSize.bind(this);
this.prepareStamps = this.prepareStamps.bind(this);
this.balance = 1;
}
prepareStamps(fulfill){
var stamps = [];
console.log('stamps: ', stamps);
for (var i = 1; i <= fulfill; i++) {
if(i<=this.balance){
stamps.push(<Stamp key={i} stampSize={this.getStampSize(fulfill)} stamped={true} stampBackgroundImage={this.props.stampBackgroundImage} stampPunchImage={this.props.stampPunchImage}/>);
}else{
stamps.push(<Stamp key={i} stampSize={this.getStampSize(fulfill)} stamped={false} stampBackgroundImage={this.props.stampBackgroundImage} stampPunchImage={this.props.stampPunchImage}/>);
}
}
console.log('stamps: ', stamps);
return(stamps);
}
getStampSize(fulfill){
if(fulfill >= 20){return 10}
if(fulfill >= 15){return 20}
if(fulfill >= 10){return 50}
if(fulfill >= 8 ){return 35}
if(fulfill >= 5 ){return 40}
if(fulfill >= 4 ){return 60}
if(fulfill >= 1 ){return 80}
}
render(){
return( <div style={styles.stampsContainer}>{this.prepareStamps(this.props.fulfill)}</div>);
}
}
class Stamp extends React.Component{
constructor(props){
super(props);
this.stamp=null;
this.buildStamp=this.buildStamp.bind(this);
this.buildStamp();
}
buildStamp(){
// Assigning correct stylesheet to build the stamp as asked.
if(this.props.stamped){
this.punched = (<img style={{position:'absolute',height:this.props.stampSize, width:this.props.stampSize, resizeMode:'contain'}} src={this.props.stampPunchImage}/>);
}else{
this.punched = null;
}
console.log('stampsize inside, ', this.props.stampSize);
this.backgroundImage = (<img style={{position:'absolute',height:this.props.stampSize, width:this.props.stampSize, resizeMode:'contain'}} src={this.props.stampBackgroundImage}/>);
this.stamp = (<div style={{position:'relative', margin:5,height:this.props.stampSize, width:this.props.stampSize}}>{this.backgroundImage}{this.punched}</div>);
}
render(){
return(this.stamp);
}
}
And here are some pics to explain better,
The initial state always renders correct
This is how it looks when i update the component via props
Additionally when i check the array object, i see size props are set with correct value but somehow it doesn't render properly.
How can i solve this?

This is because your render method just returns this.stamp which is generated only once inside the constructor. Instead of calling buildStamp inside a constructor, just move it inside render method and you should be fine.

Related

ReactJS: Child Component is not updating even I am passing different values

I am new to React.
My child component (SmithchartSeriesDirective) successfully displays the data passed from the server, when the parent component (SimplePanel) is loaded for the first time. On subsequent calls the data received from server changes, it is reflected in the props, but once I bind this data to the child component, it does not reflect the updated data in the component.
I am binding the data in listResult array.
Below is Parent Component SimplePanel
export class SimplePanel extends Component<Props> {
render() {
var reactance: number[] = [];
var resistance: number[] = [];
this.props.data.series.map((anObjectMapped, index) => {
if(index==0)
{
reactance = anObjectMapped.fields[0].values.toArray();
}
else
{
resistance = anObjectMapped.fields[0].values.toArray();
}
});
var resultArr =
{
resistance:0,
reactance:0
};
let listResult =[];
for (let index = 0; index < resistance.length; index++) {
var newObj = Object.create(resultArr);
newObj.resistance = Number(resistance[index]);
newObj.reactance=reactance[index];
listResult.push(newObj);
}
return (<div className='control-pane' style={{ height:'100%', width:'100%', backgroundColor:'#161719' }} >
<div className='col-md-12 control-section' style={{ height:'100%', width:'100%' }}>
<SmithchartComponent id='smith-chart' theme="MaterialDark" legendSettings={{ visible: true, shape: 'Circle' }}>
<Inject services={[SmithchartLegend, TooltipRender]}/>
<SmithchartSeriesCollectionDirective>
<SmithchartSeriesDirective
points= {listResult}
enableAnimation={true}
tooltip={{ visible: true }}
marker={{ shape: 'Circle', visible: true, border: { width: 2 } }}
>
</SmithchartSeriesDirective>
</SmithchartSeriesCollectionDirective>
</SmithchartComponent>
</div>
</div>);
welcome to stack overflow.
First remember that arrays saved by reference in JavaScript. So if you change any array by push() or pop() methods, reference to that array doesn't change and React can't distinguish any change in your array (to re-render your component).
let a = [2];
let b = a;
b.push(4);
a == b; //a is [2] and b is [2,4] but the result is true.
You can use this approach as a solution to this problem:
let listResult = [...oldListResult, newObj]; // ES6 spread operator
Also consider for rendering array elements you need to use key prop, so React can render your components properly. more info can be found here.

this.setState is only pulling last value in array

I'm VERY new to React and not sure how to render this nested array from an external JSON file. remittance is the only array here one that has nested values that I need to access and render. It logs to the console fine, but won't separate on setState. I apologize if this looks terrible.
class App extends Component {
constructor(props){
super(props);
this.state = {
data,
rName: "",
rDescription: "",
}
}
componentDidMount(){
console.log("Mounted!");
this.display();
};
display = () => {
for (var i = 0; i < data.length; i++){
var remittanceList = data[i].Remittance;
// console.log(remittanceList);
for (var x = 0; x < remittanceList.length; x++){
var rName = remittanceList[x].PayorName;
var rDescription = remittanceList[x].Description;
console.log(rName + rDescription);
this.setState({rName, rDescription});
}
}
}
render() {
var { rName, rDescription } = this.state;
return (
<div className="App">
<Title/>
{this.state.data.map((each, index) => (
<PayInfo key={index}
name={each.Payee.Name}
fax={each.Payee.Fax}
pan={each.Payment.PAN}
cvv={each.Payment.CVV}
exp={each.Payment.Exp}
payorName={rName}
description={rDescription}
/>
))}
</div>
);
}
}
export default App;
And the JSON file is something like this. Because the amount of remittances can very, I can't hardcode in an index to look for every time.
[
{
"Payee": {
"Name": "Bob",
"Fax": "5555555555",
},
"Payment": {
"PAN": 123456,
"CVV": 123,
"Exp": "1/2018"
},
"Remittance": [
{
"PayorName": "Me",
"Description": "Hello World.",
},
{
"PayorName": "You",
"Description": "Hey world.",
},
{
"PayorName": "Snoop",
"Description": "Bye world.",
}
]
},
And this is the PayInfo.js file I should've posted initially! Not sure if this changes any of the answer I got before?
import React from "react";
import "./PayInfo.css";
const PayInfo = props => (
<div>
<div id="payee">
<p>Payee</p>
<p>{props.name}</p>
<p>Fax: {props.fax}</p>
</div>
<hr></hr>
<div id="payment">
<p>Payment</p>
<p>PAN: {props.pan}</p>
<p>CVV: {props.cvv}</p>
<p>Exp: {props.exp}</p>
</div>
<hr></hr>
<div id="remittance">
<p><strong>Remittance(s)</strong></p>
<p>Payor: {props.payorName}</p>
<p>Description: {props.description} </p>
</div>
<hr></hr>
<hr></hr>
</div>
);
export default PayInfo;
In your display method, you are setting the state inside for loop. so only the last value being set to the state.
Please try the below code.
display = () => {
let totalRemittanceArray = []
for (var i = 0; i < data.length; i++) {
var remittanceList = data[i].Remittance;
for (var x = 0; x < remittanceList.length; x++) {
var rName = remittanceList[x].PayorName;
var rDescription = remittanceList[x].Description;
console.log(rName + rDescription);
totalRemittanceArray.push({ rName, rDescription })
}
}
this.setState({totalRemittanceArray})
}
What you might want to do is something like this:
https://codesandbox.io/s/p36jlrz7jj
As you can see I extracted another component PayerInfo. Also React works perfect with map within the render method. I hope my example helps!
Currently you call setState in the for loop and thus overwrite the state every time, but I guess you figured that one out already. I use Array.map to loop over elements within JSX a lot. Dunno if one could do this more nicely, but there is always a possibility to improve ^^
A small improvement here: You could loop in a separate function first and then return the complete Array: https://reactjs.org/blog/2017/09/26/react-v16.0.html

React component with SVG does not evaluate expression

Imagine I have a component like this it encloses an SVG:
class Image extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "Initial message"
};
}
render() {
return ( < svg xmlns = "http://www.w3.org/2000/svg"
width = "300"
height = "450" >
< rect x = "0"
y = "0"
width = "300"
height = "450"
fill = "#d0d0d0" / >
< text id = "textInHere"
x = "50%"
y = "50%"
fill = "#7d7d7d" > {this.state.message} < /text> < /svg > )
}
}
I am simply evaluating expression this.state.message as inner text.
To my surprise it generates the following DOM structure:
This is strange as i have not added any span!
The problem is illustrated in a plunk here
Appreciate, if any one could explain why I see multiple spans, and/or how to fix this problem.
You have a problem with whitespaces in your tags. When you remove them everything looks fine.
UPDATE: apparently, this is caused by spaces surrounding {this.state.message}. They are converted to span which results in breaking your syntax as you have observed (because span is not allowed inside svg tag).

Calling a function to change css within render() in a React Component

I am returning a set of information from Spotify in a React Component and want to interrogate the JSON that is returned and highlight the original search term within the artist name. so for example, if you search 'bus' and one of the artists returned is Kate Bush, then this would be highlighted green in 'Kate BUSh'. At the moment I am calling a function from within render(). However, what I get rendered is:
Kate <span style="color:green">Bus</span>h
How do I get render() to read the HTML as HTML (so that Bus would just be green) rather than rendering as text? Relevant code from the React Component below:
// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
let searchTerm = this.props.searchTerm;
if (displayString.indexOf(searchTerm) !== -1) {
displayString = displayString.replace(searchTerm, '<span style="color:green">'+searchTerm+'</span>');
}
return displayString;
}
render() {
return (
<div className="Track" id="Track">
<div className="Track-information">
<h3>{this.underlineSearch(this.props.trackName)}</h3>
<p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>
</div>
</div>
);
}
Your underlineSearch function needs to return React Elements, but right now it is returning a string. You could use a Fragment to make it work:
// Called from within render() to wrap a span around a search term embedded in the artist, album or track name
underlineSearch(displayString) {
const searchTerm = this.props.searchTerm;
const indexOfSearchTerm = displayString.indexOf(searchTerm);
let node;
if (indexOfSearchTerm === -1) {
node = displayString;
} else {
node = (
<React.Fragment>
{displayString.substr(0, indexOfSearchTerm)}
<span style={{color: 'green'}}>
{displayString.substr(indexOfSearchTerm, searchTerm.length)}
</span>
{displayString.substr(indexOfSearchTerm + searchTerm.length)}
</React.Fragment>
);
}
return node;
}
To make your solution even more reusable you can make underlineSearch and wrapper with your styles for highlighting into 2 separate components. Even more, you can search for multiple occurrences of your searchTerm with regex. Found a similar SO question here. I slightly adapted one of the answers there according to your needs, but all credit goes to this amazing and neat solution for highlighting matches of a string in longer texts. Here is the code:
const Match = ({ children }) => (
<span style={{'color':'green'}}>{children}</span>
);
const HighlightMatches = ({ text, searchTerm }) => {
let keyCount = 0;
let splits = text.split(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
let matches = text.match(new RegExp(`\\b${searchTerm}\\b`, 'ig'));
let result = [];
for (let i = 0; i < splits.length; ++i) {
result.push(splits[i]);
if (i < splits.length - 1) {
result.push(<Match key={++keyCount}>{matches[i]}</Match>);
}
}
return (
<p>{result}</p>
);
};
Then in your main component where you render everything you can do this:
render() {
<div className="Track" id="Track">
<div className="Track-information">
<h3>
<HighlightMatches text={this.props.trackName} searchTerm={this.props.searchTerm}/>
</h3>
<p>
<HighlightMatches text={this.props.artistName} searchTerm={this.props.searchTerm} /> |
<HighlightMatches text={this.props.albumName} searchTerm={this.props.searchTerm} />
</div>
</div>
}
To me this seems like the most react-like approach to solve the problem :)
While you can use dangerouslySetInnerHTML (), as the name suggests it is extremely dangerous, since it is prone to XSS attacks, for example:
{artist: "Kate Bush<script> giveMeAllYourCookies()</script>"}
You can split the displayString into an array and render it.
Please note that that my implementation of underlineSearch is buggy, and will not work if there are more than one match.
class Main extends React.Component {
underlineSearch(displayString) {
let searchTerm = this.props.searchTerm;
var index = 0;
var results = [];
var offset = 0;
while(true) {
const index = displayString.indexOf(searchTerm, offset);
if(index < 0) {
results.push(<span>{displayString.substr(offset)}</span>);
break;
}
results.push(<span> {displayString.substr(offset, index)}</span>);
results.push(<strong style={{color: 'green'}}> {displayString.substr(index, searchTerm.length)}</strong>);
offset = index + searchTerm.length;
}
return results;
}
render() {
return <div>
<h3>{this.underlineSearch(this.props.trackName)}</h3>
<p>{this.underlineSearch(this.props.artistName)} | {this.underlineSearch(this.props.albumName)}</p>
</div>
}
}
ReactDOM.render(<Main
trackName="Magic Buses"
artistName="Kate Bush"
albumName="Kate Bush Alubm"
searchTerm="Bus"
/>, document.getElementById('main'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='main'></div>

Convert working VueJS component to ReactJS

I have a Vue component that works just fine. Now I'm trying to convert that code to ReactJS equivalent. My attempt on React
var ticksArray = Array.apply(null, {length: 27}).map(Number.call, Number);
export default class Timer extends Component {
constructor(props) {
super(props);
this.state = {
angle:250,
minangle:0,
maxangle:270,
xDirection:"",
yDirection:"",
oldX:0,
dragging: false
}
}
onMousedown(){
this.setState({dragging : true});
}
onMouseup(){
this.setState({dragging : false});
}
onMousemove(e){
if(!this.state.dragging)
return;
this.setState({
xDirection : this.state.oldX < e.pageX ? 'right' : 'left',
oldX:e.pageX,
yDirection: this.state.xDirection === 'left' ? 'down' : 'up'
});
if(this.state.yDirection === 'up' && this.state.angle + 2 <=
this.state.maxangle)
this.setState({angle:this.state.angle += 2})
else if(this.state.yDirection === 'down' && this.state.angle - 2 >=
this.state.minangle)
this.setState({angle:this.state.angle -= 2})
}
knobStyle(){
return {
'transform':'rotate('+this.state.angle+'deg)'
}
}
activeTicks(){
return (Math.round(this.state.angle / 10) + 1);
}
currentValue(){
return Math.round((this.state.angle/270)*100) + '%'
}
componentDidMount(){
document.addEventListener('mouseup',this.state.onMouseup)
document.addEventListener('mousemove',this.state.onMousemove)
}
render() {
var tickDivs = ticksArray.map(function(item) {
return (
<div key={item} className="tick"></div>
);
});
return (
<div id="timer">
<div className="knob-surround">
<div className="knob"></div>
<span className="min">Min</span>
<span className="max">Max</span>
<div className="ticks" className="n <= activeTicks ?
'activetick' : ''">
{tickDivs}
</div>
</div>
</div>
);
}
}
It's not working. I'm missing something. I'm assuming the problem lies in this code bit.
<div className="ticks" className="n <= activeTicks ?
'activetick' : ''">
Please help fix this.
Add this here instead of comment:
React uses the following syntax:
className={n <= activeTicks ? 'activetick' : ''}
In componentDidMount you assign handlers in a wrong way, should be like:
document.addEventListener('mouseup', this.onMouseup)
Note here that handler is not a part of your state. And the corresponding definition of the handler:
private onMouseup = () => {...}
The reason to store reference for the event handler instead of having class method - see in #3
Do not forget to unsubscribe your event handlers in componentWillUnmount like this:
window.removeEventListener("mouseup", this.onMouseup);
UPDATE:
Here is an example working without using arrow functions: https://jsfiddle.net/6dnrLw4n/4/

Resources