Need to Populate Component with Nested Data - ReactJS/ES6 - javascript

I haven't seen a post to answer this question yet, so forgive me if I overlooked something. I'm trying to populate a table with ONLY the nested array from my data on the page. this.state.data SHOULD contain a subCollection of "masterTicketItems" from the "master ticket" that is being into this React JSX script from the Razor View.
The trouble comes when I'm trying to optimistically update a given table, and ONLY focusing on that, rather than actively posting data. I've been trying look around to understand this ReactJS and ES6 "spread" mapping technique, and I'm not sure if that applies here or something different.
In essence, I'm trying to ONLY map a "LogList" component with a collection contained INSIDE "this.state.data" inside the parent "Ticket" component. I currently have if/else logic to detect if there is nothing in the collection yet, and if not, try to populate it with a SINGLE object from the child component that was "posted" from the LogForm component. However, I can't seem to get the page to render with the single child in the table! Here's the Ticket JSX file snippet for review:
class Ticket extends React.Component {
constructor(props) {
super(props);
this.state = { data: this.props.initialData };
this.handleLogSubmit = this.handleLogSubmit.bind(this);
}
loadLogsFromServer() {
const xhr = new xmlhttprequest();
xhr.open('get', this.props.geturl + this.props.id, true);
xhr.onload = () => {
const data = json.parse(xhr.responsetext);
this.setState({ data: data });
};
xhr.send();
}
handleLogSubmit(log) {
const logs = this.state.data.apptConfirmItems;
// Optimistically set an id on the new comment. It will be replaced by an
// id generated by the server. In a production application you would likely
// use a more robust system for ID generation.
if (!logs) {
log.ticketItemId = 1;
const newLogs = log;
let masterItemsAccess = Object.assign({}, this.state.data);
masterItemsAccess.masterTicketItems = log;
this.setState((data) => Object.assign({}, data, { masterTicketItems: [log] }));
}
else {
log.ticketItemId = logs.length + 1;
const newLogs = logs.concat([log]);
this.setState({ data: newLogs });
}
const data = new FormData();
data.append('ItemType', log.itemType);
data.append('ItemDescription', log.text);
const xhr = new XMLHttpRequest();
//xhr.open('post', this.props.submitUrl, true);
//xhr.onload = () => this.loadLogsFromServer();
//xhr.send(data);
}
componentDidMount() {
window.setInterval(() => this.loadLogsFromServer(), this.props.pollInterval);
}
render() {
if (!this.state.data.masterTicketItems || this.state.data.masterTicketItems.Count == 0) {
return (
<div className="queue">
<h1>Affirm Logs</h1>
<h2>{this.state.data.summary}</h2>
<h5><i>{this.state.data.description}</i></h5>
<div><h3>No logs at this time!</h3></div>
<LogForm onLogSubmit={this.handleLogSubmit} />
</div>
);
}
else {
return (
<div className="queue">
<h1>Affirm Logs</h1>
<h2>{this.state.data.summary}</h2>
<h5><i>{this.state.data.description}</i></h5>
<LogList data={this.state.data.masterTicketItems}/>
<LogForm onLogSubmit={this.handleLogSubmit} />
</div>
);
}
}
}
class LogList extends React.Component {
render() {
const logNodes = this.props.data.masterTicketItems.map(log => (
<Log key={log.ticketItemId}>
<td>{log.itemType}</td> < td > {log.itemDescription}</td>
</Log>
));
const logRaw = this.props.data.masterTicketItmes.map(log => (
<tr>
<td>{log.itemType}</td><td>{log.itemDescription}</td>
</tr>
));
return (
<div className="logList">
<table id="affirmTable" className="table table-bordered table-hover table-striped">
<tbody>
{logNodes}
</tbody>
</table>
</div>
);
}
}
class LogForm extends React.Component {
constructor(props) {
super(props);
this.state = { itemType: 'Test Log', itemDescription: '' };
this.handleItemTypeChange = this.handleItemTypeChange.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleItemTypeChange(e) {
this.setState({ itemType: e.target.value });
}
handleTextChange(e) {
this.setState({ itemDescription: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
const itemType = this.state.itemType.trim();
const itemDescription = this.state.itemDescription.trim();
if (!itemType || !itemDescription) {
return;
}
this.props.onLogSubmit({ ItemType: itemType, ItemDescription: itemDescription });
this.setState({ itemType: '', text: '' });
}
render() {
return (
<form className="logForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.itemType}
onChange={this.handleItemTypeChange}
/>
<input
type="text"
placeholder="Enter working log here..."
value={this.state.itemDescription}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
);
}
}
I purposely left out the child Log component, since I'm confident that when this is addressed that component WILL populate with the table data as expected.
Should I use nested mapping with ES6? Is there a simpler way? I'm trying to understand React state logic a little bit better.

Related

how to match username and their profile links and how to show the fetched data in table

hope you all are safe. here im stuck in a problem.here i fetched users and their profile links from the site drupal.org using RegEx. but i got another links also.i want to fetch their profile links only and match the users and their profile links and show data in table is it possible in React js? currently it look like this
i want the data look like this :
please help me, thank you.. here is my git repo https://gitlab.com/darshankoli2397/react-practice.git
import React,{ Component } from "react"
import Axios from "axios"
class UserList extends Component {
constructor(prop) {
super(prop);
this.onChangeUserName = this.onChangeUserName.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = { users: null, profile:"", name:"" }
}
onChangeUserName(e) {
this.setState({ name: e.target.value })
}
onSubmit(e) {
e.preventDefault()
const userObject = {
name: this.state.name,
};
Axios.get(`https://www.drupal.org/search/user/${this.state.name}`).then(
response => {
if (response.status === 200) {
// all is good
console.log('all is good');
console.log(response);
var olList = response.data.match(/(?<=\<ol class="search-results user-results"\>\s+).*?(?=\s+\<\/ol)/gs);
var links = response.data.match(
/(\b(https?):\/\/[-A-Z0-9+&##\\/%?=~_|!:,.;]*[-A-Z0-9+&##\/%=~_|])/gi
);
//preg_match_all('|https?://(?:www\.)?twitter.com/#!/[a-z0-9_]+|im', $text, $matched)
this.setState({ users: olList });
this.setState({ profile: links });
} else {
console.log('something is wrong');
// something is wrong
}
}
);
}
render() {
return ( <React.Fragment>
<h1 > Search Here users of Drupal < /h1>
<form align = "center" onSubmit = { this.onSubmit } >
<input type = "search" name = "name" onChange = { this.onChangeUserName } placeholder = "Search" required / >
<button type = "submit" > Search < /button >
</form >
<h3>Relevent Search Results For Your Keywords : </h3>
<table border="2" height="100" width="300">
<tr>
<td align="center"><h2>Username</h2></td>
<td><h2>Profile Links</h2></td>
</tr>
<tr>
<td dangerouslySetInnerHTML = {
{ __html: this.state.users }
} />
<td align="char" dangerouslySetInnerHTML = {
{ __html: this.state.profile }
} />
</tr>
</table>
</React.Fragment>
);
}
}
export default UserList;
You can extract the users as anchors, to be sure that every user has it's own link:
Example: asdf
let userAnchors = response.data.match(/\<a href="(https?):\/\/www.drupal.org\/user\/\w*">[\w\s]*\<\/a\>/gi)
I found this 2 regex only that you can use to extract the name an url:
const nameExtractorRegex = /<a[^>]*>([^<]+)<\/a>/g
const linkExtractorRegex = /href="(.*?)"/g
The only thing remained is to parse the anchors
const users = userAnchors.map(anchor => {
return {
name: nameExtractorRegex(anchor)[1],
link: linkExtractorRegex(anchor)[1]
}
})
Hope it helped!

In React how do I put an element onto the DOM before render() runs?

I am using React (with Rails) and using a charting library called AnyChart. However the AnyChart code that I have in my render automatically looks for a <div id="container"></div> element to attach the chart onto. If I put the chart code in the render with the container div, then it gives an error because the chart code can't find the div because its not on the DOM yet.
I tried googling and only found stuff about portals and refs which didn't seem to apply to my problem.
I know I could move the container div to the layout view template but I want to be able to render other stuff on the component below the chart. Here is my entire StockContainer component and InputField component:
import React from 'react';
import InputField from '../components/InputField'
class StockContainer extends React.Component {
constructor(props){
super(props)
this.state = {
stockTicker: ''
}
this.handleStockTickerChange = this.handleStockTickerChange.bind(this);
this.handleClearForm = this.handleClearForm.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleClearForm(event){
event.preventDefault();
this.setState({
stockTicker: ''
});
}
handleStockTickerChange(event) {
this.setState({stockTicker: event.target.value});
}
handleSubmit(event){
event.preventDefault();
let body = {
symbol: this.state.stockTicker
}
console.log("Getting stock data for: " + body.symbol)
this.handleClearForm(event);
}
componentDidMount() {
}
render() {
var table, mapping, chart;
table = anychart.data.table();
table.addData([
['2015-12-25', 512.53, 514.88, 505.69, 507.34],
['2015-12-26', 511.83, 514.98, 505.59, 506.23],
['2015-12-27', 511.22, 515.30, 505.49, 506.47],
...sample data that will later be imported from an API
['2016-01-07', 510.93, 516.07, 506.00, 510.99],
['2016-01-08', 510.88, 515.93, 505.22, 509.95],
['2016-01-09', 509.12, 515.97, 505.15, 510.12],
['2016-01-10', 508.53, 516.13, 505.66, 510.42]
]);
// mapping the data
mapping = table.mapAs();
mapping.addField('open', 1, 'first');
mapping.addField('high', 2, 'max');
mapping.addField('low', 3, 'min');
mapping.addField('close', 4, 'last');
mapping.addField('value', 4, 'last');
// defining the chart type
chart = anychart.stock();
// set the series type
chart.plot(0).ohlc(mapping).name('ACME Corp.');
// setting the chart title
chart.title('AnyStock Demo');
// display the chart
chart.container('container');
chart.draw();
return(
<div>
<h1>Research/Add a Stock</h1>
<form onSubmit={this.handleSubmit}>
<InputField
label='Stock Symbol'
name='ticker'
content={this.state.stockTicker}
handleChange={this.handleStockTickerChange}
/>
<input type='submit' value='Get Info'/>
</form>
<div id="container"></div>
</div>
)
}
}
export default StockContainer;
import React from 'react';
const InputField =(props) =>{
return(
<label>{props.label}
<input
type='text'
name={props.name}
value={props.content}
onChange={props.handleChange}
/>
</label>
)
}
export default InputField
Like Ishwor mentioned in the comment above, have you tried moving the chart.container() and chart.draw() methods into the component's componentDidMount() hook?
If you can provide the rest of the code for your component, we might be able to help.
import React from 'react';
import InputField from '../components/InputField'
class StockContainer extends React.Component {
constructor(props){
super(props)
this.state = {
stockTicker: ''
}
let table = anychart.data.table();
table.addData([
['2015-12-25', 512.53, 514.88, 505.69, 507.34],
['2015-12-26', 511.83, 514.98, 505.59, 506.23],
['2015-12-27', 511.22, 515.30, 505.49, 506.47],
...sample data that will later be imported from an API
]);
// mapping the data
let mapping = table.mapAs();
mapping.addField('open', 1, 'first');
mapping.addField('high', 2, 'max');
mapping.addField('low', 3, 'min');
mapping.addField('close', 4, 'last');
mapping.addField('value', 4, 'last');
// defining the chart type
let chart = anychart.stock();
// set the series type
chart.plot(0).ohlc(mapping).name('ACME Corp.');
// setting the chart title
chart.title('AnyStock Demo');
// to be able to reference these later, if needed
this.table = table;
this.mapping = mapping;
this.chart = chart;
this.handleStockTickerChange = this.handleStockTickerChange.bind(this);
this.handleClearForm = this.handleClearForm.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
// draw the chart
this.drawMyAwesomeChart = this.drawMyAwesomeChart.bind(this);
}
componentDidMount() {
this.chart.container('container');
this.drawMyAwesomeChart()
}
componentDidUpdate() {
// do some checks here to see if you need to draw chart?
// this.drawMyAwesomeChart()
}
drawMyAwesomeChart() {
this.chart.draw();
}
}
You could then bind a function that specifically is responsible for refreshing the chart whenever the stock ticker changes. I haven't fully tested this, but something along these lines should work?
Try using life cycle methods componentDidUpdate
I got it to work correctly. Here is my code:
import React from 'react';
import InputField from '../components/InputField'
const divStyle = {
// width: '100%',
// height: '100%'
};
class StockContainer extends React.Component {
constructor(props){
super(props)
this.state = {
stockTicker: '',
currentPrices: [],
show: false
}
this.handleStockTickerChange = this.handleStockTickerChange.bind(this);
this.handleClearForm = this.handleClearForm.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.getDataForStockChart = this.getDataForStockChart.bind(this);
}
handleClearForm(event){
// event.preventDefault();
this.setState({ stockTicker: '' });
}
handleStockTickerChange(event) {
this.setState({stockTicker: event.target.value});
}
getDataForStockChart(){
let arrayOfArrays = []
fetch(`https://api.iextrading.com/1.0/stock/${this.state.stockTicker}/chart/1y`)
.then(response => {
if (response.ok) {
return response;
} else {
let errorMessage = `${response.status} (${response.statusText})`,
error = new Error(errorMessage);
throw(error);
}
})
.then(response => response.json())
.then(body => {
body.forEach(obj => {
arrayOfArrays.push([obj["date"], obj["open"], obj["high"], obj["low"], obj["close"]]);
});
this.setState({
show: true,
currentPrices: arrayOfArrays
});
})
.catch(error => console.error(`Error in fetch: ${error.message}`));
}
componentDidUpdate(prevProps, prevState){
var table, mapping, chart;
if (this.state.currentPrices.length > 1 && this.state.show){
this.refs.myInput.innerHTML = '';
// node = this.myRef.current
table = anychart.data.table();
table.addData(this.state.currentPrices);
mapping = table.mapAs();
mapping.addField('open', 1, 'first');
mapping.addField('high', 2, 'max');
mapping.addField('low', 3, 'min');
mapping.addField('close', 4, 'last');
mapping.addField('value', 4, 'last');
chart = anychart.stock();
chart.plot(0).ohlc(mapping).name('Stock Chart');
chart.title('AnyStock Demo');
chart.container('container');
chart.draw();
this.setState({show: false})
this.handleClearForm(event);
}
}
// shouldComponentUpdate(nextProps, nextState){
// if (this.state.show)
// }
handleSubmit(event){
event.preventDefault();
this.getDataForStockChart();
this.handleClearForm();
}
render() {
return(
<div>
<h1>Research/Add a Stock</h1>
<form onSubmit={this.handleSubmit}>
<InputField
label='Stock Symbol'
name='ticker'
content={this.state.stockTicker}
handleChange={this.handleStockTickerChange}
/>
<input type='submit' value='Get Info'/>
</form>
<div id="container" ref="myInput" style={divStyle}></div>
</div>
)
}
}
export default StockContainer;

Why am I receiving errors about assigning keys in React?

I've been working through the ReactJS demo for ASP.NET Core and I'm struggling with an error message:
Warning: Each child in an array or iterator should have a unique "key" prop.
Check the render method of CommentList. See url for more information.
in Comment (created by CommentList)
in CommentList (created by CommentBox)
in div (created by CommentBox)
in CommentBox
The message is clear, every child of an array needs a key. The code assigns a key however and having downloaded the react console for Chrome I can also see the array and all the data that is added.
In my code I have the following:
class CommentList extends React.Component {
render() {
const commentNodes = this.props.data.map(comment => (
<Comment author={comment.author} key={comment.id}>
{comment.author}
</Comment>
));
return (
<div className="commentList">
{commentNodes}
</div>
);
}
}
You can see that the key is assigned to the comment component and returned to the comment list. The id doesn't appear to be null so I'm confused as to why I'm still getting this error message.
Can help me with where I am going wrong with this?
Here is my complete source code so far:
js/app.jsx
class CommentBox extends React.Component {
constructor(props) {
super(props);
this.state = { data: [] };
this.handleCommentSubmit = this.handleCommentSubmit.bind(this);
}
loadCommentsFromServer() {
const xhr = new XMLHttpRequest();
xhr.open('get', this.props.url, true);
xhr.onload = () => {
const data = JSON.parse(xhr.responseText);
this.setState({ data: data });
};
xhr.send();
}
handleCommentSubmit(comment) {
const comments = this.state.data;
// Optimistically set an id on the new comment. It will be replaced by an
// id generated by the server. In a production application you would likely
// use a more robust system for ID generation.
comment.Id = comments.length + 1;
const newComments = comments.concat([comment]);
this.setState({ data: newComments });
const data = new FormData();
data.append('author', comment.author);
data.append('text', comment.text);
const xhr = new XMLHttpRequest();
xhr.open('post', this.props.submitUrl, true);
xhr.onload = () => this.loadCommentsFromServer();
xhr.send(data);
}
componentDidMount() {
this.loadCommentsFromServer();
window.setInterval(() => this.loadCommentsFromServer(), this.props.pollInterval);
}
render() {
return (
<div className="commentBox card">
<h4>Comments</h4>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
}
class CommentList extends React.Component {
render() {
const commentNodes = this.props.data.map(comment => (
<Comment author={comment.author} key={comment.id}>
{comment.author}
</Comment>
));
return (
<div className="commentList">
{commentNodes}
</div>
);
}
}
class CommentForm extends React.Component {
constructor(props) {
super(props);
//Initial state?
this.state = { author: '', text: '' };
//Event handlers
this.handleAuthorChange = this.handleAuthorChange.bind(this);
this.handleTextChange = this.handleTextChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleAuthorChange(e) {
this.setState({ author: e.target.value });
}
handleTextChange(e) {
this.setState({ text: e.target.value });
}
handleSubmit(e) {
e.preventDefault();
const author = this.state.author.trim();
const text = this.state.text.trim();
//If inputs are null then return nothing.
if (!text || !author) {
return;
}
//Post data to the server
this.props.onCommentSubmit({ author: author, text: text });
//Clear form
this.setState({ author: '', text: '' });
}
render() {
return (
<div className="commentForm">
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your name" value={this.state.author} onChange={this.handleAuthorChange} />
<input type="text" placeholder="Say something..." value={this.state.text} onChange={this.handleTextChange} />
<input type="submit" value="Post" />
</form>
</div>
);
}
}
class Comment extends React.Component {
render() {
return (
<div className="comment">
<p className="commentAuthor">
{this.props.author}
</p>
</div>
);
}
}
ReactDOM.render(
<CommentBox url="/comments" submitUrl="/comments/new" pollInterval={2000} />,
document.getElementById('content')
);
I am using a model for my data as I'll be introducing this to a repository later.
Models/CommentModel
namespace ReactDemo.Models
{
public class CommentModel
{
public int Id { get; set; }
public string Author { get; set; }
public string Text { get; set; }
}
}
Controllers/HomeController
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using ReactDemo.Models;
namespace ReactDemo.Controllers
{
public class HomeController : Controller
{
private static readonly IList<CommentModel> _comments;
static HomeController()
{
_comments = new List<CommentModel>
{
new CommentModel
{
Id = 1,
Author = "Daniel Lo Nigro",
Text = "Hello ReactJS.NET World!"
},
new CommentModel
{
Id = 2,
Author = "Pete Hunt",
Text = "This is one comment"
},
new CommentModel
{
Id = 3,
Author = "Jordan Walke",
Text = "This is *another* comment"
},
};
}
public IActionResult Index()
{
return View();
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
[Route("comments")]
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
public ActionResult Comments()
{
return Json(_comments);
}
[Route("comments/new")]
[HttpPost]
public ActionResult AddComment(CommentModel comment)
{
// Create a fake ID for this comment
comment.Id = _comments.Count + 1;
_comments.Add(comment);
return Content("Success :)");
}
}
}
You could also use shortid for unique keys as an alternate to id, this way even if you don't have unique id from json even then key would be unique.
var shortid = require('shortid');
function createNewTodo(text) {
return {
completed: false,
id: shortid.generate(),
text
}
}
Use Id instead of id for your key value, as javascript is case sensitive. Also add .toString()
'Id' is being set here:
comment.Id = comments.length + 1;
Updated jsx code:
<Comment author={comment.author} key={comment.Id.toString()}> {comment.author} </Comment> ));
From React documentation:
"The best way to pick a key is to use a string that uniquely identifies a list item among its siblings. Most often you would use IDs from your data as keys:"
Your id's are probably not unique. React mostly doesn't recommend using incremental ID's. The ID's are used in the reconciliation process, see here:
https://reactjs.org/docs/reconciliation.html#keys
Try using author as a key, or even better, try generating unique ones (with a package like node-uuid, or see the method provided in #tarzen chugh his answer.
As suggested by the good people in this question, the issue was being caused by the uppercase Id being set in handleCommentSubmit.
Was
handleCommentSubmit(comment) {
comment.Id = comments.length + 1;
}
Change to
handleCommentSubmit(comment) {
comment.id = comments.length + 1;
}
The confusion between uppercase and lowercase was being caused by my declaration of values in my model, I had written those in uppercase, however, the results returned by ReactJS are, of course, lowercase.

Go to previous state in reactjs

I'm building a component which proceeds according to the selections of the users. I have completed it successfully but facing some issues when trying to implement a back button to go back.
My code is like follows.
class ReportMainCat extends Component {
constructor(props) {
super(props);
this.state = {
postType: null,
}
this.changeType = this.changeType.bind(this);
this.report_next = this.report_next.bind(this);
};
report_next() {
if (this.state.postType == null) {
return <ReportFirst changeType={this.changeType}/>
}
else if (this.state.postType === 'sexual') {
return <ReportXContent changeType={this.changeType}/>
} else if (this.state.postType === 'selfharm') {
return <ReportThreatContent changeType={this.changeType}/>
}
}
changeType = (postType) => {
this.setState({postType})
this.setState({
showMainReportCats: false,
})
}
render() {
return (
<div className="top_of_overlay">
<div className="section_container text_align_center padding_10px">
<a className="">Report Category</a>
{this.report_next()}
</div>
</div>
)
}
}
I'm binding the postType value as follows.
class ReportXContent extends Component {
constructor(props) {
super(props);
this.state = {
postType: '',
}
};
textType(postType) {
this.props.changeType(postType);
}
render() {
return (
<div className="text_align_left">
<div>
<div className="width_100 margin_bottom10px">
<input type="checkbox" ref="nudity" onClick={this.textType.bind(this,'nudity')}/>
<a>Nudity or Pornography</a>
</div>
<div className="width_100 margin_bottom10px">
<input type="checkbox" ref="minor" onClick={this.textType.bind(this,'minor')}/>
<a>Includes Minors</a>
</div>
</div>
<ReportButtons/>
</div>
)
}
}
My back button
<div>
<button className="float_right margin_left5px" onClick={this.props.back_report}>Back</button>
</div>
So basically what i'm trying to do is this.
Ex: If the user selects postType as sexual it will return the ReportXContent component. How can i return to the first page when the user clicks the back button.
Thanks.
You could implement the back button click handler like this in the ReportMainCat component:
handleBackClick() {
this.setState({ postType: null });
}
, and that would show the ReportFirst view again.
If you don't want the first view, but the last view, simply change your changeType implementation to save lastPostType to state like this:
changeType = (postType) => {
this.setState({
lastPostType: this.state.postType,
postType,
showMainReportCats: false,
});
}
Edit
If you want full history of changes - let's say if you want to implement a full back button history - you can simply rename lastPostType to postTypeHistory and implement it like a stack (like the browser history is):
changeType = (postType) => {
this.setState({
postTypeHistory: [...this.state.postTypeHistory, this.state.postType],
postType,
showMainReportCats: false,
});
}
handleBackClick() {
const { postTypeHistory } = this.state;
const postType = postTypeHistory.pop();
this.setState({
postType,
postTypeHistory,
});
}

Slow react perfomance on ~2K records

I have small ReactJS chat application, messages are stored inside global array client.chat as object with propery txt, which contains text. The problem is perfomance, on ~2K messages I have to wait for few seconds after each new message even if networking is commented out, so its simply rerendeing of HTML. I tried to implement same app in AngularJS and it has no delay at all. So where is the bottleneck here?
var client = {user: {}, chat: []};
class App extends React.Component {
constructor() {
super();
this.socketio = ...
// here i perform some network initialization and
// call client.updateChat when data has arrived over network
client.updateChat = function () {
me.setState({chat: client.chat.concat([]).reverse()});
};
client.addMessage = function (msg) {
me.setState(prevState=>({
chat:[msg].concat(prevState.chat)
}));
};
this.updateState = this.updateState.bind(this);
this.state = {chat: []};
}
updateState(e) {
this.setState({data: e.target.value});
}
render() {
return (
<span>
<Input socketio={this.socketio} visitorId={this.visitorId}/>
<table>
<Chat data={this.state.chat} socketio={this.socketio}>
</Chat>
</table>
</span>
);
}
}
This is Input component, representing essentially input box:
class Input extends React.Component {
constructor() {
super();
this.state = {inputValue: ''};
this.updateInputValue = this.updateInputValue.bind(this);
this.handleKeyPress = this.handleKeyPress.bind(this);
this.getFile = this.getFile.bind(this);
}
getFile(e) {/* attachment handling */ }
handleKeyPress(target) {
if (target.charCode == 13) {
if (this.state.inputValue.length == 0) return;
var inputValue = this.state.inputValue;
this.setState({inputValue: ''});
var ts = Date.now();
var elem = {
txt: inputValue, file: null, ts: ts, from: 'support', tsStr: formateDate(ts), name: client.user.name, attachmentName: null, dataType: null
};
client.addMessage(elem);
}
}
updateInputValue(evt) {
this.setState({inputValue: evt.target.value});
}
render() {
return (<div className="input">
<table width="100%">
<tbody>
<tr>
<td>
<label className="customUpload btnUpload btnM">
<span><img width="15px" src="images/attach.png"/></span>
<input onChange={this.getFile} id="fileUpload" type="file" className="upload"/>
</label>
</td>
<td>
<input value={this.state.inputValue}
onKeyPress={this.handleKeyPress}
onChange={this.updateInputValue}
className="textInput"/>
</td>
</tr>
</tbody>
</table>
</div>);
}
}
This is chat component, representing chat itself.
class Chat extends React.Component {
constructor() {super();}
render() {
return (
<tbody>
{this.props.data.map((p, i) => <Msg socketio={this.props.socketio} key={i} data={p}/>)}
</tbody>
);
}
}
This is a single message:
class Msg extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.data.txt == nextProps.data.txt) {
return false;
}
return true;
}
render() {
var words = (this.props.data.txt || "").split(" ");
// this piece of code splits this.props.data.txt by spaces
// and converts it to array of words warr
// ...
return (
<tr className={this.trStyle()}>
<td>
<div className="msg">
{
warr.map((word, k) => <Word socketio={this.props.socketio} key={k} data={word.txt}/>)
}
</div>
</td>
</tr>
);
}
}
This class is for a single word inside a message. If a word is too long, function shortened() returns abbreviated version.
class Word extends React.Component {
shortened() { //....
render() {
return (
<span className={this.wordClass()} onClick={this.click}>{this.shortened()} </span>
);
}
}
I have implemented adding new messages using concat() instead of push(), following perfomance guidelines from Facebook and also implemented shouldComponentUpdate to avoid txt.split(" ") recalculation. But it didn't give me any perfomance boost. Can anybody give me some more advice or idea?
What is the size of response object which your are binding with react? we had similar issue,we had earlier 120kb size of json object which we were binding with react but later on optimize the json object and devided the json object in two part. 1. Pageload JSON object -It was having complete Json object with all fields and values(default)
2. Delta object- It was having only changed object field and it's value which we use to merge in React

Categories