Improve poor progress bar performance - reactjs

I am trying to pass progress state to other progress bar by using the MobX store.
There are two progress bars. One of them should be changing independently in a short period(about 2secs). I used setTimeOut to increase the current progress and it worked well. Then I tried to save the current progress value into the MobX store to pass to another component. After this, the performance of the progress bar was degraded.
render() {
...
if (tradingProgress.progress > 100) {
this.setState(prevState => ({
tradingProgress: {
...prevState.tradingProgress,
progress: 100,
},
}));
} else if (tradingProgress.isTrading) {
setTimeout(() => {
this.setState(prevState => ({
tradingProgress: {
...prevState.tradingProgress,
progress: prevState.tradingProgress.progress + 5,
},
}));
}, 100);
}
...
// save current progress to mobx store.
// convertProgress, setConvertProgress are in my mobx store.
if (tradingProgress.progress !== convertProgress && tradingProgress.isTrading) {
setConvertProgress(tradingProgress.progress); // in mobx store: this.convertProgress = currentProgress
}
I think using the MobX store frequently is the bottleneck, and I didn't use that store value in other components.
Thanks for spending your golden time for me.

MobX is unlikely to be the bottleneck here. What's more worrying is the use of setState in the render method. This is almost always a bad idea, because setState always causes another render. So you could end up rendering way more often than you actually meant to, which can definitely impact performance. Likewise, you don't want to update your MobX store from inside render, as it's very likely to trigger another render.
Instead, try to move your logic to other parts of the program, and have rendering be more of an 'afterthought': a final consequence of everything else you're doing.
Without really understanding your goal, here's a demonstration with a simple MobX store backing it.
import React from "react";
import ReactDOM from "react-dom";
import { action, decorate, observable } from "mobx";
import { inject, Provider, observer } from "mobx-react";
class UIStore {
convertProgress = 0;
setConvertProgress = progress => {
if (this.convertProgress < 100) {
this.convertProgress = progress;
}
};
}
decorate(UIStore, {
convertProgress: observable,
setConvertProgress: action
});
const store = new UIStore();
class TradingThing extends React.Component {
state = { progress: 0 };
componentDidMount() {
this.setState({ interval: setInterval(this.tick, 100) });
}
componentWillUnmount() {
clearInterval(this.state.interval);
}
tick = () => {
const { convertProgress, setConvertProgress } = this.props.store;
const { progress } = this.state;
setConvertProgress(convertProgress + 1);
if (progress < 100) {
this.setState({ progress: progress + 5 });
}
};
render() {
return (
<>
<div>Progress from component state: {this.state.progress}</div>
<div>Progress from MobX state: {this.props.store.convertProgress}</div>
</>
);
}
}
const TradingProgress = inject("store")(observer(TradingThing));
ReactDOM.render(
<Provider store={store}>
<TradingProgress />
</Provider>,
document.getElementById("root")
);
CodeSandbox
As you can see, the render method is very simple. This is usually a good sign!

Related

Render an array as list with onClick buttons

I'm new at ReactJs development, and I'm trying to render a list below the buttons I created with mapping my BE of graphQl query. I don't know what I'm doing wrong (the code has a lot of testing on it that I tried to solve the issue, but no success.)
The buttons rendered at getCategories() need to do the render below them using their ID as filter, which I use another function to filter buildFilteredCategoryProducts(categoryParam).
I tried to look on some others questions to solve this but no success. Code below, if need some more info, please let me know!
FYK: I need to do using Class component.
import React, { Fragment } from "react";
import { getProductsId } from "../services/product";
import { getCategoriesList } from "../services/categories";
//import styled from "styled-components";
class ProductListing extends React.Component {
constructor(props) {
super(props);
this.state = {
category: { data: { categories: [] } },
product: { data: { categories: [] } },
filteredProduct: { data: { categories: [] } },
};
this.handleEvent = this.handleEvent.bind(this);
}
async handleEvent(event) {
var prodArr = [];
const testName = event.target.id;
const testTwo = this.buildFilteredCategoryProducts(testName);
await this.setState({ filteredProduct: { data: testTwo } });
this.state.filteredProduct.data.map((item) => {
prodArr.push(item.key);
});
console.log(prodArr);
return prodArr;
}
async componentDidMount() {
const categoriesResponse = await getCategoriesList();
const productsResponse = await getProductsId();
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
}
getCategories() {
return this.state.category.data.categories.map((element) => {
const elName = element.name;
return (
<button id={elName} key={elName} onClick={this.handleEvent}>
{elName.toUpperCase()}
</button>
);
});
}
buildFilteredCategoryProducts(categoryParam) {
const filteredCategories = this.state.product.data.categories.filter(
(fil) => fil.name === categoryParam
);
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildCategoryProducts() {
const filteredCategories = this.state.product.data.categories;
let categoryProducts = [];
filteredCategories.forEach((category) => {
category.products.forEach((product) => {
const categoryProduct = (
<div key={product.id}>{`${category.name} ${product.id}`}</div>
);
categoryProducts.push(categoryProduct);
});
});
return categoryProducts;
}
buildProductArr() {
for (let i = 0; i <= this.state.filteredProduct.data.length; i++) {
return this.state.filteredProduct.data[i];
}
}
render() {
return (
<Fragment>
<div>{this.getCategories()}</div>
<div>{this.buildProductArr()}</div>
</Fragment>
);
}
}
export default ProductListing;
Ok, so this won't necessarily directly solve your problem,
but I will give you some pointers that would definitely improve some of your code and hopefully will strengthen your knowledge regarding how state works in React.
So first of all, I see that you tried to use await before a certain setState.
I understand the confusion, as setting the state in React works like an async function, but it operates differently and using await won't really do anything here.
So basically, what we want to do in-order to act upon a change of a certain piece of state, is to use the componentDidUpdate function, which automatically runs every time the component re-renders (i.e. - whenever there is a change in the value of the state or props of the component).
Note: this is different for function components, but that's a different topic.
It should look like this:
componentDidUpdate() {
// Whatever we want to happen when the component re-renders.
}
Secondly, and this is implied from the previous point.
Since setState acts like an async function, doing setState and console.log(this.state) right after it, will likely print the value of the previous state snapshot, as the state actually hasn't finished setting by the time the console.log runs.
Next up, and this is an important one.
Whenever you set the state, you should spread the current state value into it.
Becuase what you're doing right now, is overwriting the value of the state everytime you set it.
Example:
this.setState({
...this.state, // adds the entire current value of the state.
filteredProduct: { // changes only filteredProduct.
...filteredProduct, // adds the current value of filteredProduct.
data: testTwo
},
});
Now obviously if filteredProduct doesn't contain any more keys besides data then you don't really have to spread it, as the result would be the same.
But IMO it's a good practice to spread it anyway, in-case you add more keys to that object structure at some point, because then you would have to refactor your entire code and fix it accordingly.
Final tip, and this one is purely aesthetic becuase React implements a technique called "batching", in-which it tries to combine multiple setState calls into one.
But still, instead of this:
this.setState({ category: { data: categoriesResponse } });
this.setState({ product: { data: productsResponse } });
You can do this:
this.setState({
...this.state,
category: {
...this.state.category,
data: categoriesResponse,
}
product: {
...this.state.product,
data: productsResponse,
},
})
Edit:
Forgot to mention two important things.
The first is that componentDidUpdate actually has built-in params, which could be useful in many cases.
The params are prevProps (props before re-render) and prevState (state before re-render).
Can be used like so:
componentDidUpdate(prevProps, prevState) {
if (prevState.text !== this.state.text) {
// Write logic here.
}
}
Secondly, you don't actually have to use componentDidUpdate in cases like these, because setState actually accepts a second param that is a callback that runs specifically after the state finished updating.
Example:
this.setState({
...this.state,
filteredProduct: {
...this.state.filteredProduct,
data: testTwo
}
}, () => {
// Whatever we want to do after this setState has finished.
});

React, TypeScript - Using non-React objects to inject components into component tree

I'm working on a React Typescript project. A very simplified version of the project is below. I'm trying to use more traditional polymorphism here where I have components returned from vanilla Typescript objects (not React components) that are rendered in the component tree. The reason I want to do this is so that I can have polymorphic classes that I change at runtime and that manage their own state and business logic.
import React, { useEffect } from "react";
class ClickCounter {
private count: number;
constructor() {
this.count = 0;
}
IncrementCount() {
this.count += 1;
}
GetCount(): number {
return this.count;
}
}
interface Operation {
HandleMouseDown(event: React.MouseEvent<HTMLDivElement>): void;
GetComponents(): JSX.Element[];
}
class ClickCounterOperation implements Operation {
private clickCounter: ClickCounter;
constructor() {
const counter: ClickCounter = new ClickCounter();
this.clickCounter = counter;
}
HandleMouseDown(_: React.MouseEvent<HTMLDivElement>): void {
this.clickCounter.IncrementCount();
}
GetComponents(): JSX.Element[] {
const count: number = this.clickCounter.GetCount();
return [<div>you have clicked {count} times</div>];
}
}
export type AppState = {
currentOperation: Operation;
};
export class App extends React.Component<{}, AppState> {
constructor(props = {}) {
super(props);
const initialOperation: Operation = new ClickCounterOperation();
this.state = {
currentOperation: initialOperation,
};
this.HandleMouseDown = this.HandleMouseDown.bind(this);
}
HandleMouseDown(event: React.MouseEvent<HTMLDivElement>) {
console.log("Dispatching mouse down event to current operation");
this.state.currentOperation.HandleMouseDown(event);
}
render() {
return (
<div className="App" onMouseDown={this.HandleMouseDown}>
{this.state.currentOperation.GetComponents()}
<div>some other stuff to show</div>
</div>
);
}
}
export default App;
In the example above everything will render initially, but not after the count is updated. This is because react has no way of knowing that the state has changed and that a rerender is needed. What I'm currently doing is forcing React to rerender by passing down a RefreshOperationState callback to the Operation object that will call the App.setState() method but this feels very ugly and I don't want to do this.
Any way to achieve this kind of traditional polymorphism with React and have non-React objects inject components into the component tree and have the components update when appropriate? I understand what I am trying to do is not following the common React patterns of using Flux/Redux and having all/most app state in a store/s, but I'd like to make this app in a less functional and more OOP pattern where objects store their own state and are called polymorphicly.
Any suggestions?
As you've noted, mixing paradigms might be more trouble than it's worth. React relies on object reference equality to handle its rendering logic. Since you're mutating objects instead of creating new ones, it will never know to update.
Another rule of React state is that it is only data (never behavior and definitely not JSX), and you're trying to use both.
You could make components which use hooks like these, and then let your parent component choose how it composes itself based on what kind of Operation you want.
const useClickCounter = () => {
const [count, setCount] = useState(0);
const incCount = setCount(count + 1);
return [incCount, count];
};
The only other thing I've done is use the observable pattern on the class objects and have a React context in between which observes them and sends the updated state into the React component. The React context provider will cause all consumers beneath it to rerender with fresh state.
public subscribe = (fn: (state) => void) => {
this.observers.push(fn);
}
private update = async () => {
// Give new state to the Context which is subscribed
this.observers.forEach(fn => fn(state));
}
PS: if you're familiar with Redux, you could start with something like this:
const ClickCounter = () => {
const value = useSelector(selectedClickCounter);
return <div>{value}</div>;
};
const operations = {
clickCounter: {
RenderComponent: ClickCounter,
onPressDispatchData: { type: "increment-counter" },
},
};
const OperationHandler = () => {
const [currentOperation, setCurrentOperation] = useState(operations.clickCounter);
return <HandleMouse {...currentOperation} />;
};
const HandleMouse = (props) => {
return (
<div className="App" onMouseDown={props.onPressDispatchData}>
{props.RenderComponent}
<div>some other stuff to show</div>
</div>
);
};

Is there a way to make phaser game object into a react controlled component?

I'm trying to make a web game using react to work as the UI part of the game, and use phaser 3 as the engine. I've integrated phaser and react with ion-phaser/react, and it works well if all the data is there when the phaser app is ran, however I can't figure out if I can feed data to the phaser app while it's running.
For example:
const GameEngine = ({width, backgroundColor}) => {
const state = { /* ... game config */ };
let {initialize, game} = state;
return <IonPhaser game={game} initialize={initialize} />;
};
this works well if the props width and background are provided, but when they change, phaser doesn't update. Is there a way to continually feed data in the game from outside?
One option would be to publish events to Phaser's event system from React. This is pretty straightforward if you already have a reference to the Phaser game object in your React component.
In your React component, you could do something like:
game.events.emit('MY_EVENT', 'Hello world!')
Then, in any Phaser scene, you could do something like:
this.game.events.on('MY_EVENT', (event) => {
console.log(event)
})
I was also struggling with this issue.
Ended up settling for a hacky workaround.
Defined the game config object outside of the React state and keep checking for the instance keys existence on the game config object. See the getInstance function below:
import React from "react";
import Phaser from "phaser";
import { IonPhaser } from "#ion-phaser/react";
const game = {
/* ... game config */
};
function getInstance() {
if (game.instance) {
console.log("ready")
return Promise.resolve(game.instance);
} else {
console.log("waiting")
return new Promise((resolve) => {
const interval = setInterval(() => {
if (game.instance) {
clearInterval(interval);
resolve(game.instance);
}
});
}, 30);
}
}
export default class GameEngine extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false,
};
}
componentDidMount() {
getInstance().then((instance) => {
console.log(instance)
this.setState({ isReady: true });
});
}
render() {
const { initialize } = this.props;
return (
<IonPhaser
game={game}
initialize={initialize}
/>
);
}
}
I opened an issue here on the GitHub ion-phaser project
Following up to Ben's answer... For anybody coming here wondering how to get the game object... I added this to my code to get it working as an example. It's specific to my setup, but should be helpful to others:
in App.js:
import { useRef } from 'react';
in function App() you'll want: const gameRef = useRef(null)
pass it to your IonPhaser component: <MyIonPhaserComponent gameRef={gameRef} ...
in your IonPhaser component:
...
return (
<>
<IonPhaser ref={gameRef} ...
</>
)
...
in whatever other react component you want to emit events from, pass it gameRef={gameRef} as well, then use it with:
var PhaserGameInstance = gameRef.current.getInstance().then( game => phaserGameObject = game);
someFunctionToCallOnClick(){
phaserGameObject.events.emit("MY_CUSTOM_EVENT", "hello");
}
If not in a function, the phaserGameObject will probably be undefined until the getInstance() resolves
Then back in the game setup in MyIonPhaserComponent, within the create function:
this.game.events.on('MY_CUSTOM_EVENT', (event) => {
console.log(event)
})
At this point, your other react component should emit events that are immediately picked up within Phaser

React get new data in componentDidUpdate

I have a component which receives a list of news from two or three APIs. the first time the component renders, the apis are called and data is rendered in componentDidMount
something like this:
componentDidMount() {
this.state.platforms.forEach((platform, i) => {
let objToSend = {
phrase: this.props.searchParams.phrase,
// this is the main place when input data changes
...this.props.searchParams.general_params,
...this.props.searchParams.platforms[i].api_params,
stringPath: platform,
apiPath: this.props.searchParams.platforms[i].apiPath,
}
this.props.loadData(objToSend)
// this is the api call using redux which sends data as props to this component
}
new when the phrase change, I want this component to re-render and re-run the componentDidMount, but it's not working because componentDidMount will run once.
So I used componentDidUpdate, but as there are a number of calls so the api is being updated continuesly.
How can I make the component re-render and re-run componentDidMount every time I change the phrase
You can use componentDidUpdate arguments (previousProps, previousState) to check whether some new changes happened.
Example
componentDidUpdate(previousProps, previousState) {
if (previousProps.phrase !== this.props.phrase) {
//Do whatever needs to happen!
}
}
I stopped the infinite loop for my situation this way.
Here's one way to do something() when you re-render.
import React, { Component } from 'react';
import { render } from 'react-dom';
const fakeFetch = (n) => {
console.log(`Doing fake fetch: ${n}`)
return n
}
class App extends Component {
state = {
value: false,
number: 0,
}
componentDidMount() {
const number = fakeFetch(this.state.number + 1);
this.setState({ number })
}
componentDidUpdate(prevProps, prevState) {
if (prevState.value !== this.state.value) {
const number = fakeFetch(this.state.number + 1);
this.setState({ number })
}
}
toggle = () => {
this.setState(({ value }) => ({ value: !value }))
}
render() {
return (
<React.Fragment>
<h1>Number: {this.state.number}</h1>
<button onClick={this.toggle}>re-render</button>
</React.Fragment>
);
}
}
render(<App />, document.getElementById('root'));
Live example here.

Adding Redux to an existing React app

I've been working on a React app and have gotten to a point where I'll need Redux to handle some aspects of it.
After reading a bunch of tutorials, I'm fairly stuck on how to make my "smarter" components "dumber" and move functions into my actions and reducers.
So, for example, one aspect of the app is more of a to-do list style.
One of my classes starts like this:
export default class ItemList extends React.Component {
constructor() {
super();
this.state = { items: [],
completed: [],
};
this.addItem = this.addItem.bind(this);
this.completeItem = this.completeItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
}
addItem(e) {
var i = this.state.items;
i.push({
text: this._inputElement.value,
paused: false,
key: Date.now()
});
this.setState({ items: i });
e.preventDefault();
this._inputElement.value = '';
this._inputElement.focus();
}
completeItem(e) {
this.deleteItem(e);
var c = this.state.completed;
c.push({
text: e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML,
paused: false,
key: Date.now()
});
this.setState({ completed: c });
}
deleteItem(e) {
var i = this.state.items;
var result = i.filter(function(obj) {
return obj.text !== e.target.parentNode.parentNode.getElementsByClassName('item-name')[0].innerHTML;
});
this.setState({ items: result });
}
// ... more irrelevant code here ...
// there's a function called createTasks that renders individual items
render() {
var listItems = this.state.items.map(this.createTasks);
return <div className="item-list">
<form className="form" onSubmit={this.addItem}>
<input ref={(a) => this._inputElement = a}
placeholder="Add new item"
autoFocus />
<button type="submit"></button>
</form>
{listItems}
</div>;
}
}
So, as you can see, it's very logic-heavy. I've started adding Redux by adding a <Provider> in my index file, and made a basic reducers file that is fairly empty so far:
import { combineReducers } from 'redux';
const itemList = (state = {}, action) => {
};
// ... other irrelevant reducers
const rootReducer = combineReducers({
itemList,
// ...
});
export default rootReducer;
...and I've made an actions file that doesn't have much in it yet either.
I've been struggling to figure out:
Most actions I've seen examples of just return some kind of JSON, what do I return in the reducer that uses that JSON that my component can use?
How much of my component logic is reusable, or should I just forget it? What is the best way to go about this to reuse as much code as I've written as possible?
First of all you need to understand the overall picture of how redux works with react.
Before coming to that lets first understand what are smart components and dumb components.
Smart Components
All your code logic needs to be handled here
They are also called containers.
They interact with the store(aka state management) to update your components.
Dumb Components
They just read props from your containers and render you components
This is just the UI view and should not contain any logic.
All styling/html/css comes in your dumb components.
Here is an amazing piece of article which you can go through to understand smart and dumb components if you still have doubts.
Ok, now lets try understanding how redux works:-
Your smart components(aka containers) interact with your redux store
You fire actions from your containers.
Your actions call your apis
The result of your action updates the store through a reducer
You containers read the store through mapStateToProps function and as soon as value in store changes it updates your component.
Now lets consider your todo example
TodoListContainer.js
class TodoListContainer extends Component {
componentWillMount () {
// fire you action action
}
render () {
return (
<Todos todos=={this.props.todos} />
)
}
}
function mapStateToProps(state) {
const {todos} = state;
return {
todos;
}
}
export default connect(mapStateToProps)(TodoListContainer)
TodoList.js
class TodoList extends Component {
renderTodos() {
return this.props.todos.map((todo)=>{
return <Todo todo={todo} key={todo.id} />
})
}
render () {
return () {
if (this.props.todos.length === 0) {
return <div>No todos</div>
}
return (
<div>
{this.renderTodos()}
</div>
)
}
}
}
export default class TodoList
Todo.js
class Todo extends Component {
render () {
return (
<div>
<span>{this.props.todo.id}</span>
<span>{this.props.todo.name}</span>
</div>
)
}
}
Reducer
export default function todos(state={},action) {
switch (action.type) {
case 'RECEIVE_TODOS':
return Object.assign(state,action.todos);
}
}
action
function fetchTodos() {
return(dispatch) => {
axios.get({
//api details
})
.then((res)=>{
dispatch(receiveTodos(res.todos))
})
.catch((err)=>{
console.warn(err)
})
}
}
function receiveTodos(todos) {
return {
type: 'RECEIVE_TODOS',
todos
}
}
Now if you have read redux documentation you would see that actions return objects then how would i call my api there which returns a function instead of an object. For that I used redux thunk about which you can read here.
I gave you an example in which you can fetch todos. If you want to do other operations like deleteTodo, addTodo, modifyTodo then you can do that in appropriate components.
DeleteTodo - you can do in TodoListContainer.
AddingTodo - you can do in TodoListContainer.
Changing State(completed/Pending) - you can do in TodoListContainer.
ModifyingTodo - you can do in TodoContainer.
You can also check out here for a detailed example, but before that I would say just should go through basics of redux which you can find here
P.S: I wrote code on the fly so it might not work properly but it should work with little modification.

Resources