Central State Management without Redux or Mobx? - reactjs

Recently I contemplated the idea of having central state management in my React apps without using Redux or Mobx, instead opting to create something similar to the application class in Android. In any event, I implemented something similar to this:
Create a store folder and a file called store.js in it whose contents are:
// State
let state = {
users: {},
value: 0
};
// Stores references to component functions
let triggers = [];
// Subscription Methods
export const subscribe = trigger => {
triggers.push(trigger);
trigger();
}
export const unsubscribe = trigger => {
let pos = -1;
for (let i in triggers) {
if (triggers[i]===trigger) {
pos = i;
break;
}
}
if (pos!==-1) {
triggers.splice(pos, 1);
}
}
// Trigger Methods
let triggerAll = () => {
for (let trigger of triggers) {
trigger();
}
}
// State Interaction Methods
export const setUser = (name, description) => {
state.users[name] = description;
triggerAll();
}
export const removeUser = name => {
if (name in state.users) {
delete state.users[name];
}
triggerAll();
}
export const getAllUsers = () => {
return state.users;
}
export const getUser = name => {
if (!(name in state.users)) {
return null;
}
return state.users[name];
}
export const getValue = () => {
return state.value;
}
export const setValue = value => {
state.value = value;
triggerAll();
}
And connecting to this store in the following manner:
// External Modules
import React, { Component } from 'react';
import {Box, Text, Heading} from 'grommet';
// Store
import {subscribe, unsubscribe, getAllUsers} from '../../store/store';
class Users extends Component {
state = {
users: []
}
componentDidMount() {
subscribe(this.trigger); // push the trigger when the component mounts
}
componentWillUnmount() {
unsubscribe(this.trigger); // remove the trigger when the component is about to unmount
}
// function that gets triggered whenever state in store.js changes
trigger = () => {
let Users = getAllUsers();
let users = [];
for (let user in Users) {
users.push({
name: user,
description: Users[user]
});
}
this.setState({users});
}
render() {
return <Box align="center">
{this.state.users.map(user => {
return <Box
style={{cursor: "pointer"}}
width="500px"
background={{color: "#EBE7F3"}}
key={user.name}
round
pad="medium"
margin="medium"
onClick={() => this.props.history.push("/users/" + user.name)}>
<Heading margin={{top: "xsmall", left: "xsmall", right: "xsmall", bottom: "xsmall"}}>{user.name}</Heading>
<Text>{user.description}</Text>
</Box>
})}
</Box>;
}
}
export default Users;
Note. I've tested this pattern on a website and it works. Check it out here. And I apologize I am trying to keep the question concise for stackoverflow, I've provided a more detailed explanation of the pattern's implementation here
But anyway, my main question, what could be the possible reasons not to use this, since I assume if it was this simple, people wouldn't be using Redux or Mobx. Thanks in advance.

That's what Redux and MobX basically do, you are wrong in thinking that at their core concept they are much different. Their size and complexity came as a result of their effort to neutralize bugs and adapt to a vast variety of application cases. That's it. Although they might be approaching the task from different angles, but the central concept is just that. Maybe you should familiarize your self with what they actually do underneath.
Btw, you do not need to store redundant state in your component, if all you need is to trigger the update. You can just call forceUpdate() directly:
// function that gets triggered whenever state in store.js changes
trigger = () => {
this.forceUpdate();
}
That's similar to what Redux and MobX bindings for react do under the hood.

Related

Create helper function for a (click handler) function to reuse in multiple React components

For a 'back' button I've created below (onClick) handler function in my React app.
const { length: historyLength, goBack, replace } = useHistory();
const handleBack = () => {
if (historyLength > 2) {
goBack();
} else {
// History length is 2 by default when nothing is pushed to history yet
// https://stackoverflow.com/questions/9564041/why-history-length-is-2-for-the-first-page
replace(HomePage);
}
};
Then I am passing the onClick handler to my child component like: <Button onClick={handleBack}/>
I am using this handleBack function in multiple places in my React app. Is it a good approach make it e.g. a helper function and how exactly?
I also don't see any issue with the code or using it as a utility callback.
Is it a good approach make it e.g. a helper function and how exactly?
Anytime you can make your code more DRY (Don't Repeat Yourself) it's generally a good thing. My personal rule-of-thumb is if I've written the same utility code a third time I'll spend a bit of time to refactor it into a common utility (and unit test!!).
I might suggest creating a custom hook to return the back handler.
Example:
import { useHistory } from 'react-router-dom';
const useBackHandler = () => {
const history = useHistory();
const handleBack = React.useCallback(() => {
const { length: historyLength, goBack, replace } = history;
if (historyLength > 2) {
goBack();
} else {
replace(HomePage);
}
}, []);
return handleBack;
};
export default useBackHandler;
Now you have a single hook to import and use.
import useBackHandler from '../path/to/useBackHandler';
...
const backHandler = useBackHandler();
...
<button type="button" onClick={backHandler}>Back?</button>
If you are needing this function in older class components, then you'll need a way to inject the handleBack as a prop. For this you can create a Higher Order Component.
Example:
import useBackHandler from '../path/to/useBackHandler';
const withBackHandler = Component => props => {
const backHandler = useBackHandler();
return <Component {...props} backHandler={backHandler} />;
};
export default withBackHandler;
To use, import withBackHandler and decorate a React component and access props.backHandler.
import withBackHandler from '../path/to/withBackHandler';
class MyComponent extends React.Component {
...
someFunction = () => {
...
this.props.backHandler();
}
...
}
export default withBackHandler(MyComponent);
#meez
Don't see why this wouldn't work. Just a couple of things: (a) I would add the event argument and e.preventDefault() within the function and (b) would be careful of the function name you are passing on the onClick property of your button: handleBackClick !== handleBack, you'll get an ReferenceError because of an undefined function.
Additionally, I also noticed that this can be achieved with native browser functions. Here's a snippet:
const { length: historyLength, back } = window.history;
const { replace } = window.location;
const handleBack = (e) => {
e.preventDefault();
if (historyLength > 2) {
back();
} else {
replace('homepageUrl');
}
};

React-component is not re-rendered when the store is changed, neither automatically nor even by force update

This functional component should display a sorted list with checkboxes at each item that change the values in the store.
For some reason it is not re-rendered when the store is changed. And without a re-renderer, it (and the whole application) works very crookedly and halfway. I suspect that this is because the store object remains the same, albeit with new content. But I don’t understand how to fix it. I have even inserted a force update to the checkbox handler, but for some reason it does not work too.
Component:
import React, { useState, useReducer } from 'react';
import { ReactSortable } from 'react-sortablejs';
import ListItem from '#mui/material/ListItem';
import Checkbox from '#mui/material/Checkbox';
import { connect } from 'react-redux';
import { setGameVisible, setGameInvisible } from '../store/actions/games';
interface IGamesListProps {
games: [];
setGameVisible: (id: string) => void;
setGameInvisible: (id: string) => void;
}
interface ItemType {
id: string;
name: string;
isVisible: boolean;
}
const GamesList: React.FunctionComponent<IGamesListProps> = ({games, setGameVisible, setGameInvisible}) => {
const [state, setState] = useState<ItemType[]>(games);
// eslint-disable-next-line
const [ignored, forceUpdate] = useReducer(x => x + 1, 0); // this way of force updating is taken from the official React documentation (but even it doesn't work!)
const onCheckboxChangeHandle = (id: string, isVisible: boolean) => {
isVisible ? setGameInvisible(id) : setGameVisible(id);
forceUpdate(); // doesn't work :(((
}
return (
<ReactSortable list={state} setList={setState} tag='ul'>
{state.map((item) => (
<ListItem
sx={{ maxWidth: '300px' }}
key={item.id}
secondaryAction={
<Checkbox
edge="end"
onChange={() => onCheckboxChangeHandle(item.id, item.isVisible)}
checked={item.isVisible}
/>
}
>
{item.name}
</ListItem>
))}
</ReactSortable>
);
};
export default connect(null, { setGameVisible, setGameInvisible })(GamesList);
Reducer:
import { SET_GAMES, SET_GAME_VISIBLE, SET_GAME_INVISIBLE } from '../actions/games';
export const initialState = {
games: [],
};
export default function games(state = initialState, action) {
switch(action.type) {
case SET_GAMES: {
for(let obj of action.payload.games) {
obj.isVisible = true;
}
return {
...state,
games: action.payload.games,
};
}
case SET_GAME_VISIBLE: {
for(let obj of state.games) {
if (obj.id === action.payload.id) {
obj.isVisible = true;
};
}
return {
...state,
};
}
case SET_GAME_INVISIBLE: {
for(let obj of state.games) {
if (obj.id === action.payload.id) {
obj.isVisible = false;
};
}
return {
...state,
};
}
default:
return state;
}
}
Thank you for any help!
Note: By the information You gave I came with the idea of the problem, but I posted here because it is going to be explanatory and long.
First of all, you don't pass the new game via mapStateToProps into Component in a state change, and even you do, useState won't use new game prop value for non-first render. You must use useEffect and trigger changes of the game and set the to state locally.
At this point you must find the inner state redundant and you can remove it and totally rely on the redux state.
const mapStateToProp = (state) => ({
games: state.games // you may need to change the path
})
connect(mapStateToProp, { setGameVisible, setGameInvisible })(GamesList);
Second, the reducer you made, changes the individual game item but not the games list itself. because it is nested and the reference check by default is done as strict equality reference check-in redux state === state. This probably doesn't cause an issue because the outer state changes by the way, but I think it worth it to mention it.
for(let obj of action.payload.games) {
obj.isVisible = true; // mutating actions.payload.games[<item>]
}
return {
...state,
games: [...action.payload.games], // add immutability for re-redenr
};
// or use map
return {
...state,
games: action.payload.games.map(obj => ({...obj, isVisible:true})),
};
Third, It's true your forceUpdate will cause the component to re-render, and you can test that by adding a console.log, but it won't repaint the whole subtree of your component including inner children if their props don't change that's because of performance issue. React try to update as efficiently as possible. Also you the key optimization layer which prevent change if the order of items and id of them doesn't change

Is separating data retrieval code into interfaces and impl. of those interfaces a good idea in React?

My question is: is the below pattern a good idea in React or no? I come from Java world where this type of code is standard. However, I've ran into several things that, while being a good idea in Java, are NOT a good idea in ReactJS. So I want to make sure that this type of code structure does not have weird memory leaks or hidden side-effects in the react world.
Some notes on below code: I'm only putting everything in the same file for brevity purposes. In real life, the react component the interface and the class would all be in their own source files.
What I'm trying to do: 1) Separate the display logic from data access logic so that my display classes are not married to a specific implementation of talking to a database. 2) Separating DAO stuff into interface + class so that I can later use a different type of database by replacing the class implementaton of the same DAO and won't need to touch much of the rest of the code.
so, A) Is this a good idea in React? B) What sort of things should I watch out for with this type of design? and C) Are there better patterns in React for this that I'm not aware of?
Thanks!
import { useState, useEffect } from 'react';
interface Dao {
getThing: (id: string) => Promise<string>
}
class DaoSpecificImpl implements Dao {
tableName: string;
constructor(tableName: string) {
this.tableName = tableName;
}
getThing = async (id: string) => {
// use a specific database like firebase to
// get data from tabled called tablename
return "herp";
}
}
const dao: Dao = new DaoSpecificImpl("thingies");
const Display: React.FC = () => {
const [thing, setThing] = useState("derp");
useEffect(() => {
dao.getThing("123").then((newThing) =>
setThing(newThing));
});
return (
<div>{thing}</div>
)
}
export default Display;
https://codesandbox.io/s/competent-taussig-g948n?file=/src/App.tsx
The DaoSpecificImpl approach works however I would change your component to use a React hook:
export const useDAO = (initialId = "123") => {
const [thing, setThing] = useState("derp");
const [id, setId] = useState(initialId);
useEffect(() => {
const fetchThing = async () => {
try{
const data = await dao.getThing(id);
setThing(data);
}catch(e){
// Handle errors...
}
}
fetchThing();
}, [id]);
return {thing, setId};
}
using the hook in your component:
const Display = () => {
const {thing, setId} = useDao("123"); // If you don't specify initialId it'll be "123"
return <button onClick={() => setId("234")}>{thing}</button> // Pressing the button will update "thing"
}
Side note: You could also use a HOC:
const withDAO = (WrappedComponent, initialId = "123") => {
.... data logic...
return (props) => <WrappedComponent {...props} thing={thing} setId={setId}/>
};
export default withDAO;
E.g. using the HOC to wrap a component:
export default withDao(Display); // If you don't specify initialId it'll be "123"

JSX Element does not have any construct or call signatures

I am trying to add Application Insights in my ReactJS Application. I changed the JS code that is provided on the GitHub Demo to TypeScript.. now I have
class TelemetryProvider extends Component<any, any> {
state = {
initialized: false
};
componentDidMount() {
const { history } = this.props;
const { initialized } = this.state;
const AppInsightsInstrumentationKey = this.props.instrumentationKey;
if (!Boolean(initialized) && Boolean(AppInsightsInstrumentationKey) && Boolean(history)) {
ai.initialize(AppInsightsInstrumentationKey, history);
this.setState({ initialized: true });
}
this.props.after();
}
render() {
const { children } = this.props;
return (
<Fragment>
{children}
</Fragment>
);
}
}
export default withRouter(withAITracking(ai.reactPlugin, TelemetryProvider));
But when I try to import the same component <TelemetryProvider instrumentationKey="INSTRUMENTATION_KEY" after={() => { appInsights = getAppInsights() }}></Telemetry> I get an error Severity Code Description Project File Line Suppression State
(TS) JSX element type 'TelemetryProvider' does not have any construct or call signatures.
I attempted to simply // #ts-ignore, that did not work. How do I go about solving this?
Given the example above, I hit the same issue. I added the following:
let appInsights:any = getAppInsights();
<TelemetryProvider instrumentationKey={yourkeyher} after={() => { appInsights = getAppInsights() }}>after={() => { appInsights = getAppInsights() }}>
Which seem to solve the issue for me, I am now seeing results in Application Insights as expected.
I guess if you want to have the triggers etc on a different Page/Component you may wish to wrap it in your own useHook or just add something like this to the component.
let appInsights:any;
useEffect(() => {
appInsights = getAppInsights();
}, [getAppInsights])
function trackEvent() {
appInsights.trackEvent({ name: 'React - Home Page some event' });
}
Not the best answer, but it's moved me forward. Would be nice to see a simple hooks version in typescript.
Really hope it helps someone or if they have a cleaner answer.

Using shouldComponentUpdate for block component re-render every second

That game developed with react and redux.I am not react-redux developer(I am .net developer) but I must continue that project so I am new in react and redux
That game performance is too bad in some android phones.So I analyze project.I see that components render method works every second.My component contain more than 30 other components.So every secon it re render and this is cause bad performance in some old android phones
Why React component re render every second?Can I block this?
I search for that problem I see that solution is shouldComponentUpdate function
shouldComponentUpdate(nextProps,nextState) {
console.log(nextProps.gameStore.get('state'));//waiting
console.log(this.props.gameStore.get('state'));//waiting
console.log(this.state);
console.log(nextState);
if (nextProps.gameStore.get('state')==this.props.gameStore.get('state')) {
return false;
}
else {
return true;
}
}
but in this function nextstate and this state is same,
nextProps.gameStore.get('state') and this.props.gameStore.get('state') is the same.Why next state and current state is same?What should I do?I use constructor but it is still same here is all my component code
import React from 'react';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
//import { default as HTML5Backend } from 'react-dnd-touch-backend';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { bindFirstArguments } from 'utils/bindFirstArgument';
import * as OkeyGameActions from 'actions/OkeyGameActions';
import * as OkeyNetActions from 'actions/OkeyNetActions';
import * as OkeyChatActions from 'actions/Chatactions';
import { SeatDirection } from 'constants/AppConstants';
import { OkeyScoreboardDialog }
from 'components/OkeyScoreboardDialog/OkeyScoreboardDialog';
import OkeyMatchResultDialog
from 'components/OkeyMatchResultDialog/OkeyMatchResultDialog';
import OkeyRackWrapper from 'components/OkeyRackWrapper/OkeyRackWrapper';
import OkeyTopToolbar from 'components/OkeyTopToolbar/OkeyTopToolbar';
import OkeyTableToolbar from 'components/OkeyTableToolbar/OkeyTableToolbar';
import OkeyTableCenter from 'components/OkeyTableCenter/OkeyTableCenter';
import CustomDragLayer from 'components/CustomDragLayer/CustomDragLayer';
import MessageList from 'components/chat/MessageList';
import PrivateEastMessageList from 'components/chat/PrivateEastMessageList';
import PrivateNorthMessageList from 'components/chat/PrivateNorthMessageList';
import PrivateWestMessageList from 'components/chat/PrivateWestMessageList';
import PrivateSouthMessageList from 'components/chat/PrivateSouthMessageList';
import './_OkeyGame.scss';
function toJS(item) {
if (item === null) {
return null;
}
//var item1=item.toJS();
//if (item1.color==='BLACK') {
// var a='a';
//}
if (item == undefined) {
return;
}
return item.toJS();
}
function getRelativeDirection(selfSeat, direction) {
let relativeDirection = direction;
if (selfSeat >= 0) {
relativeDirection = (selfSeat - direction + 4) % 4;
}
return relativeDirection;
}
class OkeyGame extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate(nextProps,nextState) {
console.log(nextProps.gameStore.get('state'));//waiting
console.log(this.props.gameStore.get('state'));//waiting
console.log(this.state);
console.log(nextState);
if (nextProps.gameStore.get('state')==this.props.gameStore.get('state')) {
return false;
}
else {
return true;
}
}
render() {
const { dispatch, gameStore, gamePlay, playRules } = this.props;
let actions = bindActionCreators(OkeyGameActions, dispatch);
let netActions = bindActionCreators(OkeyNetActions, dispatch);
const currentTurn = gameStore.get('currentTurn');
const playState = {
selectedStone: gamePlay.get('selectedStone'),
gosterge: gamePlay.get('gosterge'),
middleStoneCount: gamePlay.get('middleStoneCount'),
currentTurn: currentTurn
};
if (playState.gosterge != undefined) {
window.localStorage.setItem('gostergeNumber', playState.gosterge._root.entries[0][1]);
window.localStorage.setItem('gostergeColor', playState.gosterge._root.entries[1][1]);
}
const hasOpenedStonesThisTurn = {
hasOpenedSequenceThisTurn: playRules.get('hasOpenedSequenceThisTurn'),
hasOpenedPairsThisTurn: playRules.get('hasOpenedPairsThisTurn')
};
const rules = {
canOpenSequence: playRules.get('canOpenSequence'),
canOpenPairs: playRules.get('canOpenPairs'),
canWithdraw: playRules.get('canWithdraw'),
canDiscard: playRules.get('canDiscard'),
canCollectOpen: playRules.get('canCollectOpen'),
canLeaveTaken: playRules.get('canLeaveTaken'),
canProcessStone: playRules.get('canProcessStone')
};
const discardMiniBoxes = {
discardMiniBoxPairs: gamePlay.get('pairs'),
discardMiniBoxSequence: gamePlay.get('sequence')
};
const selfSeat = gameStore.get('selfSeat');
const { westSeat, eastSeat, northSeat, southSeat } =
{
westSeat: getRelativeDirection(selfSeat, SeatDirection.WEST),
eastSeat: getRelativeDirection(selfSeat, SeatDirection.EAST),
northSeat: getRelativeDirection(selfSeat, SeatDirection.NORTH),
southSeat: getRelativeDirection(selfSeat, SeatDirection.SOUTH)
};
const players = {
selfSeat: selfSeat,
pSouth: {
seatId: southSeat,
discardStones: gamePlay.getIn(['discardStones', southSeat]),
profile: toJS(gameStore.getIn(['players', southSeat])),
dispatch: dispatch
},
pNorth: {
seatId: northSeat,
discardStones: gamePlay.getIn(['discardStones', northSeat]),
profile: toJS(gameStore.getIn(['players', northSeat])),
dispatch: dispatch
},
pEast: {
seatId: eastSeat,
discardStones: gamePlay.getIn(['discardStones', eastSeat]),
profile: toJS(gameStore.getIn(['players', eastSeat])),
dispatch: dispatch
},
pWest: {
seatId: westSeat,
discardStones: gamePlay.getIn(['discardStones', westSeat]),
profile: toJS(gameStore.getIn(['players', westSeat])),
dispatch: dispatch
}
};
let profiles = [
players.pSouth.profile,
players.pEast.profile,
players.pNorth.profile,
players.pWest.profile
];
localStorage.setItem("selfSeat", selfSeat);
localStorage.setItem("roomID", gameStore.get('id'));
if (selfSeat == 0)
profiles = [players.pSouth.profile,players.pEast.profile,players.pNorth.profile,players.pWest.profile];
else if (selfSeat == 1)
profiles = [players.pWest.profile,players.pSouth.profile,players.pEast.profile,players.pNorth.profile];
else if (selfSeat == 2)
profiles = [players.pNorth.profile,players.pWest.profile,players.pSouth.profile,players.pEast.profile];
else if (selfSeat == 3)
profiles = [players.pEast.profile,players.pNorth.profile,players.pWest.profile,players.pSouth.profile];
const matchState = {
name: gameStore.getIn(['options', 'name']),
maxRounds: gameStore.getIn(['options', 'rounds']),
stake: gameStore.getIn(['options', 'stakes']),
round: gameStore.get('round')
};
const owner = gamePlay.get('ownerID');
const scoreboard = gameStore.get('scoreboard');
const matchResult = gameStore.get('matchResult');
const restCountdown = gameStore.get('restCountdown');
const roomState = gameStore.get('roomState');
const { messageList } = this.props;
const { privateEastMessageList } = this.props;
const { privateNorthMessageList } = this.props;
const { privateWestMessageList } = this.props;
const { privateSouthMessageList } = this.props;
let chatActions = bindActionCreators(OkeyChatActions, dispatch);
// const dispatch1 = this.props
// each action has a first argument of room id
netActions = bindFirstArguments(netActions, gameStore.get('id'));
let from = gameStore.get('from');
let to = gameStore.get('to');
let gift = gameStore.get('gift');
let from1 = gameStore.get('from1');
let to1 = gameStore.get('to1');
let gift1 = gameStore.get('gift1');
let from2 = gameStore.get('from2');
let to2 = gameStore.get('to2');
let gift2 = gameStore.get('gift2');
let from3 = gameStore.get('from3');
let to3 = gameStore.get('to3');
let gift3 = gameStore.get('gift3');
let arayan = gameStore.get('arayan');
let aranan = gameStore.get('aranan');
return (
<div className="game-background" style={{background: 'url(http://okey101.xyz/staticImg/background.png)',backgroundSize:'cover'}}>
<div className="okey-game flex-centered-column">
<CustomDragLayer isMini={gamePlay.get('isOver') > 0}></CustomDragLayer>
<MessageList {...chatActions} {...netActions} messageList={messageList} />
<OkeyScoreboardDialog profiles={profiles}
scoreboard={scoreboard} />
<OkeyMatchResultDialog matchResult={matchResult}
{...netActions}
{...actions}
roomState={roomState}/>
<OkeyTopToolbar {...netActions}
{...matchState}
profiles={profiles}/>
<OkeyTableCenter {...actions}
{...netActions}
{...playState}
{...rules}
{...discardMiniBoxes}
{...players}
owner={owner}
messageList={messageList}
privateEastMessageList={privateEastMessageList}
privateNorthMessageList={privateNorthMessageList}
privateWestMessageList={privateWestMessageList}
privateSouthMessageList={privateSouthMessageList}
from={from}
to={to}
gift={gift}
from1={from1}
to1={to1}
gift1={gift1}
from2={from2}
to2={to2}
gift2={gift2}
from3={from3}
to3={to3}
gift3={gift3}
arayan={arayan}
aranan={aranan}
stones={gamePlay.get('stones')}/>
<OkeyRackWrapper {...actions}
{...netActions}
{...playState}
stones={gamePlay.get('stones')}
stoneGroups={gamePlay.get('stoneGroups')}/>
<OkeyTableToolbar {...actions}
{...netActions}
{...rules}
restCountdown={restCountdown}
currentTurn={currentTurn}
{...hasOpenedStonesThisTurn}
roomState={roomState}
stones={gamePlay.get('stones')}
{...discardMiniBoxes}
okeyStone={gamePlay.get('okeyStone')}/>
</div>
</div>
);
}
}
const mapStateToProps = (state => ({
gameStore: state.gameStore,
gamePlay: state.gamePlay,
playRules: state.playRules,
messageList: state.MessageList,
privateEastMessageList: state.PrivateEastMessageList,
privateNorthMessageList: state.PrivateNorthMessageList,
privateWestMessageList: state.PrivateWestMessageList,
privateSouthMessageList: state.PrivateSouthMessageList
}));
const OkeyGameWithDnD = DragDropContext(HTML5Backend)(OkeyGame);
export default connect(mapStateToProps)(OkeyGameWithDnD);
Edit:With Aftab Khan directives I change component to PureComponent but the page does not open and there is not error in console
I change this to
const mapStateToProps = (state => ({
gameStore: toJS(state.gameStore),
gamePlay: toJS(state.gamePlay),
playRules: toJS(state.playRules),
messageList: toJS(state.MessageList),
privateEastMessageList: toJS(state.PrivateEastMessageList),
privateNorthMessageList: toJS(state.PrivateNorthMessageList),
privateWestMessageList: toJS(state.PrivateWestMessageList),
privateSouthMessageList: toJS(state.PrivateSouthMessageList)
}));
but it still does not work
then I change it to this
const mapStateToProps = (state => ({
gameStore: state.gameStore.toJS(),
gamePlay: state.gamePlay.toJS(),
playRules: state.playRules.toJS(),
messageList: state.MessageList.toJS(),
privateEastMessageList: state.PrivateEastMessageList.toJS(),
privateNorthMessageList: state.PrivateNorthMessageList.toJS(),
privateWestMessageList: state.PrivateWestMessageList.toJS(),
privateSouthMessageList: state.PrivateSouthMessageList.toJS()
}));
but it still does not open in browser
There are few things I noticed about your component
render method does all lot of initialization for the children components
shouldComponentUpdate is unnecessary as it deals only with props (which is expected as all your state is in the redux store).
PureComponent is unnecessary to given that you are connecting it to the store using connect(). Your component has not state, its better being and functional components created using a simple es6 arrow function
For 2. and 3. refer the note on optimizations connect() does for you as it creates containers.
Now its impossible to tell you why your components are being rendered every second without inspecting every line of your components. However, with carefully consideration of the following can help you make sure re-renders are triggered frugally and only when necessary.
1. Keep render() method as light-weight as possible
render is probably the most called method in all components. More bloated it is slow will you render cycles be. You can move all you localStorage.setItems to componentWillReceiveProps. Each one of those synchronous LocalStorage calls is taking up render time.
2. Remove shouldComponentUpdate. Instead pass only those props to components that it absolutely requires. Make as many components stateless pure functions
shouldComponentUpdate is an escape hatch for components that take in more props they need to render. Downside of sCU: it runs before every component re-render. In some cases it can slow down instead of speeding things up. See this comment here by jimfb. This supports my suggestion of turning as many components into stateless function components as you can - including maybe the component you posted here
3. PureComponent will not be needed if your components are stateless javascript functions taking in only the props they need.
Seems like you need React.PureComponent.
Here is the link to it. Link
Basically what it does is before rendering each component, it will check if the component has any changes in the props or the state. If there is no change it won't reRender.
Looks like you are using immutable data structures in your reducer. In ur mapStateToProps, change all the immutable data to normal JS objects calling toJS() method. Then PureComponent will work as intended.

Resources