why useEffect run twice in react, how to prevent this issue? - reactjs

right now i have 2 components, my parent component is Game and from that it calls the child component, GameInner, from GameInner component i have define the useEffect, but it calls 2 times, i don't know why. this is my useEffect function, also i have define both component code here,
React.useEffect(() => {
if(!isCalled.current) {
isCalled.current = true;
if(started) {
console.log('i fire once')
if(isCalled.current) {
getUpdate();
}
}
}
}, []);
Game.tsx
import {Redirect, RouteComponentProps, withRouter} from "react-router";
import React, { useEffect, useState } from "react";
import {GameDataStore, GameDataStorePayload} from "../../Global/DataStore/GameDataStore";
import {UserData, UserDataStore} from "../../Global/DataStore/UserDataStore";
import Helmet from "react-helmet";
import {Dialog, DialogContent, Typography} from "#material-ui/core";
import {ContainerProgress} from "../../UI/ContainerProgress";
import {LoadingButton} from "../../UI/LoadingButton";
import {Support} from "./Components/Gameplay/Support";
import {GameChatFab} from "./Components/Chat/GameChatFab";
import {ChatSidebar} from "./Components/Chat/ChatSidebar";
import {GameInner} from "./Components/Gameplay/GameInner";
import {SocketDataStore, SocketDataStorePayload} from "../../Global/DataStore/SocketDataStore";
import moment from "moment";
import {getTrueRoundsToWin} from "../../Global/Utils/GameUtils";
import {ClientGameItem} from "../../Global/Platform/Contract";
import {PlayerJoinApproval} from "#Areas/Game/Components/Gameplay/PlayerJoinApproval";
interface IGameParams
{
id: string;
}
interface IGameState
{
socketData: SocketDataStorePayload;
gameData: GameDataStorePayload;
userData: UserData;
restartLoading: boolean;
restartDelayed: boolean;
showSupport: boolean;
chatDrawerOpen: boolean;
}
class Game extends React.Component<RouteComponentProps<IGameParams>, IGameState>
{
private supportDelayTimeout = 0;
constructor(props: RouteComponentProps<IGameParams>)
{
super(props);
this.state = {
socketData: SocketDataStore.state,
gameData: GameDataStore.state,
userData: UserDataStore.state,
restartLoading: false,
restartDelayed: true,
showSupport: false,
chatDrawerOpen: true
};
}
public componentDidMount(): void
{
GameDataStore.hydrate(this.props.match.params.id);
SocketDataStore.listen(data => this.setState({
socketData: data
}));
GameDataStore.listen(data => this.setState({
gameData: data
}));
UserDataStore.listen(data => this.setState({
userData: data
}));
}
private getWinnerFromState(state: IGameState)
{
const {
players,
settings
} = state.gameData.game ?? {};
const playerGuids = Object.keys(players ?? {});
const roundsToWin = getTrueRoundsToWin(state.gameData.game as ClientGameItem);
const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);
return winnerGuid;
}
public componentDidUpdate(prevProps: Readonly<RouteComponentProps<IGameParams>>, prevState: Readonly<IGameState>, snapshot?: any): void
{
const hadWinner = this.getWinnerFromState(prevState);
const hasWinner = this.getWinnerFromState(this.state);
if (!hadWinner && hasWinner && this.supportDelayTimeout === 0)
{
this.supportDelayTimeout = window.setTimeout(() =>
{
this.setState({
restartDelayed: true,
showSupport: true
});
setTimeout(() => this.setState({
restartDelayed: false
}), 5000);
}, 2000);
}
}
private restartClick = (playerGuid: string) =>
{
this.setState({
restartLoading: true
});
GameDataStore.restart(playerGuid)
.finally(() => this.setState({
restartLoading: false
}));
};
public render()
{
const {
id,
} = this.props.match.params;
if (!id)
{
return <Redirect to={"/"}/>;
}
const {
dateCreated,
ownerGuid,
spectators,
pendingPlayers,
players,
settings,
} = this.state.gameData.game ?? {};
if (!this.state.gameData.game || !this.state.gameData.loaded || !this.state.socketData.hasConnection)
{
return <ContainerProgress/>;
}
const {
playerGuid
} = this.state.userData;
const owner = players?.[ownerGuid ?? ""];
const amInGame = playerGuid in (players ?? {});
const amSpectating = playerGuid in {...(spectators ?? {}), ...(pendingPlayers ?? {})};
const title = `${unescape(owner?.nickname ?? "")}'s game`;
const playerGuids = Object.keys(players ?? {});
const roundsToWin = getTrueRoundsToWin(this.state.gameData.game as ClientGameItem);
const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);
const canChat = (amInGame || amSpectating) && moment(dateCreated).isAfter(moment(new Date(1589260798170)));
return (
<>
<Helmet>
<title>{title}</title>
</Helmet>
<PlayerJoinApproval/>
<GameInner gameId={id} />
{winnerGuid && (
<Dialog open={this.state.showSupport} onClose={() => this.setState({showSupport: false})}>
<DialogContent style={{padding: "2rem"}}>
<Typography variant={"h6"} style={{textAlign: "center"}}>
Game over! {unescape(players?.[winnerGuid].nickname ?? "")} is the winner.
</Typography>
<Support/>
{playerGuid === ownerGuid && (
<div style={{
marginTop: "7rem",
textAlign: "center"
}}>
<LoadingButton loading={this.state.restartLoading || this.state.restartDelayed} variant={"contained"} color={"secondary"} onClick={() => this.restartClick(playerGuid)}>
Restart this game?
</LoadingButton>
</div>
)}
</DialogContent>
</Dialog>
)}
{canChat && (
<>
<GameChatFab showChat={amInGame || amSpectating}/>
<ChatSidebar />
</>
)}
</>
);
}
};
export default withRouter(Game);
GameInner.tsx
import { Alert } from "#material-ui/lab";
import { Typography, useMediaQuery } from "#material-ui/core";
import { ShowWinner } from "./ShowWinner";
import { ErrorBoundary } from "../../../../App/ErrorBoundary";
import { GamePlayWhite } from "../../GamePlayWhite";
import { GamePlayBlack } from "../../GamePlayBlack";
import { GamePlaySpectate } from "../../GamePlaySpectate";
import React, { useEffect, useState } from "react";
import { useDataStore } from "../../../../Global/Utils/HookUtils";
import { GameDataStore } from "../../../../Global/DataStore/GameDataStore";
import { UserDataStore } from "../../../../Global/DataStore/UserDataStore";
import { IntervalDataStore } from "../../../../Global/DataStore/IntervalDataStore";
import GameStart from "../../GameStart";
import GameJoin from "../../GameJoin";
import moment from "moment";
import { ChatDataStore } from "../../../../Global/DataStore/ChatDataStore";
import { useHistory, useParams } from "react-router";
import { SiteRoutes } from "../../../../Global/Routes/Routes";
import { getTrueRoundsToWin } from "../../../../Global/Utils/GameUtils";
import { ClientGameItem } from "../../../../Global/Platform/Contract";
import { CurriedFunction1 } from "lodash";
interface Props {
gameId: string;
}
export const GameInner: React.FC<Props> = (
{
gameId,
}
) => {
const gameData = useDataStore(GameDataStore);
const userData = useDataStore(UserDataStore);
const chatData = useDataStore(ChatDataStore);
const params = useParams<{ throwaway?: string }>();
const history = useHistory();
const [updateShowTimer, setUpdateShowTimer] = React.useState('02:00');
//const [isCalled, setIsCalled] = React.useState<any>('0');
const [intervalData, setIntervalData] = useState(null as NodeJS.Timeout | null);
let setSeconds = 30;
const isGameStarted = React.useRef(false);
const isCalled = React.useRef(false);
/******************* interval timer ****************/
/***************************************************/
const {
dateCreated,
started,
chooserGuid,
ownerGuid,
spectators,
pendingPlayers,
players,
settings,
kickedPlayers
} = gameData.game ?? {};
const {
playerGuid
} = userData;
const iWasKicked = !!kickedPlayers?.[playerGuid];
const amInGame = playerGuid in (players ?? {});
useEffect(() => {
const playMode = params.throwaway !== "play" && started && !iWasKicked && amInGame;
const notPlayMode = iWasKicked && params.throwaway === "play";
if (playMode) {
history.push(SiteRoutes.Game.resolve({
id: gameId,
throwaway: "play"
}))
}
if (notPlayMode) {
history.push(SiteRoutes.Game.resolve({
id: gameId,
throwaway: "kicked"
}));
}
}, [started, iWasKicked, amInGame]);
React.useEffect(() => {
if(!isCalled.current) {
isCalled.current = true;
if(started) {
console.log('i fire once')
if(isCalled.current) {
getUpdate();
}
}
}
}, []);
React.useEffect(() => {
if(gameData?.game?.roundStarted) {
if(!isGameStarted.current) {
console.log("round is started");
isGameStarted.current = true;
}
}
}, [gameData]);
const skipPlayer = (game_string_id: any, target_turn: any, chooserGuid: any) => {
return GameDataStore.skipPlayer(game_string_id, target_turn, chooserGuid);
}
const interval = () => {
let timer = setSeconds, minutes, seconds;
let chooserGuid = localStorage.getItem('chooserGuid');
let game_string_id = localStorage.getItem('game_id');
let target_turn = localStorage.getItem('target_turn');
let is_called = localStorage.getItem('is_called');
if(!isGameStarted.current) {
console.log("isGameStarted : "+isGameStarted.current);
if (typeof timer !== undefined && timer != null) {
minutes = parseInt(timer / 60 as any, 10);
seconds = parseInt(timer % 60 as any, 10);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
//console.log("test");
console.log(minutes + ":" + seconds);
setUpdateShowTimer(minutes+":"+seconds);
if (timer == 0) {
skipPlayer(game_string_id, target_turn, chooserGuid);
if(intervalData != undefined && intervalData!== null)
clearInterval(intervalData);
}
if (--timer < 0) {
if(intervalData != undefined && intervalData!== null)
clearInterval(intervalData);
}
setSeconds -= 1;
}
}
}
const startTimer = () => {
console.log("called again");
//interval_counter = setInterval(interval,1000);
setIntervalData(setInterval(interval,1000));
}
const getUpdate = () => {
if(gameData?.game?.players && gameData?.game?.id) {
let game_id = gameData.game.id;
let all_players = gameData.game.players;
let all_player_id = Object.keys(all_players);
let filteredAry = all_player_id.filter(e => e !== userData.playerGuid);
console.log("user player guid:"+userData.playerGuid);
console.log("guid:"+chooserGuid);
console.log("all players:"+all_player_id);
console.log("new array:"+filteredAry);
let target_item = filteredAry.find((_, i, ar) => Math.random() < 1 / (ar.length - i));
if(typeof target_item !== undefined && target_item!=null) {
localStorage.setItem('target_turn',target_item);
}
localStorage.setItem('is_started','0');
if(typeof game_id !== undefined && game_id!=null) {
localStorage.setItem('game_id',game_id);
}
if(typeof chooserGuid !== undefined && chooserGuid!=null) {
localStorage.setItem('chooserGuid',chooserGuid);
}
if(isChooser) {
startTimer();
} else {
//clearInterval(intervalData);
}
}
}
const isOwner = ownerGuid === userData.playerGuid;
const isChooser = playerGuid === chooserGuid;
const amSpectating = playerGuid in { ...(spectators ?? {}), ...(pendingPlayers ?? {}) };
const playerGuids = Object.keys(players ?? {});
const roundsToWin = getTrueRoundsToWin(gameData.game as ClientGameItem);
const winnerGuid = playerGuids.find(pg => (players?.[pg].wins ?? 0) >= roundsToWin);
const inviteLink = (settings?.inviteLink?.length ?? 0) > 25
? `${settings?.inviteLink?.substr(0, 25)}...`
: settings?.inviteLink;
const meKicked = kickedPlayers?.[playerGuid];
const tablet = useMediaQuery('(max-width:1200px)');
const canChat = (amInGame || amSpectating) && moment(dateCreated).isAfter(moment(new Date(1589260798170)));
const chatBarExpanded = chatData.sidebarOpen && !tablet && canChat;
/**********************************************/
/********************************************/
return (
<div style={{ maxWidth: chatBarExpanded ? "calc(100% - 320px)" : "100%" }}>
<div style={{ minHeight: "70vh" }}>
{iWasKicked && (
<Alert variant={"filled"} severity={"error"}>
<Typography>
{meKicked?.kickedForTimeout ? "You were kicked for being idle. You may rejoin this game any time!" : "You left or were kicked from this game"}
</Typography>
</Alert>
)}
{!winnerGuid && settings?.inviteLink && (
<Typography variant={"caption"}>
Chat/Video Invite: <a href={settings.inviteLink} target={"_blank"} rel={"nofollow noreferrer"}>{inviteLink}</a>
</Typography>
)}
{winnerGuid && (
<ShowWinner />
)}
{!winnerGuid && (
<ErrorBoundary>
{updateShowTimer}
{(!started || !(amInGame || amSpectating)) && (
<BeforeGame gameId={gameId} isOwner={isOwner} />
)}
{started && amInGame && !isChooser && (
[
<GamePlayWhite />
]
)}
{started && amInGame && isChooser && (
[
<GamePlayBlack />
]
)}
{started && amSpectating && (
<GamePlaySpectate />
)}
</ErrorBoundary>
)}
</div>
</div>
);
};
interface BeforeGameProps {
isOwner: boolean;
gameId: string;
}
const BeforeGame: React.FC<BeforeGameProps> = (props) => {
return (
<>
{props.isOwner && (
<GameStart id={props.gameId} />
)}
{!props.isOwner && (
<GameJoin id={props.gameId} />
)}
</>
);
};

Does the double render also occur in Production (i.e. build) mode and are you using Strict Mode?
Strict Mode will render your components twice in dev, which means your useEffect will be called twice.

It must be the gameId that initially has no value (First call to useEffect) and gets populated by Match component of router library (Second call to useEffect)
You can test this by giving a hardcoded value to gameId where it should only call useEffect once:
<GameInner gameId={"5"} /> // hardcoded id variable
Regarding prevention; I think its pretty normal with react, as long as you have proper control inside useEffect implementation.

If someone else finds this post:
I think it's the useMediaQuery that initially returns false even though the resolution is narrow enough for tablet. And then just moments later the correct value is returned and another component is rendered.

Related

MobX doesn't rerender my FC for some reason, I can't explain why

So there is a storage containing 2 lists, one of the lists is fetching from server (using axios).
Both the lists renders by FC - PlayerList.
RondomizerPage contains these 2 lists.
So when I F5 my page on "localhost:3000/randomizer" nothing appears to be in my fetched list(fetched confirmed). But when I click anything(link to "randomizer" for example or any button) rerender goes in, and my list appears.
The 'store':
import { makeAutoObservable } from "mobx";
import { IPlayerInfo } from "../types/player";
import axios from "../axios";
import IMove from "./../types/movement";
class Player {
playerList: IPlayerInfo[] = [];
randomPlayerList: IPlayerInfo[] = [];
isAuth: boolean = false;
constructor() {
makeAutoObservable(this);
}
addPlayer(playerdata: IPlayerInfo) {
this.playerList.push(playerdata);
}
move(from: IMove, to: IMove) {
if (from.list === to.list) {
const list = from.list;
if (list === "randomList") {
const [temp] = this.randomPlayerList.splice(from.index, 1);
this.randomPlayerList.splice(to.index, 0, temp);
}
if (list === "playerList") {
const [temp] = this.playerList.splice(from.index, 1);
this.playerList.splice(to.index, 0, temp);
}
} else {
if (from.list === "playerList") {
const [temp] = this.playerList.splice(from.index, 1);
this.randomPlayerList.splice(to.index, 0, temp);
}
if (from.list === "randomList") {
const [temp] = this.randomPlayerList.splice(from.index, 1);
this.playerList.splice(to.index, 0, temp);
}
}
}
fetchPlayers = async () => {
try {
const response = await axios.get("/players");
this.playerList = response.data;
return
} catch (error) {
console.log(error);
}
};
randomPlayers = async () => {
if (
this.randomPlayerList.length % 2 === 0 &&
this.randomPlayerList.length > 1
) {
const response = await axios.post("/random", {
amount: this.randomPlayerList.length,
});
const data = response.data.result.random.data[0];
let playerList = [];
for (let index = 0; index < data.length; index++) {
playerList.push(this.randomPlayerList[data[index] - 1]);
}
return playerList;
}
return false;
};
}
const playerStore = new Player()
export default playerStore;
And I have a 'Randomizer page':
import React, { useEffect, useState } from "react";
import PlayerList from "../../components/Lists/PlayerList";
import players from "../../store/players";
import styles from "./RandomizerPage.module.scss";
import { useNavigate } from "react-router";
import axios from "../../axios";
import { DragDropContext, Droppable, DropResult } from "react-beautiful-dnd";
import IMove from "./../../types/movement";
import PlayerCreation from "../../components/Modals/PlayerCreationModal/PlayerCreation";
import MyButton from "../../components/UI/MyButton/MyButton";
import RandomedPlayersModal from "../../components/Modals/RandomedPlayersModal/RandomedPlayersModal";
import { observer } from "mobx-react-lite";
const RandomizerPage = observer(() => {
const [playerCreationModalisOn, setPlayerCreationModalisOn] =
useState(false);
const [randomedPlayerModalIsOn, setRandomedPlayerModalIsOn] =
useState(false);
const nav = useNavigate();
const onDragEndHandle = (result: DropResult) => {
if (!result.destination) return;
const from: IMove = {
index: result.source.index,
list: result.source.droppableId,
};
const to: IMove = {
index: result.destination.index,
list: result.destination.droppableId,
};
players.move(from, to);
};
const playerCreationHandler = () => {
setPlayerCreationModalisOn(true);
};
const playerCreationAbortion = () => {
setPlayerCreationModalisOn(false);
};
const randomPlayersHandler = () => {
const response = players.randomPlayers();
console.log(response);
setRandomedPlayerModalIsOn(true);
};
const randomPlayerAbortion = () => {
setRandomedPlayerModalIsOn(false);
};
useEffect(() => {
const user = JSON.parse(localStorage.getItem("user") || "null");
if (user) {
axios.defaults.headers["authorization"] = user.token;
players.fetchPlayers();
} else nav("/login");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
{playerCreationModalisOn && (
<PlayerCreation
abort={playerCreationAbortion}
title={"Создание персонажа"}
/>
)}
{randomedPlayerModalIsOn && (
<RandomedPlayersModal
abort={randomPlayerAbortion}
title={"random.org выбрал команды"}
/>
)}
<DragDropContext onDragEnd={onDragEndHandle}>
<div className={styles.wrapper}>
<div className={styles.column}>
<div>
<h4>Переместите сюда игроков</h4>
<Droppable droppableId="randomList">
{(provided) => (
<div
className={styles.list}
{...provided.droppableProps}
ref={provided.innerRef}
>
<PlayerList list={players.randomPlayerList} />
{provided.placeholder}
</div>
)}
</Droppable>
</div>
<MyButton click={randomPlayersHandler}>
Сгенерировать случайные команды
</MyButton>
</div>
<div className={styles.column}>
<div>
<h4>Игроки</h4>
<Droppable droppableId="playerList">
{(provided) => (
<div
className={styles.list}
{...provided.droppableProps}
ref={provided.innerRef}
>
<PlayerList list={players.playerList} />
{provided.placeholder}
</div>
)}
</Droppable>
</div>
<MyButton click={playerCreationHandler}>
Добавить игрока
</MyButton>
</div>
</div>
</DragDropContext>
</>
);
});
export default RandomizerPage;
And my PlayerList looks like that, btw:
import React from "react";
import { observer } from "mobx-react-lite";
import { Draggable } from "react-beautiful-dnd";
import PlayerCard from "../PlayerCard/PlayerCard";
import { IPlayerInfo } from "../../types/player";
interface PlayerListProps {
list: IPlayerInfo[];
}
const PlayerList = observer(({ list }: PlayerListProps) => {
console.log('rerender', list);
return (
<>
{list.map((player: IPlayerInfo, index: number) => (
<Draggable
key={player.steamId}
draggableId={player.steamId}
index={index}
>
{(provided) => (
<PlayerCard
playerinfo={player}
{...provided.draggableProps}
{...provided.dragHandleProps}
innerRef={provided.innerRef}
/>
)}
</Draggable>
))}
</>
);
});
export default PlayerList;
So, I found the crutch, if I add in RandomizerPage and change list={playerList}:
const playerList = players.playerList

TypeError: (0 , _reactRouterDom.withRouter) is not a function----- Getting this error after updating the application to the latest React version

// App.js - WEB
/* eslint-disable no-undef */
import { hot } from 'react-hot-loader/root';
import React, { Component } from "react";
import { View, Platform, Dimensions, Linking, Text } from "react-native";
import { Routes } from "react-router-dom";
import { ModalContainer } from "react-router-modal";
import IdleTimer from "react-idle-timer";
import {
BroadcastChannel
} from 'broadcast-channel';
import { withRouter } from './utils/withRouterComponent';
const routeMap = {
...CommonRouteMap,
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: "#F36414",
openmodel: false,
bootboxtext: "",
modalopenhide: false,
measure: getMeasure(this.getWidth()),
workingtime: true,
userIdle: false,
maintainanceText: APP.MAINTAINANCE_TIME,
linearColors: linearColors(),
isDuplicate: false
};
this.handleWindowPerformanceEvents();
// Store the previous pathname and search strings
this.currentPathname = null;
this.currentSearch = null;
this.workingTime = dayjs(new Date()).format("HH");
}
changeBackground = (color) => {
this.setState({ backgroundColor: color });
};
handleLayout = ({ nativeEvent }) => {
const { width } = nativeEvent.layout;
const measure = getMeasure(width);
this.setState(() => ({ measure }));
};
getWidth = () => {
const { width } = Dimensions.get("window");
return width;
};
handleWindowPerformanceEvents = async () => {
let ExternalFlag;
if (Platform.OS === "web") {
ExternalFlag = sessionStorage.getItem("ExternalFlag");
} else {
ExternalFlag = await AsyncStorage.getItem("ExternalFlag");
}
let parts = window.location.pathname.split("/");
let path = parts[parts.length - 1];
if (window.performance) {
if (
performance.navigation.type === 0 &&
!window.location.pathname != "404Page"
) {
localStorage.clear();
} else if (!window.location.pathname != "404Page") {
localStorage.clear();
const pathName = window.location.pathname;
const domainName = window.location.origin;
let updatedPath = pathName.substr(0, pathName.lastIndexOf("/"));
if (paths.includes(updatedPath)) {
if (ExternalFlag == 'false') {
window.location.href = `${domainName}${updatedPath}`;
}
return;
} else {
if (ExternalFlag == 'false') {
window.location.href = `${window.location.origin}/servicing/index.html`;
}
}
}
}
};
async componentDidMount() {
const bc = new BroadcastChannel("MY-Project");
bc.onmessage = (event) => {
if (event === "newTab") {
bc.postMessage(`duplicateTab`);
this.setState({ isDuplicate: false })
}
if (event === `duplicateTab`) {
bc.close()
}
};
bc.postMessage('newTab');
this.myContext = {
...this.myContext,
isSupported: await this.isSupportedForVKYC(),
};
const windowValues = window.location;
this.navigateOptions();
if (
windowValues.pathname.includes("product") &&
windowValues.search != ""
) {
fetchUrl(windowValues, this.props);
}
this.setState({
workingtime: true,
});
this.interval = setInterval(() => this.timer(windowValues), 5000);
let machine = null;
if (Platform.OS === 'web') {
machine = sessionStorage.getItem('machine');
}
if (machine) {
this.setState({ machine: true })
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
timer = (windowValues) => {
if (
windowValues.hostname != "localhost" &&
this.state.workingtime &&
!(this.workingTime >= 6 && this.workingTime < 23)
) {
this.setState({
workingtime: true,
});
this.interval = setInterval(() => this.timer(windowValues), 5000);
}
};
navigateOptions() {
const { history } = this.props;
if (
window.location.pathname ===
`${"/" + injectUrlPaths(window.location).path + "/"}`
) {
history.push(`${"/" + injectUrlPaths(window.location).path + ""}`);
}
}
refresh() {
let clear = [
"time",
"product_name",
"payment_gateway",
"stopwatch",
"credit_opted",
"flow",
"easyPay",
"token",
];
clear.forEach((item) => {
sessionStorage.removeItem(item);
});
}
handleKeyPress = (event) => {
if (this.state.userIdle && event.keyCode === 9) {
event.preventDefault();
}
};
closebootbox = () => {
if (this.state.userIdle) {
this.refresh();
window.removeEventListener("keydown", this.handleKeyPress);
Linking.openURL(window.location.origin);
} else {
this.setState({
openmodel: false,
modalopenhide: false,
});
}
};
onIdle = async () => {
const pathName = window.location.pathname;
window.addEventListener('keydown', this.handleKeyPress);
let ExternalFlag = (Platform.OS === "web") ? sessionStorage.getItem("ExternalFlag") : await AsyncStorage.getItem("ExternalFlag");
const { history } = this.props;
let newProps = this.props.location.state;
newProps = { ...newProps, idle: true };
let redirectPath = "/servicing/index.html";
let parts = window.location.pathname.split("/");
let path = parts[parts.length - 1];
if (ExternalFlag) {
history.push({
pathname: '/servicing/redirectHome',
state: newProps
});
} else {
let machine = null;
if (Platform.OS === 'web') {
machine = sessionStorage.getItem('machine');
}
if (machine) {
window.location.href = `http://localhost:9309/SendStatus?CurrentStatus=Timeout`;
} else {
history.push({
pathname: redirectPath,
state: newProps
});
}
}
};
setTimeout = data => {
if (data.otptimer) {
this.idleTimer.pause();
} else {
this.idleTimer.resume();
}
};
clearStorage = (data) => {
data.map((item) => {
sessionStorage.removeItem(item);
});
};
render() {
const ua = window.navigator.userAgent;
return (
!this.state.isDuplicate ?
<CacheBuster enableCaching>
{({ loading, isLatestVersion, refreshCacheAndReload }) => {
if (loading) return null;
if (!loading && !isLatestVersion) {
refreshCacheAndReload();
}
return (
browserSupport(ua) && (
<LinearGradient {...this.state.linearColors}>
<IdleTimer
ref={(ref) => {
this.idleTimer = ref;
}}
element={document}
onIdle={this.onIdle}
debounce={APP.DEBOUNCE}
timeout={APP.IDLE_TIMEOUT}
/>
<View onLayout={this.handleLayout} style={styles.container}>
<script>
window.onpopstate = function() {this.props.history.go(1)};
</script>
{this.state.workingtime ? (
<AppProvider value={{ ...this.myContext, ...this.state }}>
<View style={styles.rootStyle}>
<CustomBootbox
{...this.props}
measure={this.state.measure}
bootboxtext={this.state.bootboxtext}
bootboxmodal={this.state.openmodel}
closebootbox={this.closebootbox}
hideBlackout
/>
<Routes>
{WebRoutesGenerator(
{ routeMap },
this.changeBackground
)}
</Routes>
<ModalContainer />
</View>
</AppProvider>
) : (
<AvailableTimeError
maintainanceText={this.state.maintainanceText}
/>
)}
</View>
</LinearGradient>
)
);
}}
</CacheBuster> : <View style={{ height: Dimensions.get("window").height, }}>
<View >
<Text>Previous session is already open...!</Text>
<Text><br></br> Please close the window and try again </Text>
</View>
</View>
);
}
}
export default hot((withRouter((App))))
I have a react native project and it was running in the older version of React, React Dom and rest. After updating the application to match the latest version, I am getting the above error and I couldn't find a proper solution for it.
Initially I searched withRouter imports and uses, and then created a custom hook of withRouter. Still the error persists.
import {
useLocation,
useNavigate,
useParams
} from "react-router-dom";
export function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
This issue is likely surfacing as you might be using --legacy-peer-deps flag when installing node_modules. Now Remove node_modules, and just try to fix code after doing npm install. have a nice day.

Still re-rendering using React.memo when the prop remain unchanged

I am creating a dropzone uploader for video-uploading (using react-dropzone-uploader package),
and also get its thumbnail with package ThumbnailExtractor.
I used React.Memo(base64) to ensure if the thumbnail's base64 remain unchanged, React will not re-render.
However, when I click submit button, the thumbnail keep re-rendering even the base64 remain unchanged.
Any clue?
import React, { useEffect, useState } from "react";
import axios from "axios";
import produce from "immer";
import Dropzone from "react-dropzone-uploader";
import ThumbnailContainer from "./ThumbnailContainer";
require("./dropzone.scss");
const DropZoneUploader = ({ api, setFiles }) => {
const [clonedFiles, setClonedFiles] = useState([]);
const UploadFileWithProgress = async (f, formData) => {
try {
const options = {
onUploadProgress: (progressEvent) => {
const { loaded, total } = progressEvent;
let percent = Math.floor((loaded * 100) / total);
console.log(`${loaded}kb of ${total}kb | ${percent}%`);
if (percent < 100) {
setClonedFiles(
produce((draft) => {
const newItem = draft.find(
(item) => item.meta.id === f.meta.id
);
newItem.percent = percent;
})
);
} else {
setClonedFiles(
produce((draft) => {
const newItem = draft.find(
(item) => item.meta.id === f.meta.id
);
newItem.percent = 99;
})
);
}
}
};
await axios
.post("https://reqres.in/api/users", formData, options)
.then(async (res) => {
console.log(res);
f.meta.percent = 100;
setClonedFiles(
produce((draft) => {
const newItem = draft.find((item) => item.meta.id === f.meta.id);
newItem.true_status = "uploaded";
})
);
setFiles(
produce((draft) => {
draft.unshift(res.data.data);
})
);
f.remove();
});
} catch (err) {
console.log(err);
return null;
}
};
// called every time a file's `status` changes
const handleChangeStatus = ({ meta, file }, status) => {
if (status === "done") {
setClonedFiles(
produce((draft) => {
draft.push({
file,
meta,
true_status: "ready"
});
})
);
}
if (status === "removed") {
setClonedFiles(
produce((draft) => {
const newItemIndex = draft.findIndex(
(item) => item.meta.id === meta.id && item.true_status === "ready"
);
if (newItemIndex !== -1) {
draft.splice(newItemIndex, 1);
}
})
);
}
};
// receives array of files that are done uploading when submit button is clicked
const handleSubmit = (files, allFiles) => {
files.map(async (f) => {
const formData = new FormData();
formData.append("title", `${f.meta.name}`);
formData.append("type", f.meta.type);
formData.append("file_purpose", "private");
formData.append("file", f.file);
const reee = await UploadFileWithProgress(f, formData);
if (reee) {
console.log("pushed");
}
});
};
const PreviewOutput = ({ files }) => {
const iconByFn = {
cancel: {
backgroundImage: `url(https://upload.wikimedia.org/wikipedia/commons/d/dc/Cancel_icon.svg)`
},
remove: {
backgroundImage: `url(https://upload.wikimedia.org/wikipedia/commons/d/dc/Cancel_icon.svg)`
},
restart: {
backgroundImage: `url(https://upload.wikimedia.org/wikipedia/commons/d/dc/Cancel_icon.svg)`
}
};
return (
<div className="dropzone__imgs-wrapper">
{clonedFiles?.map((f) => (
<div key={f.meta.id}>
<ThumbnailContainer f={f} files={files} />
</div>
))}
</div>
);
};
const MyCustomPreview = ({
input,
previews,
submitButton,
dropzoneProps,
files,
}) => (
<div className="dropzone">
<div {...dropzoneProps}>
<PreviewOutput files={files} />
{input}
</div>
{submitButton}
</div>
);
return (
<Dropzone
onChangeStatus={handleChangeStatus}
onSubmit={handleSubmit}
autoUpload={true}
accept="video/*"
LayoutComponent={(props) => <MyCustomPreview {...props} />}
/>
);
};
export default DropZoneUploader;
ThumbnailContainer.jsx
import React, { useEffect, useState } from "react";
import ThumbnailExtractor from "react-thumbnail-extractor";
import ThumbnailImageSrc from "./ThumbnailImageSrc";
const ThumbnailContainer = ({ f, files }) => {
const [vFile, setVFile] = useState();
const [vImage, setVImage] = useState();
const [isCaptured, setIsCaptured] = useState(false);
const fileFunction = files.find((a) => a.meta.id === f.meta.id);
const { name, previewUrl } = f.meta;
const { true_status, percent } = f;
useEffect(() => {
setVFile(f.file);
console.log("fFile render");
}, [f.file]);
return (
<div key={f.meta.id} className="dropzone__img">
{!previewUrl && (
<>
<ThumbnailExtractor
maxWidth={600}
videoFile={vFile}
count={1}
onCapture={(image) => {
if (!isCaptured) {
console.log("capture Render");
setVImage(image[0]);
setIsCaptured(true);
}
}}
/>
<span className="dzu-previewFileName">{name}</span>
</>
)}
<div className="dzu-previewStatusContainer">
{fileFunction &&
fileFunction.meta.percent !== 100 &&
f.true_status !== "uploaded" ? (
<progress max={100} value={percent} />
) : (
""
)}
{true_status === "ready" && (
<button
className="dropzone__img-delete"
type="button"
onClick={fileFunction?.remove}
>
<i className="las la-times"></i>
</button>
)}
{vImage && <ThumbnailImageSrc src={vImage} />}
</div>
</div>
);
};
export default ThumbnailContainer;
ThumbnailImageSrc.jsx (React.memo)
import React from "react";
export const ThumbnailImageSrc = React.memo(function ThumbnailImageSrc({
src
}) {
return (
<div
key={src}
className="dropzone_thumbnail-preview"
style={{ backgroundImage: `url(${src}` }}
></div>
);
});
export default ThumbnailImageSrc;
Here is the codesandbox sample.

React Redux state cannot rerender component and state is UNDEFINED

There is some issues with state in Redux. I want to change color of a component with state update. But the state in TargetList.js has error: TypeError: Cannot read property 'includes' of undefined.
The Reducer file is:
import {
SENSOR_HI,
SENSOR_UNHI,
} from "redux/actionTypes";
const initialState = {
title: "",
text: "",
detailedList: "hide",
};
const Reducer = (state = initialState, action) => {
switch (action.type) {
case SENSOR_HI:
return {
...state,
sensorarray: action.data,
};
case SENSOR_UNHI:
return {
...state,
sensorarray: [],
};
case FILTER_TAGS_CHANGED:
return {
...state,
filterTags: action.data,
};
default:
return state;
}
};
export default Reducer;
And the TargetList.js is:
import "assets/scss/global.scss";
import moment from "jalali-moment";
import React, { useState, useEffect, useRef, memo } from "react";
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { GiMovementSensor, GiRadarSweep } from "react-icons/gi";
import { MdDescription, MdSignalWifiOff,MdNetworkWifi } from "react-icons/md";
import { connect } from "react-redux";
import ToggleButton from "react-switch";
import socketConn from "sockets";
import "./Leaflet.scss";
import classes from "./TargetsList.module.scss";
import { targetTypes, targetsIconsHandlerImage } from "helpers/icons";
import { BackButton } from "assets/js/Icons";
import ReactDOMServer from "react-dom/server";
let TEMP_TARGETS = [];
let counter = 0;
let now = new Date();
const TargetsList = memo((props) => {
const [sensorFiltersSelected, setSensorFiltersSelected] = useState([]);
const [sensorFiltersSelectedIds, setSensorFiltersSelectedIds] = useState([]);
const [sensorSelected, setSensorSelected] = useState(null);
const [targetFiltersSelected, setTargetFiltersSelected] = useState([]);
const [hi, sethi] = useState(false);
let filterType = (sensors) => {
if (sensorFiltersSelected.length == 0) {
return sensors;
} else {
let ids = sensorFiltersSelected.map((s) => s["_id"]);
return sensors.filter((s) => ids.includes(s.sensorType["_id"]));
}
};
let filterTextSensors = (sensors) => {
return sensors.filter((s) => s.latinName.includes(searchInput));
};
let toggleFilter = (sensor) => {
let filters = sensorFiltersSelected;
let ids = filters.map((f) => f["_id"]);
if (filters.length && ids.includes(sensor["_id"])) {
filters = filters.filter((f) => f["_id"] != sensor["_id"]);
} else {
filters.push(sensor);
}
props.dispatch({
type: "FILTER_TAGS_CHANGED",
data: [...filters],
});
setSensorFiltersSelected(filters);
setSensorFiltersSelectedIds(filters.map(s=>s.latinName))
props.dispatch({
type: "SENSOR_HI",
data: [filters.map(s=>s.latinName)],
});
};
return (
<div>
<div
className="container"
style={{
padding: 0,
}}
>
<div className="rowx my-2 flex-wrap">
{props.sensors && props.sensors.length
? filterTextSensors(props.sensors).map(
(s, sensorTypeIndex) => (
<div
key={s._id}
/////////////////////////////////////// Here is Error ///////////////////////////////////////////////
className={`cursor-pointer badge badge-pill m-1 ${props.sensorarray.includes(s.latinName) ? `badge-success` : `badge-light`}`}
onClick={() => {
showLogsSensor(s)
toggleFilter(s);
}}
>
{s.persianName}
</div>
)
)
: ""}
</div>
});
const mapStateToProps = (state) => {
return {
sensorarray: state.sensorarray,
};
};
export default connect(mapStateToProps)(TargetsList);
And the Drawer.js is change state that should color change in TargetList.js.
Drawer.js:
import React from "react";
import clsx from "clsx";
import {makeStyles} from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import {connect} from "react-redux";
import "assets/scss/global.scss";
import "./style.scss";
import socketConn from "sockets";
import moment from "jalali-moment";
import {MdSave} from "react-icons/md";
import axios from "config.js";
const useStyles = makeStyles({
fullList: {
width: "auto",
},
});
function BottomDrawer(props) {
const classes = useStyles();
const [tab, setTab] = React.useState("sensors");
const [state, setState] = React.useState({bottom: false});
const [type, setType] = React.useState(null);
const [info, setInfo] = React.useState(null);
const [logs, setLogs] = React.useState([]);
const toggleDrawer = (anchor, open) => {
setState({
...state,
[anchor]: open,
});
// stop receiving target logs
targetsLogAPIHandler(false);
};
const sensorunhi = () => {
props.dispatch({
type: "SENSOR_UNHI",
});
};
let setLogSocket = (type, info) => {
setLogs([]);
if (type === "target") {
targetsLogAPIHandler(true, info);
}
if (type === "sensor") {
sensorsLogAPIHandler(info);
}
};
// handler for API request to NodeJS to get logs of specific target (! ! ! requestMode => {true: when you want to START coming logs | false: when you want to STOP coming logs})
const targetsLogAPIHandler = async (requestMode, info) => {
try {
const userId = localStorage.getItem('user_id');
socketConn().removeAllListeners("show_specific_target_log");
const {data} = await axios.post(`/targets/log/${userId}`, {
requestMode,
unique_id: info ? info.unique_id : undefined
});
socketConn().on("show_specific_target_log", targetLogs => {
setLogs((prev) => {
return [targetLogs, ...prev].filter((s, index) => index < 500);
});
});
} catch (err) {
console.log(err);
}
};
// handler for API request to NodeJS to get logs of specific sensor
const sensorsLogAPIHandler = async info => {
try {
socketConn().removeAllListeners("sensor_moment_log");
let userId = localStorage.getItem("user_id");
const logs = await axios.post(`sensors/sensors/logs`, {topic: info.latinName, userId});
socketConn().on("sensor_moment_log", (data) => {
let enc = new TextDecoder("utf-8");
let decoded = enc.decode(data.result.content);
decoded = JSON.parse(decoded);
setLogs((prev) => {
return [decoded, ...prev].filter((s, index) => index < 500);
});
});
socketConn().on("sensor_moment_log_error", (data) => {
console.log(data);
});
} catch (err) {
console.log(err);
}
};
React.useEffect(() => {
setTab((prev) => {
if (prev != props.selectedTab) {
socketConn().removeAllListeners("sensor_moment_log");
socketConn().removeAllListeners("show_specific_target_log");
setLogs([]);
return props.selectedTab;
}
return prev;
});
}, [props.selectedTab]);
React.useEffect(() => {
if (props.showLogs && state["bottom"] == false) {
toggleDrawer("bottom", true);
}
}, [props.showLogs]);
React.useEffect(() => {
if (props.logInfo && props.logType) {
if (info != null) {
if (
type != props.logType ||
info.Identification + info.sensorName !=
props.logInfo.Identification + props.logInfo.sensorName
) {
setType(props.logType);
setInfo(props.logInfo);
setLogSocket(props.logType, props.logInfo);
}
} else {
setType(props.logType);
setInfo(props.logInfo);
setLogSocket(props.logType, props.logInfo);
}
}
}, [props.logInfo, props.logType]);
const list = anchor => (
<div className={clsx(classes.list, {[classes.fullList]: anchor === "bottom",})} role="presentation">
<div className="log-container p-4">
<React.Fragment>
<div className="rowx log-headers mb-3">
<div className="colx log-header font-weight-bold">نام سنسور</div>
<div className="colx log-header font-weight-bold">نوع هدف</div>
<div className="colx log-header font-weight-bold">طول</div>
<div className="colx log-header font-weight-bold">عرض</div>
<div className="colx log-header font-weight-bold">ارتفاع</div>
</div>
<div className="log-elements">
{logs &&
logs.length &&
logs.map((log, index) => (
<div className="rowx log-element mb-2" key={index}>
<div className="colx log-text">{log && log[0] && log[0].sensorName}</div>
<div className="colx log-text">{log && log[0] && log[0].sensorType}</div>
<div className="colx log-text">{log && log[0] && log[0].longitude}</div>
<div className="colx log-text">{log && log[0] && log[0].latitude}</div>
<div className="colx log-text">{log && log[0] && log[0].altitude}</div>
</div>
))}
</div>
</React.Fragment>
)}
</div>
</div>
);
return (
<div>
<React.Fragment>
<Drawer
classes={{root: "drawer-root"}}
ModalProps={{hideBackdrop: true}}
anchor="bottom"
open={state["bottom"]}
onClose={() => {
toggleDrawer("bottom", false);
sensorunhi(); /////////////////// CHANGE REDUX STATE
}}
>
{list("bottom")}
</Drawer>
</React.Fragment>
</div>
);
}
const mapStateToProps = (state) => {
return {
sensorarray: state.sensorarray,
};
};
export default connect(mapStateToProps)(BottomDrawer);
In other word, I want to change the color component TargetList.js by change the state in Drawer.js.

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

I am creating a library in which depending on the input parameters it is shown as nodes or as a tree.
I have been days trying to solve the problem but it always gives me the same error, when I delete the redux part it loads but if it shows the error "Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:".
I have tried calling redux functions the way you are seeing or using mapStateToProps with mapDispatchToProps.
Since I saw that it was a hooks error, I changed the code from hook to class.
Can anyone tell me what I'm doing wrong?
Version Hooks (I'm using this)
import React, { useEffect } from "react";
import { compose, Dispatch } from "redux";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { updateBranchs, updateNumBranchs } from "../../redux/actions/portal";
import {
updateSelected,
updateDma,
updateHiddenBranchs,
} from "../../redux/actions/hierarchy";
import { makeSelectBranchItem, makeSelectHierarchyItem } from "./selectors";
import { generatePortalBranch } from "../../utils/portal";
import {
replaceHierarchyBranch,
findDistance,
findDuplicatedItems,
} from "../../utils/hierarhcy";
import PotalBranch from "../portalbranch";
import HierarchyBranch from "../hierarchybranch";
import { MainProps, IBranch, IHierarchyBranch } from "./interfaces";
const MainTree = ({
data,
portal,
branch,
hierarchy,
onUpdateBranchs,
onUpdateNumBranchs,
onUpdateSelected,
onUpdateDma,
onUpdateHiddenBranchs,
removeBranchWithChildren,
}: MainProps) => {
useEffect(() => {
if (data && !portal) {
onUpdateBranchs([data]);
}
}, [data, portal, onUpdateBranchs]);
const onUpdatedHiddenBranchs = (ids: Array<string>, show: boolean) => {
let hiddenBranchs: Array<string> = [...hierarchy.hiddenBranchs];
let items: Array<string> = [];
if (!show) {
items = [...hiddenBranchs, ...ids];
} else {
const copyItems: Array<string> = [...hiddenBranchs];
const duplicatedItems: Array<string> = findDuplicatedItems(
copyItems
);
items = copyItems.filter((item: string) => {
return !ids.includes(item);
});
items = [...items, ...duplicatedItems];
}
onUpdateHiddenBranchs(items);
};
const onUpdateSelectedBranch = (id: number, dma: IHierarchyBranch) => {
onUpdateDma(dma);
onUpdateSelected(id);
};
const onUpdateFavourites = (dma: IHierarchyBranch) => {
let branchs: Array<IBranch> = [...branch.branchs];
const updatedDma: IHierarchyBranch = Object.assign({}, dma, {
favourite: !dma.favourite,
});
const updated: Array<IHierarchyBranch> = replaceHierarchyBranch(
branchs,
updatedDma
);
onUpdateBranchs(updated);
onUpdateDma(dma);
};
const updateBranchs = () => {
let numBranchs: number = branch.numBranchs;
const branchToUpdate: IBranch = generatePortalBranch(numBranchs);
let updatedBranchs = [...branch.branchs];
updatedBranchs.push(branchToUpdate);
onUpdateBranchs(updatedBranchs);
onUpdateNumBranchs(numBranchs + 1);
};
const renderHierarchyBranch = (index: number, branch: IHierarchyBranch) => {
const selected: number = hierarchy.selected;
const hiddenBranchs: Array<string> = [...hierarchy.hiddenBranchs];
const level: number = findDistance(data, branch.value);
return (
<HierarchyBranch
key={index}
id={branch.id}
level={level}
label={branch.label}
alarms={branch.value}
favourite={branch.favourite}
icon={branch.icon}
sensorizable={branch.sensorizable}
children={branch.children}
branch={branch}
selectedId={selected}
hiddenBranchs={hiddenBranchs}
onUpdateSelectedBranch={onUpdateSelectedBranch}
onUpdateFavourites={onUpdateFavourites}
onUpdatedHiddenBranchs={onUpdatedHiddenBranchs}
renderHierarchyBranch={renderHierarchyBranch}
/>
);
};
const renderPortalBranch = (index: number, branchSelected: IBranch) => {
const branchs: Array<IBranch> = [...branch.branchs];
const numBranchs: number = branch.numBranchs;
return (
<PotalBranch
key={index}
id={branchSelected.id}
parentId={branchSelected.parentId}
active={branchSelected.active}
favorite={branchSelected.favorite}
level={branchSelected.level}
title={branchSelected.title}
editable={branchSelected.editable}
checkbox={branchSelected.checkbox}
checkboxEnabled={branchSelected.checkboxEnabled}
children={branchSelected.children}
branch={branchSelected}
branchs={branchs}
removeBranchWithChildren={removeBranchWithChildren}
onUpdateBranchs={onUpdateBranchs}
numBranchs={numBranchs}
onUpdateNumBranchs={onUpdateNumBranchs}
renderPortalBranch={renderPortalBranch}
/>
);
};
const renderPortal = () => {
const branchs: Array<IBranch> = [...branch.branchs];
return (
<div className="go-react-tree">
<div className="go-react-tree__create-button">
<i
className="icon-plus-squared-alt"
onClick={updateBranchs}
/>
<span>Crear nuevo</span>
</div>
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map((branch: IBranch, index: number) => {
return renderPortalBranch(index, branch);
})}
</div>
</div>
);
};
const renderHierarchy = () => {
const branchs: Array<IHierarchyBranch> = [...branch.branchs];
return (
<div className="go-react-tree">
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map(
(branch: IHierarchyBranch, index: number) => {
return renderHierarchyBranch(index, branch);
}
)}
</div>
</div>
);
};
return portal ? renderPortal() : renderHierarchy();
};
const mapStateToProps = createStructuredSelector({
branch: makeSelectBranchItem(),
hierarchy: makeSelectHierarchyItem(),
});
function mapDispatchToProps(dispatch: Dispatch) {
return {
onUpdateBranchs: (branchs: Array<IBranch>) => {
dispatch(updateBranchs(branchs));
},
onUpdateNumBranchs: (numbranchs: number) => {
dispatch(updateNumBranchs(numbranchs));
},
onUpdateSelected: (id: number) => {
dispatch(updateSelected(id));
},
onUpdateDma: (dma: any) => {
dispatch(updateDma(dma));
},
onUpdateHiddenBranchs: (dmas: any) => {
dispatch(updateHiddenBranchs(dmas));
},
dispatch,
};
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
export default compose(withConnect)(MainTree);
Version Class Component
import React, { Component } from "react";
import { connect } from "react-redux";
import HierarchyActions from "../../redux/actions/hierarchy";
import PortalActions from "../../redux/actions/portal";
import { generatePortalBranch } from "../../utils/portal";
import {
replaceHierarchyBranch,
findDistance,
findDuplicatedItems,
} from "../../utils/hierarhcy";
import PotalBranch from "../portalbranch";
import HierarchyBranch from "../hierarchybranch";
import { IBranch, IHierarchyBranch } from "./interfaces";
class MainLibrary extends Component<any, any> {
componentDidMount() {
const { data, portal, updateBranchs } = this.props;
if (data && !portal) {
updateBranchs([data]);
}
}
onUpdatedHiddenBranchs = (ids: Array<string>, show: boolean) => {
const { updateHiddenBranchs } = this.props;
let hiddenBranchs: Array<string> = [...this.props.hiddenBranchs];
let items: Array<string> = [];
if (!show) {
items = [...hiddenBranchs, ...ids];
} else {
const copyItems: Array<string> = [...hiddenBranchs];
const duplicatedItems: Array<string> = findDuplicatedItems(
copyItems
);
items = copyItems.filter((item: string) => {
return !ids.includes(item);
});
items = [...items, ...duplicatedItems];
}
updateHiddenBranchs(items);
};
onUpdateSelectedBranch = (id: number, dma: IHierarchyBranch) => {
const { updateDma, updateSelected } = this.props;
updateDma(dma);
updateSelected(id);
};
onUpdateFavourites = (dma: IHierarchyBranch) => {
const { updateBranchs, updateDma } = this.props;
let branchs: Array<IBranch> = [...this.props.branchs];
const updatedDma: IHierarchyBranch = Object.assign({}, dma, {
favourite: !dma.favourite,
});
const updated: Array<IHierarchyBranch> = replaceHierarchyBranch(
branchs,
updatedDma
);
updateBranchs(updated);
updateDma(dma);
};
updateBranchs = (event: any) => {
event.preventDefault();
const { updateBranchs, updateNumBranchs } = this.props;
let numBranchs: number = this.props.numBranchs;
const branchToUpdate: IBranch = generatePortalBranch(numBranchs);
let updatedBranchs = [...this.props.branchs];
updatedBranchs.push(branchToUpdate);
updateBranchs(updatedBranchs);
updateNumBranchs(numBranchs + 1);
};
renderHierarchyBranch = (index: number, branch: IHierarchyBranch) => {
const { data } = this.props;
const selected: number = this.props.selected;
const hiddenBranchs: Array<string> = [...this.props.hiddenBranchs];
const level: number = findDistance(data, branch.value);
return (
<HierarchyBranch
key={index}
id={branch.id}
level={level}
label={branch.label}
alarms={branch.value}
favourite={branch.favourite}
icon={branch.icon}
sensorizable={branch.sensorizable}
children={branch.children}
branch={branch}
selectedId={selected}
hiddenBranchs={hiddenBranchs}
onUpdateSelectedBranch={this.onUpdateSelectedBranch}
onUpdateFavourites={this.onUpdateFavourites}
onUpdatedHiddenBranchs={this.onUpdatedHiddenBranchs}
renderHierarchyBranch={this.renderHierarchyBranch}
/>
);
};
renderPortalBranch = (index: number, branchSelected: IBranch) => {
const {
updateBranchs,
updateNumBranchs,
removeBranchWithChildren,
} = this.props;
const branchs: Array<IBranch> = [...this.props.branchs];
const numBranchs: number = this.props.numBranchs;
return (
<PotalBranch
key={index}
id={branchSelected.id}
parentId={branchSelected.parentId}
active={branchSelected.active}
favorite={branchSelected.favorite}
level={branchSelected.level}
title={branchSelected.title}
editable={branchSelected.editable}
checkbox={branchSelected.checkbox}
checkboxEnabled={branchSelected.checkboxEnabled}
children={branchSelected.children}
branch={branchSelected}
branchs={branchs}
removeBranchWithChildren={removeBranchWithChildren}
onUpdateBranchs={updateBranchs}
numBranchs={numBranchs}
onUpdateNumBranchs={updateNumBranchs}
renderPortalBranch={this.renderPortalBranch}
/>
);
};
renderPortal = () => {
const branchs: Array<IBranch> = [...this.props.branchs];
return (
<div className="go-react-tree">
<div className="go-react-tree__create-button">
<i
className="icon-plus-squared-alt"
onClick={this.updateBranchs}
/>
<span>Crear nuevo</span>
</div>
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map((branch: IBranch, index: number) => {
return this.renderPortalBranch(index, branch);
})}
</div>
</div>
);
};
renderHierarchy = () => {
const branchs: Array<IHierarchyBranch> = [...this.props.branchs];
return (
<div className="go-react-tree">
<div className="go-react-branches">
{branchs &&
branchs.length > 0 &&
branchs.map(
(branch: IHierarchyBranch, index: number) => {
return this.renderHierarchyBranch(
index,
branch
);
}
)}
</div>
</div>
);
};
render() {
const { portal } = this.props;
return portal ? this.renderPortal() : this.renderHierarchy();
}
}
function selectStateApp(state: any) {
return {
numBranchs: state.branch.numBranchs,
branchs: state.branch.branchs,
hiddenBranchs: state.hierarchy.hiddenBranchs,
selected: state.hierarchy.selected,
selectedDma: state.hierarchy.selectedDma,
};
}
export default connect(selectStateApp, (dispath) => ({
updateSelected: (id: number) =>
HierarchyActions.updateSelected(id)(dispath),
updateDma: (dma: any) => HierarchyActions.updateDma(dma)(dispath),
updateHiddenBranchs: (dmas: Array<string>) =>
HierarchyActions.updateHiddenBranchs(dmas)(dispath),
updateBranchs: (branchs: Array<IBranch>) =>
PortalActions.updateBranchs(branchs)(dispath),
updateNumBranchs: (numBranchs: number) =>
PortalActions.updateNumBranchs(numBranchs)(dispath),
}))(MainLibrary);

Resources