Reputation: 3488
I'm new in React, I'm building a web app that display a video and a box with transcription words.
The words can be edited and could have some data to be displayed with tooltips or dialogs.
The main React component download the data from an API and compose the page made of some components: top bar, side bar, player and transcription box.
Once downloaded I store json data in state and pass it into components via props.
The component process the json objects (the words) and create a lot of stateless components
, the words, with some props too (for example, the tooltips, key
prop, the id, the className...) then i put all of them inside an array and return to the transcription box.
What I've notice is that every time I edit the main state of the parent element (not the data used from transcription component) the words are re-rendered (I enabled highlighy updates
in react tab of chrome's dev tools).
This makes no problem with a small array of words, like 50-100 elements, but with thousands of items it produce a big latency between user actions and graphical updates.
I'm probably missing the point: what I'm doing wrong with it?
Can you please show me how can I process large amount of JSON object items to react components in the correct way?
EDIT: I cut off the not interested code, leaving the minimal code to reproduce what I meant.
The main component that contains transcription and other components in this example is Info.js. I put the data download by API in a separated file because it's very long data, you can find it there: https://pastebin.com/nw1A391y
Info.js
import React, { Component } from 'react';
import TranscriptionBox from '../partials/TranscriptionBox.js';
class Info extends Component {
/**
* Set default props
* @type {Object}
*/
static defaultProps = {
name: 'Info'
}
/**
* Component constructor
* @param {Object} props [Component props]
*/
constructor (props) {
// Make property available in this module
super(props);
this.state = {
task: {
attachments: [],
automatic_created: true,
clips: [],
creation_user: "https://example.com/",
end_datetime: "2019-08-02T04:30:03.022",
id: 498,
metadata: [],
owner_user: null,
source: "https://example.com/",
source_repr: "Hello",
start_datetime: "2019-08-02T04:00:03",
status: "https://example.com/",
status_repr: "created",
url: "https://example.com/"
},
transcription: [] // NOTE: Put here the transcription form the file attached!
}
}
onWordClick = () => {
console.log("onWordClick");
this.setState({
hello: 'onWordClick'
})
}
onActionChange = () => {
console.log("action change");
this.setState({
hello: 'onActionChange'
})
}
onTaskProgressChange = () => {
console.log("onTaskProgressChange");
this.setState({
hello: 'onTaskProgressChange'
})
}
render() {
return (<div>
<TranscriptionBox
key="transcription_box_component"
ref="transcription_box_component"
task={this.state.task}
transcription={this.state.transcription}
taskLoaded={true}
action={null}
progress={null}
selected={null}
isSpecialPressed={false}
handleActionChange={this.onActionChange}
handleWordClick={this.onWordClick}
handleProgressChange={this.onTaskProgressChange}
/>
</div>);
}
}
export default Info;
This is the transcription box component, that render API data and make words object inside a scroll box.
TranscriptionBox.js
import React from 'react';
// Import classes and components
import Word from './Word.js';
// Import style
import '../styles/TranscriptionBox.css'
class TranscriptionBox extends React.Component {
/**
* Set default props
* @type {Object}
*/
static defaultProps = {
name: 'TranscriptionBox',
tempData: undefined
}
/**
* Component constructor
* @param {Object} props [Component props]
*/
constructor (props) {
// Make property available in this module
super(props);
// Set default state
this.state = {
visible: true,
value: '',
transcription: this.props.transcription,
lastPostion: 0,
inDownloading: false,
downloaded: false
}
}
/*
|-------------------------------------------------------------------------|
| ACTION HANDLE |
|-------------------------------------------------------------------------|
*/
/**
* Handle click action on transcription box word
* @param Event ev Double click event
* @return void
*/
handleWordClick = (ev) => {
// Get current action
let action = this.props.action;
// Get the target of click
let target = ev.currentTarget;
// Init word object
let data = false;
// Manage wordclick
this.props.handleActionChange(undefined);
}
/*
|-------------------------------------------------------------------------|
| ELEMENTS GENERATION |
|-------------------------------------------------------------------------|
*/
/**
* Call the ReactJS dangerouslySetInnerHTML to transform string into HTML
* @return {HTML} Html code
*/
renderTranscription = () => {
console.log("*** TB@renderTranscription");
// Create HTML tag from text transcription
return this._elaborateTranscription(this.props.transcription);
}
/**
* Elaborate transcription and create all tags
* // COMBAK: make clips tag manage array of clips
* @param array transcription The transcription to elaborate
* @return array The transcription elaborated
*/
_elaborateTranscription = (transcription) => {
// Init vars
let reactWord, word, wordObject, seconds;
// Init array
let elaborated = [];
// If transcription is null or empty transcription
if (!transcription || transcription.length < 1) {
// Init reactWord to be pushed
reactWord = <em key="no-transcription">Loading...</em>;
// Return a placeholder
elaborated.push(reactWord);
// Return elaborated
return elaborated;
}
// Get this context to me var
let me = this;
// Iterate each word and take the iterator count
transcription.forEach(function (transcriptionWord, i) {
// Create a copy
wordObject = {...transcriptionWord};
// If has no id
if (!transcriptionWord['id']) {
// Compose an unique id
wordObject['id'] = "word-" + i;
}
// Get seconds
seconds = parseInt(transcriptionWord['time']);
// Calculate the seconds at
wordObject['seconds'] = seconds;
// Create a new word react element
word = <Word key={wordObject.id}
handleDoubleClick={me.handleWordDoubleClick}
handleClick={me.handleWordClick}
{...wordObject} />;
// Push created tag into array
elaborated.push(word);
});
// Return elaborated text
return elaborated;
}
/*
|-------------------------------------------------------------------------|
| RENDER |
|-------------------------------------------------------------------------|
*/
/**
* Render component
* @return {} []
*/
render() {
return (
<div
id="transcription-box"
ref="transcription-box"
className='task-transcription-box-container border-secondary'>
{this.renderTranscription()}
</div>
);
}
}
// Export default component to be accessible in other components
export default TranscriptionBox;
This is the word stateless component.
Word.js
import React from 'react';
import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
const Word = props => {
return <div
key={props.id}
className='transcription-word'
data-id={props.id}
data-metadata=''
data-clips=''
data-seconds=''
data-datetime=''
onDoubleClick={props.handleDoubleClick}
onClick={props.handleClick}
>
{props.data}
</div>;
};
export default Word;
I make a short video of the problem. In this gif you can see that each time I press on a word (the method handleClick
is called) and an useless prop of Info.js
state change. No word data is touched, but chrome show me with light blue border that react has re-render the words.
Upvotes: 0
Views: 470
Reputation: 2472
Well from what I understood from your question you are rendering a lot of components unnecessarily?
What you can do for those stateless components
is to control when they render. For example, render only when they receive different props. (making them a stateless pure component). You can use memo() for that. For example:
import React, { memo } from "react";
import { isEqual } from "../definedByYou";
const YourComponent = props => {
return <div>{props.text}</div>;
};
// When text changes (isEqual returns false) => component will render
function arePropsEqual(prevProps, nextProps) {
return isEqual(prevProps.text, nextProps.text);
}
export default memo(YourComponent, arePropsEqual);
Where isEqual
is a function defined by you that returns true
when the compared props are the same
EDIT
Acordion to your Word component try:
import React, { memo } from "react";
import { TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';
import { Icon } from 'office-ui-fabric-react/lib/Icon';
const Word = props => {
return <div
key={props.id}
className='transcription-word'
data-id={props.id}
data-metadata=''
data-clips=''
data-seconds=''
data-datetime=''
onDoubleClick={props.handleDoubleClick}
onClick={props.handleClick}
>
{props.data}
</div>;
};
// if your data prop is an object you can't just use "==="
function arePropsEqual(prevProps, nextProps) {
return prevProps.id === nextProps.id && prevProps.data === nextProps.data;
}
export default memo(Word, arePropsEqual);
EDIT 2
Using shouldComponentUpdate in your TranscriptionBox component:
shouldComponentUpdate(nextProps, nextState) {
return true; // Here check differences in state and props :)
}
Upvotes: 1