How to properly render heavy web socket stream in React? - reactjs

I currently have something like this:
const socket = require('socket.io-client')('https://example.com');
(....)
// Listen to the channel's messages
socket.on('m', message => {
// this is a Redux action that updates the state
this.props.updateTrades(message);
});
The reducer looks like this:
case actions.UPDATE_TRADES:
return {
...state,
trades: [
...state.trades,
action.trade
]
};
I've tried not using redux and just to the following:
socket.on('m', message => {
this.setState(state => {
if (state.trades.length > 99) {
state.trades.splice(0, 1);
}
return {
trades: [
...state.trades,
message
]
});
});
I don't need to keep increasing my trades array. I'm happy just to keep around 100 items or so...
Socket is sending around 15 messages / second.
My problem is: I can't seem to render the messages in real-time! It just freezes. I guess the stream is just too fast? Any suggestions?
Thanks in advance!

The thing is to do the minimum possible and when the trades change only draw what has change and not all of the elements of the array.A technique that I use is to keep a cache map of already drawn obj, so in the render method I only render the new incoming elements.
Take a look at https://codesandbox.io/s/wq2vq09pr7
class RealTimeList extends React.Component {
constructor(props) {
super(props);
this.cache = [];
}
renderRow(message, key) {
return <div key={key}>Mesage:{key}</div>;
}
renderMessages = () => {
//let newMessages=this,props.newMessage
let newElement = this.renderRow(this.props.message, this.cache.length);
this.cache.push(newElement);
return [...this.cache];
};
render() {
return (
<div>
<div> Smart List</div>
<div className="listcontainer">{this.renderMessages()}</div>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "hi" };
}
start = () => {
if (this.interval) return;
this.interval = setInterval(this.generateMessage, 200);
};
stop = () => {
clearTimeout(this.interval);
this.interval = null;
};
generateMessage = () => {
var d = new Date();
var n = d.getMilliseconds();
this.setState({ title: n });
};
render() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={this.start}> Start</button>
<button onClick={this.stop}> Stop</button>
<RealTimeList message={this.state.message} />
</div>
);
}
}
The class RealTime List have a cache of elements.Let me know if this helps.

It's probably not a good idea to try to render all of the changes. I think you should try rendering them in batches so you only update once every few seconds, that should help.

Related

React marking which page i'm on

I'd like for my app to indicate which page I'm currently on. Page numbers get calculated based on the length of an array, and when I move between them, the current page would have a different background. The current page is saved inside the state as well as in local storage.
So I have a CSS class .mark-page {background-color} and I thought I'd just make a ternary inside the button, but it didn't work, however, I'm not sure which parameters to input. Any ideas?
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(allFacts.length / factsPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map((number) => {
return (
<button key={number} id={number} onClick={this.handleClick}>
{number}
</button>
);
});
return <div id="paging">{renderPageNumbers}</div>;
#KavinduVindika sure, posting here because it won't let me edit my post:
class Body extends React.Component {
constructor() {
super()
this.state = {
isLoading: true,
allFacts: [],
currentPage: null,
factsPerPage: 15,
error: null,
}
this.handleClick = this.handleClick.bind(this)
}
componentDidMount() {
gets API, saves it into state and sets currentPage to 1 if the page had just
loaded }
handleClick(event) {
this.setState ({
currentPage: localStorage.getItem("currentPage")
})
let currentPage = Number(event.target.id)
localStorage.setItem("currentPage", JSON.stringify(currentPage))
this.setState ({currentPage: currentPage})
}
render() {
const {allFacts, currentPage, factsPerPage } = this.state
const indexOfLastFact = currentPage * factsPerPage
const indexOfFirstFact = indexOfLastFact - factsPerPage
const allFactsSliced = allFacts.slice(indexOfFirstFact, indexOfLastFact)
console.log(allFactsSliced)
const renderAllFacts = this.state.isLoading ?
<div id="loading"><h3 className="fact-div" >Please wait.<br/> The cats are disclosing their secrets.</h3></div> :
allFactsSliced.map((fact, index) => {
return <div className="fact-div" key={index}>
<p className="fact-num">Fact # {(index+1) + (currentPage-1) * factsPerPage}:</p>
<p className="fact-txt">{fact.fact}</p>
</div>
})
hope this is all the important bits

React: Changing parent state not rendering child

I am extremely new to react and am building a simple todo list app. I am trying to edit data from my child component and send it back to my parent. When I am printing console logs the parent state seems to be getting set correctly, but the child elements are not refreshing. Am I doing something conceptually wrong here?
I have tried to share the entire code as I am not sure whether it is correct conceptually. I am new to JS. When the handleSave() and handleComplete() are called i can see correct values getting returned and set to my PArent State, but there is no refresh of the child components.
Below is my Parent class code.
class App extends Component {
state = {
taskList: [
]
};
saveEventHandler = data => {
console.log("I am in saveEventHandler");
var uniqid = Date.now();
const taskList = [...this.state.taskList];
taskList.push({
id: uniqid,
taskDescText: data,
isFinished: false
});
console.log(taskList);
this.setState({'taskList':taskList});
};
deleteEventHandler = (index) => {
const taskList = [...this.state.taskList];
taskList.splice(index,1)
this.setState({'taskList':taskList});
}
editEventHandler = (index,data) => {
var uniqid = Date.now();
console.log("In edit event handler")
console.log(data)
console.log(index)
const taskList = [...this.state.taskList];
taskList[index] = {
id: uniqid,
taskDescText: data,
isFinished: false
}
this.setState({'taskList':taskList});
console.log(this.state.taskList)
}
handleComplete = (index) => {
console.log("In complete event handler")
const taskList = [...this.state.taskList];
const taskDescriptionOnEditIndex = taskList[index]
taskDescriptionOnEditIndex.isFinished = true
taskList[index] = taskDescriptionOnEditIndex
this.setState({'taskList':taskList});
console.log(this.state.taskList)
}
render() {
return (
<div className="App">
<h1>A Basic Task Listing App </h1>
<CreateTask taskDescription={this.saveEventHandler} />
{this.state.taskList.map((task, index) => {
return (
<Task
taskDescText={task.taskDescText}
taskCompleted={task.isFinished}
deleteTask={() => this.deleteEventHandler(index)}
editTask={(editTask) => this.editEventHandler(index,editTask)}
handleComplete={() => this.handleComplete(index)}
editing='false'
/>
);
})}
</div>
);
}
}
export default App;
and my child class code
export default class Task extends React.Component {
state = {
editing : false
}
notCompleted = {color: 'red'}
completed = {color: 'green'}
textInput = React.createRef();
constructor(props) {
super(props);
this.state = props
}
handleEdit = () => {
this.setState({editing:true});
}
handleSave = () => {
this.props.editTask(this.textInput.current.value);
this.setState({editing:false});
};
editingDiv = (<div className = 'DisplayTask'>
<span className='TaskDisplayText' style={!this.props.taskCompleted ? this.notCompleted: this.completed}>{this.props.taskDescText} </span>
<button label='Complete' className='TaskButton' onClick={this.props.handleComplete}> Complete</button>
<button label='Edit' className='TaskButton' onClick={this.handleEdit}> Edit Task</button>
<button label='Delete' className='TaskButton' onClick={this.props.deleteTask}> Delete Task</button>
</div> );
nonEditingDiv = ( <div className = 'DisplayTask'>
<input className='TaskDescEditInput' ref={this.textInput}/>
<button label='Save' className='TaskButton' onClick={this.handleSave} > Save Task</button>
<button label='Delete' className='TaskButton' onClick={this.props.deleteTask}> Delete Task</button>
</div>);
render() {
return (
!this.state.editing ? this.editingDiv : this.nonEditingDiv
)
};
}
Move your editingDiv and nonEditingDiv definitions inside render() method. Since you're defining them as instance variables, they're initialized once, and never get re-rendered again with new prop values.
By moving them to render() method, render() which is called every time when there's a prop update will pick up the new prop values.

Reactjs show hide multiple components

Newbie React question here on show hide functionality.
I have a state of 'show' that I set to false:
this.state = {
show: false,
};
Then I use the following function to toggle
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
And my display is
{this.state.show && <xxxxxx> }
This all works fine. However I want to apply the function it to multiple cases (similar to accordion, without the closing of other children. So I change my constructor to
this.state = {
show: [false,false,false,false,false,false]
};
and this to recognise there are 6 different 'shows'.
{this.state.show[0] && <xxxxxx> }
{this.state.show[1] && <xxxxxx> } etc
But where I get stuck is how to account for them in my toggleDiv function. How do I insert the square bracket reference to the index of show (if this is my problem)?
toggleDiv = () => {
const { show } = this.state;
this.setState({ show : !show })
}
Thanks for looking.
First of all I'd suggest you not to rely on current state in setState function, but to use the callback option to be 100% sure that you are addressing to the newest state:
this.setState((prevState) => ({ show: !prevState.show }));
How to deal with multiple elements?
You'll have to pass the index of currently clicked element.
{yourElements.map((elem, i) => <YourElem onClick={this.toggleDiv(i)} />)}
and then inside your toggleDiv function:
toggleDiv = (i) => () => {
this.setState((prevState) => {
const r = [...prevState.show]; // create a copy to avoid state mutation
r[i] = !prevState.show[i];
return {
show: r,
}
}
}
Use an array instead of a single value. In your toggle div function make a copy of the state array make necessary changes and push the entire array back up to state at the end.
This is some simplified code showing the workflow I described above
export default class myClass extends React.Component{
constructor(props){
super(props);
this.state = { show: new Array(2).fill(false) };
}
//you need a index or id to use this method
toggleDiv = (index) => {
var clone = Object.assign( {}, this.state.show ); //ES6 Clones Object
switch(clone[index]){
case false:
clone[index] = true
break;
case true:
clone[index] = false
break;
}
this.setState({ show: clone });
}
render(){
return(
<div>
{ this.state.show[0] && <div> First Div </div> }
{ this.state.show[1] && <div> Second Div </div> }
{ this.state.show[2] && <div> Third Div </div> }
</div>
)
}
}

Delay formatting input

I'm trying to find a way to make an editable number with decimals.
Requirements:
I want the value readable by other components. For this reason I store it in a parent state.
The value may be updated by a fetched value. Currently this happens for multiple variables in the parent component.
It doesn't matter if the actual value has more places as long as the input only shows x places.
I'm running into a problem when converting to the fixed value - specifically on Chrome which happens to be the browser of choice. I wrote up a codepen:
https://codepen.io/j1dopeman/pen/wQJNzQ
Only C is using the fixed value. It's stored in the parent state as 'places'. When trying to edit C it immediately converts it to the fixed value which moves the cursor and ruins the input. Backspace also doesn't work as expected. I've tried debouncing the change which didn't work - react won't show the change in the meantime and the second number will get messed up when it eventually updates. I've tried using local state but that interferes with an outside fetch propagating the values down and I think there were other problems too. I just want to enforce the decimal places but not immediately. Someone should be able to type 1.25 or backspace and type a new number with it doing the conversion like a second later.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: {
a: { val: 0 },
b: { val: 0 },
c: { val: 1.5, places: 2 },
d: { val: 0 },
e: { val: 0 },
f: { val: 0 }
}
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(value) {
const update = newVals => {
return state => {
let nv = {};
for (let [key, val] of Object.entries(newVals)) {
nv = { ...nv, [key]: Object.assign(state.inputs[key], val) };
}
const ni = Object.assign(state.inputs, nv);
return { inputs: ni };
};
};
//-----
this.setState(update(value));
}
render() {
return (
<div className="App">
<header className="App-header">
<h1>Calc</h1>
</header>
<InputArea
inputs={this.state.inputs}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
class InputArea extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(value) {
this.props.onInputChange(value);
}
render() {
const inputList = [];
for (let [key, value] of Object.entries(this.props.inputs)) {
inputList.push(
<Variable
key={key}
name={key}
value={value}
onInputChange={this.handleInputChange}
/>
);
}
return (
<div className="input">
<h1>Input</h1>
<div className="input-area">{inputList}</div>
</div>
);
}
}
class Variable extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(e) {
let v = this.props.value;
v.val = Number(e.target.value);
this.props.onInputChange({ [this.props.name]: v });
}
render() {
const label = this.props.name;
let val = this.props.value.val;
if (this.props.value.places !== undefined)
val = val.toFixed(this.props.value.places);
return (
<div className="flex-row">
<label>{label}</label>
<input
className="variable-input"
type="number"
name={label}
value={val}
step="any"
onChange={this.handleInputChange}
/>
</div>
);
}
}
So wrapping with parseFloat:
val = parseFloat(val.toFixed(this.props.value.places));
seems to not completely botch the input as the person is typing and backspace mostly works. So that's what I'm using for now. I would still like to know if there's a way to delay formatting an input.

How to debounce user input in reactjs using rxjs

My problem may be a trivial one but I wasn't able to find the answer so far.
How can I defer (debounce) updating state in React while user is typing, to avoid unnecessary updates?
Having <input onChange={this.onChange} .../>, how can I bind onChange event with rxjs? Should I try to make this input observable or should I use FromEventPattern?
In both cases I have no idea how to bind React events with rxjs.
The second question is whether the user will see any input changes during debounce?
Solution #1
Using subjects:Fiddle
const state = new Rx.Subject()
.debounceTime(1000)
.scan((acc) => {
return ++acc
}, 0).do(::console.log)
const handler = (e) => {
state.next(e)
}
state.startWith(0).subscribe((clicks) => {
ReactDOM.render(<button onClick={handler}>Clicked {clicks}</button>, document.querySelector('#app'))
})
Solution #2
Using rxjs's fromEvent: Fiddle
// Intial render so element exists in dom (there is probably a better pattern)
ReactDOM.render( <button id='clickMe'>Click Me</button>, document.querySelector('#app'))
const clicks = Rx.Observable
.fromEvent(document.getElementById('clickMe'), 'click')
.do(::console.log)
.debounceTime(1000)
.scan((acc) => {
return ++acc
}, 0)
clicks.subscribe((clicks) => {
ReactDOM.render( <button id='clickMe'>Click Me {clicks}</button>, document.querySelector('#app'))
})
Solution #3
Note: highly experimental, and just something I tried to do for fun.
This is more for an action based architecture, where you have actions that change your state (flux). This is a handler that is fully standalone. It is used with a custom operator 'fromEventArgs': Fiddle (look at the console)
const handler = (e) => {
Rx.Observable
.fromEventArgs(e, 'UniqueKey')
.debounceTime(1000)
.subscribe(x => console.log('Send an action', x))
}
based on omerts propositions, (especially solution #1) here is my final code
input: Rx.Subject<any>;
constuctor(...){
this.input = new Rx.Subject();
this.input.debounce(1000).subscribe(this.processInput);
}
handleChange = event => {
event.persist();
this.input.onNext(event);
};
processInput = x => {
// invoke redux/flux action to update the state
}
render(){
...
<input onChange={this.handleChange} ... />
...
}
try like this:
class MyScene extends React.Component {
constructor(props) {
var onChangeTextObservable = Rx.Observable.fromEventPattern(
(handler) => {this.onChangeText = handler}
);
onChangeTextObservable.subscribe(x => console.log(x));
}
render() {
return (
<TextInput onChangeText={this.onChangeText}>
)
}
}
I keep getting to this question, while I prefer this solution from another one.
Copied below for simplicity. Please upvote the original.
You will need to cretae observable from change events(for example
using Subject) and then debounce on that.
Here is the fully featured example for you:
class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
search: '',
debounced: '',
};
this.onSearch$ = new Rx.Subject();
this.onSearch = this.onSearch.bind(this);
}
componentDidMount(){
this.subscription = this.onSearch$
.debounceTime(300)
.subscribe(debounced => this.setState({ debounced }));
}
componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
onSearch(e) {
const search = e.target.value;
this.setState({ search });
this.onSearch$.next(search);
}
render() {
const { search, debounced } = this.state;
return (
<div>
<input type="text" value={search} onChange={this.onSearch} />
<div>debounced value: {debounced}</div>
</div>
);
}
}
ReactDOM.render(
<Search />,
document.getElementById('root')
);
<script src="https://unpkg.com/rxjs#5.4.0/bundles/Rx.min.js"></script>
<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="root"></div>

Resources