Warning: Using UNSAFE_componentWillMount in strict mode is not recommended (upgrade to CRA 4.0.2) - reactjs

I updated my React application from 16.3+ to React 17 while upgrading to crate-react-app#4.0.2. Everything works as expected, but I see the following in the console:
Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See react-unsafe-component-lifecycles for details.
* Move code with side effects to componentDidMount, and set initial state in the constructor.
Please update the following components: SideEffect(NullComponent)
My App.jsx file:
import React, { useRef, useEffect, useCallback, createRef } from 'react';
import { useDispatch, useSelector, batch } from 'react-redux';
import './App.scss';
import { CountryBox, Error, MasterBox, MetaTags, ModalContainer, ScreenLoader } from '../../components';
import { dataActions, settingsActions, statisticsActions, statisticsUpdatesActions } from '../../store/actions/actions';
import { engineService } from '../../services';
import { coreUtils } from '../../utils';
const App = (props) => {
const dispatch = useDispatch();
// Refs.
const elRefs = useRef([]);
// State variables.
const settingsList = useSelector((state) => state.settings.settingsList);
const loadingList = useSelector((state) => state.settings.loadingList);
const sourcesList = useSelector((state) => state.data.sourcesList);
const countriesList = useSelector((state) => state.data.countriesList);
const { isActive, isRefreshMode, viewType, isDisplayError, activeModalName,
activeModalValue, isReplaceModalMode, isActionLoader } = settingsList;
const { loadingPrecentage, isScreenLoaderComplete } = loadingList;
// Functions to update the state.
const onSetStateCurrentTime = (data) => dispatch(statisticsActions.setStateCurrentTime(data));
const onSetStateSettingsList = (listName, listValues) => dispatch(settingsActions.setStateSettingsList(listName, listValues));
const onSetStateStatisticsField = (fieldName, fieldValue) => dispatch(statisticsActions.setStateStatisticsField(fieldName, fieldValue));
const onSetStateStatisticsList = (statisticsList) => dispatch(statisticsActions.setStateStatisticsList(statisticsList));
const onSetStateStatisticsUpdatesSettingsList = (statisticsUpdatesSettingsList) => dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
const onSetStateDataCollection = (collectionName, collectionValue) => dispatch(dataActions.setStateDataCollection(collectionName, collectionValue));
const onSetStateInitiateSettings = (data) => {
const { settingsList, loadingList } = data;
batch(() => {
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(settingsActions.setStateSettingsList('loadingList', loadingList));
});
};
const onSetStateInitiateSources = (data) => {
const { sourcesList, countriesList, countriesNameIdList, statisticsList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('sourcesList', sourcesList));
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
});
};
const onSetStateUpdateRound = (data) => {
const { countriesList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateActionUpdate = (data) => {
const { countriesList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
});
};
const onSetStateActionRefresh = (data) => {
const { countriesList, settingsList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateUpdateCountryVisibility = (data) => {
const { countriesList, countriesNameIdList, statisticsList, statisticsUpdatesList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
}
});
};
// Run the engine.
useEffect(() => {
engineService.runEngine({
mode: props.mode,
onSetStateCurrentTime: onSetStateCurrentTime,
onSetStateSettingsList: onSetStateSettingsList,
onSetStateStatisticsField: onSetStateStatisticsField,
onSetStateStatisticsList: onSetStateStatisticsList,
onSetStateStatisticsUpdatesSettingsList: onSetStateStatisticsUpdatesSettingsList,
onSetStateInitiateSettings: onSetStateInitiateSettings,
onSetStateInitiateSources: onSetStateInitiateSources,
onSetStateUpdateRound: onSetStateUpdateRound,
onSetStateDataCollection: onSetStateDataCollection,
onSetStateActionUpdate: onSetStateActionUpdate,
onSetStateActionRefresh: onSetStateActionRefresh,
onSetStateUpdateCountryVisibility: onSetStateUpdateCountryVisibility
});
return () => {
engineService.clearSources();
};
}, []);
// Set loader for each master action.
useEffect(() => {
engineService.updateActionLoader(false);
}, [countriesList]);
// After exit from any modal - Scroll back to the element's vertical position.
const scrollToCountry = useCallback((data) => {
const { action, value } = data;
if (action === 'modal' && !value && activeModalValue && !isReplaceModalMode && activeModalName !== 'country') {
setTimeout(() => {
const offsetTop = elRefs.current.find(c => c.current?.dataset?.countryId === activeModalValue).current.offsetTop;
if (offsetTop > window.innerHeight) {
window.scrollTo(0, offsetTop);
}
}, 10);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on master modal click.
const handleActionClick = useCallback((e) => {
if (!isActionLoader) {
const data = {
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getAttributeName(e, 'name'),
id: coreUtils.getAttributeName(e, 'data-country-id')
};
scrollToCountry(data);
engineService.runMasterActionClick(data);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on relevant modal change.
const handleModalActionChange = useCallback((e) => {
engineService.runModalActionUpdate({
modalName: coreUtils.getAttributeName(e, 'data-modal-name'),
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getValue(e)
});
}, []);
// Validate all OK to show the data and generate the countries.
const isInitiateComplete = !isDisplayError && countriesList && countriesList.length > 0 && loadingPrecentage === 100;
const renderCountries = useCallback(() => {
const countriesDOM = [];
const refsList = [];
for (let i = 0; i < countriesList.length; i++) {
const country = countriesList[i];
const ref = elRefs.current[i] || createRef();
refsList.push(ref);
countriesDOM.push(
(<CountryBox
key={country.id}
{...country} // React memo works only with separated properties.
isRefreshMode={isRefreshMode}
sourcesList={sourcesList}
onActionClick={handleActionClick}
ref={ref}
/>));
}
elRefs.current = refsList;
return countriesDOM;
}, [countriesList]);
return (
<div className="main">
{MetaTags}
{!isScreenLoaderComplete &&
<ScreenLoader
isActive={isActive}
loadingList={loadingList}
isDisplayError={isDisplayError}
/>
}
{isDisplayError &&
<Error
isDisplayError={isDisplayError}
/>
}
{activeModalName &&
<ModalContainer
onActionClick={handleActionClick}
onActionChange={handleModalActionChange}
/>
}
{isInitiateComplete &&
<div className="page">
<div className="main-container">
<div className={`container ${viewType} f32 f32-extra locations`}>
<MasterBox
onActionClick={handleActionClick}
/>
{renderCountries()}
</div>
</div>
</div>
}
</div>
);
};
export default App;
How can I fix this problem?

OK, I solved it.
The issue was with one of the components named MetaTags:
MetaTags.jsx
import React from 'react';
import { Helmet } from 'react-helmet';
import { timeUtils } from '../../../utils';
const MetaTags =
(<Helmet>
<title data-rh="true">World Covid 19 Data | Covid 19 World Data | {timeUtils.getTitleDate()}</title>
</Helmet>);
export default MetaTags;
The react-helmet package is outdated, and I needed to install 'react-helmet-async' instead, and change the code to:
initiate.jsx
app = (
<HelmetProvider>
<Suspense fallback={null}>
<Provider store={createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)))}>
<Helmet>
<title data-rh="true">Dynamic title {timeUtils.getTitleDate()}</title>
</Helmet>
<BrowserRouter>
{component}
</BrowserRouter>
</Provider>
</Suspense>
</HelmetProvider>
);
This solved my issue and the warning was gone.

Related

why console gives in this sequence?

I am making a mern ecommerce website i just want to see how useEffect works so i console.log in some part of useEffect and loadFilteredResults i saw that --->
initial
entry
skip
entry1
screen shot
but i think it shoud be-->
initial
entry
entry1
skip
why console give this?? i am a begginer, i am a self learner , so please if you need any extra info please comment.
code snippet-->
const loadFilteredResults = (newFilters) => {
console.log("entry")
getFilteredProducts(skip, limit, newFilters).then((data) => {
console.log("entry1")
if (data.error) {
setError(data.error);
} else {
//console.log(data);
setFilteredResults(data.data);
//console.log("size-->");
//console.log(data.size);
setSize(data.size);
setSkip(0);
}
});
};
....
....
useEffect(() => {
init();
console.log("initial");
loadFilteredResults(skip, limit, myFilters.filters);
console.log("skip");
}, []);
//full code of shop.js
import React, { useEffect, useState } from "react";
import Layout from "./Layout";
import Card from "./Card";
import { getCategories, getFilteredProducts } from "./apiCore";
import Checkbox from "./Checkbox";
import RadioBox from "./RadioBox";
import { prices } from "./fixedPrices";
const Shop = () => {
const [myFilters, setMyFilters] = useState({
filters: { category: [], price: [] }
});
const [categories, setCategories] = useState([]);
const [error, setError] = useState(false);
const [limit, setLimit] = useState(3);//prpduct lesss so use 3 but sir used 6
const [skip, setSkip] = useState(0);
const [size, setSize] = useState(0);
const [filteredResults, setFilteredResults] = useState([]);
const init = () => {
getCategories().then((data) => {
if (data.error) {
//console.log("error");
setError(data.error);
} else {
//console.log("set");
//console.log(data);
setCategories(data);
//console.log(data);
}
});
};
const loadFilteredResults = (newFilters) => {
//console.log(newFilters);
console.log("entry")
getFilteredProducts(skip, limit, newFilters).then((data) => {
console.log("entry1")
if (data.error) {
setError(data.error);
} else {
//console.log(data);
setFilteredResults(data.data);
//console.log("size-->");
//console.log(data.size);
setSize(data.size);
setSkip(0);
}
});
};
const loadMore = () => {
console.log("skip"+skip);
console.log("limit"+limit);
let toSkip = skip + limit;
console.log("toSkip"+toSkip);
getFilteredProducts(toSkip, limit, myFilters.filters).then((data) => {
//console.log("filter");
//console.log( myFilters.filters)
if (data.error) {
setError(data.error);
} else {
//console.log(filteredResults);
//console.log(data.data);
setFilteredResults([...filteredResults, ...data.data]);
//console.log("after");
//console.log(...filteredResults);
//console.log(filteredResults);
//console.log(filteredResults);
//console.log([...filteredResults])
//console.log([...filteredResults, ...data.data])
setSize(data.size);
setSkip(toSkip);
}
});
};
const loadMoreButton = () => {
return (
size > 0 &&
size >= limit && (
<button onClick={loadMore} className="btn btn-warning mb-5">
load more
</button>
)
);
};
useEffect(() => {
init();
//console.log(skip);
console.log("initial");
loadFilteredResults(skip, limit, myFilters.filters);
console.log("skip");
}, []);
const handleFilters = (filters, filterBy) => {
//console.log("SHOP", filters, filterBy);
const newFilters = { ...myFilters };
//console.log(newFilters);
newFilters.filters[filterBy] = filters;
//console.log(typeof(filters));
if (filterBy === "price") {
let priceValues = handlePrice(filters);
newFilters.filters[filterBy] = priceValues;
//console.log(priceValues);
}
//console.log(myFilters.filters);
loadFilteredResults(myFilters.filters);
setMyFilters(newFilters);
};
const handlePrice = (value) => {
const data = prices;
let array = [];
//console.log(value);
for (let key in data) {
if (data[key]._id === parseInt(value)) {
array = data[key].array;
}
}
return array;
};
// const x = (filters)=>{
// console.log("filters:"+filters);
// handleFilters(filters, "category")
// }
return (
<Layout
title="Shop Page"
description="search and buy books of your choice"
className="container-fluid"
>
<div className="row">
<div className="col-4">
<h4>Filter by categories</h4>
<ul>
{/* below will be show in list show we wrap it in unorder list */}
<Checkbox
categories={categories}
handleFilters={(filters) =>
handleFilters(filters, "category")
}
/>
</ul>
<h4>Filter by price range</h4>
<div>
<RadioBox
prices={prices}
handleFilters={(filters) => handleFilters(filters, "price")}
/>
</div>
</div>
<div className="col-8">
<h2 className="mb-4">Products</h2>
<div className="row">
{filteredResults.map((product, i) => (
<Card key={i} product={product} />
))}
</div>
<hr />
{loadMoreButton()}
</div>
</div>
</Layout>
);
};
export default Shop;
getFilteredProducts must be a Promise. Please read Using promises
Callbacks added with then() will never be invoked before the
completion of the current run of the JavaScript event loop.

React has detected a change in the order of Hooks error

I tried implementing infinite scroll using useSWRInfinite hook.
Found a code from an Youtube tutorial 👈 and made little alteration.
But got an error named "React has detected a change in the order of Hooks called by InfiniteDataList."
2 hours of debugging -- no solution found.
Picture of the error
Youtube tutorial code --> https://github.com/gdangelo/micro-blogging-workshop/blob/main/components/InfiniteDataList.js
Youtube tutorial link --> https://www.youtube.com/watch?v=FsngdxyvFrQ
MY CODE:
InfiniteDataList.js
import React, { useEffect, useRef } from "react";
import { useInfiniteQuery } from "../../hooks";
import MessageWrapper from "../../UI/MessageWrapper";
import { isInViewport } from "../../Utility/windowUtils";
import { useDebouncedCallback } from "use-debounce";
import DefaultListItemComponent from "./components/DefaultListItemComponent";
import DefaultContainerComponent from "./components/DefaultContainerComponent";
import DefaultLoadMoreComponent from "./components/DefaultLoadMoreComponent";
const InfiniteDataList = ({
queryKey,
initialData = [],
listItemComponent: ListItemComponent = DefaultListItemComponent,
containerComponent: ContainerComponent = DefaultContainerComponent,
onError = () => {},
onEmpty = () => {},
onEmptyComponent: OnEmptyComponent = null,
onNoMoreData = () => {},
noMoreDataComponent: NoMoreDataComponent = null,
isAutoLoadMoreAtEnd = true,
autoLoadMoreAtEndOptions: {
timeout = 500,
onLoadMoreDetected = () => {},
} = {},
loadMoreComponent: LoadMoreComponent = DefaultLoadMoreComponent,
}) => {
// hooks
const {
data,
error,
hasNextPage,
fetchNextPage,
isFetchingInitialData,
isFetchingNextPageData,
} = useInfiniteQuery(queryKey, { initialData });
const moreRef = useRef();
const loadMore = useDebouncedCallback(() => {
if (isInViewport(moreRef.current)) {
onLoadMoreDetected();
fetchNextPage();
}
}, timeout);
const getLoadMoreRef = () => moreRef;
useEffect(() => {
if (isAutoLoadMoreAtEnd) {
window.addEventListener("scroll", loadMore);
}
return () => window.removeEventListener("scroll", loadMore);
}, []);
// some configuration
OnEmptyComponent = OnEmptyComponent && (() => <h4>No Details found</h4>);
NoMoreDataComponent =
NoMoreDataComponent &&
(() => <MessageWrapper message="No More Data found !" />);
// helper utils
const infiniteQueryProps = {
data,
error,
hasNextPage,
fetchNextPage,
isFetchingInitialData,
isFetchingNextPageData,
};
// if error occurs
if (error) {
onError(error);
console.log("error");
}
// no data found
if (!isFetchingInitialData && data?.length === 0) {
onEmpty();
console.log(typeof OnEmptyComponent);
return <OnEmptyComponent />;
}
// no more data to load
if (!hasNextPage) {
onNoMoreData();
}
return (
<ContainerComponent loading={isFetchingInitialData}>
{data?.map((item, index) => (
<ListItemComponent key={index} {...item} />
))}
{hasNextPage ? (
<LoadMoreComponent
{...infiniteQueryProps}
getLoadMoreRef={getLoadMoreRef}
/>
) : (
<NoMoreDataComponent {...infiniteQueryProps} />
)}
</ContainerComponent>
);
};
export default InfiniteDataList;
useInfiniteQuery.js
import useSWRInfinite from "swr/infinite";
import { axiosInstance } from "../Utility/axiosInstance";
function getFetcher(requestType = "get") {
return (url, dataToPost) =>
axiosInstance[requestType](url, dataToPost).then((res) => res.data);
}
export function useInfiniteQuery(
queryKey,
{ initialData, requestType = "get" }
) {
const { data, error, size, setSize } = useSWRInfinite(
(pageIndex, previousPageData) => {
// reached the end
if (previousPageData && !previousPageData.after) return null;
// first page
if (pageIndex === 0) return queryKey;
// next pages
const search = queryKey.includes("?");
return `${queryKey}${search ? "$" : "?"}cursor=${encodeURIComponent(
JSON.stringify(previousPageData.after)
)}`;
},
getFetcher(requestType),
initialData
);
// to fetch next page from react component
function fetchNextPage() {
setSize((prev) => prev + 1);
}
// flatten all the data obtained so far to a single array
const flattenPages = data?.flatMap((page) => page.data) ?? [];
// indicates whether the api will have data for another page
const hasNextPage = !!data?.[size - 1]?.after;
// isLoading for initial request
const isFetchingInitialData = !data && !error;
// isLoading for other requests including the initial request
const isFetchingNextPageData =
isFetchingInitialData ||
(size > 0 && data && typeof data[size - 1] === "undefined");
return {
data: flattenPages,
error,
hasNextPage,
fetchNextPage,
isFetchingInitialData,
isFetchingNextPageData,
};
}
isInViewport.js
// Check if element is visible inside the viewport
export function isInViewport(element) {
if (!element) return false;
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
DefaultLoadMoreComponent.js
import React, { useState } from "react";
const DefaultLoadMoreComponent = ({ getLoadMoreRef = () => {} }) => {
const ref = getLoadMoreRef();
return <div ref={ref} />;
};
export default DefaultLoadMoreComponent;
DefaultListItemComponent.js
import React from "react";
const DefaultListItemComponent = ({ children = [] }) => <div>{children}</div>;
export default DefaultListItemComponent;
DefaultContainerComponent.js
import React from "react";
import AsyncDiv from "../../../UI/AsyncDiv";
const DefaultContainerComponent = ({ children = [], ...rest }) => (
<AsyncDiv {...rest}>{children}</AsyncDiv>
);
export default DefaultContainerComponent;
Component where I render InfiniteDataList component
import React from "react";
import InfiniteDataList from "../../../../../UI/InfiniteDataList";
import PaginatedLeads from "./components/PaginatedLeads";
import { getError } from "../../../../../Utility/apiUtils";
const ViewAllLeads = (props) => {
return (
<InfiniteDataList
initialData={[]}
listItemComponent={PaginatedLeads}
onError={(err) =>
window.flash({ title: getError(err).message, type: "error" })
}
queryKey="/employee/leads"
/>
);
};
export default ViewAllLeads;
PaginatedLeads.js
import React from "react";
const PaginatedLeads = (props) => {
console.log(props);
return <div>PaginatedLeads</div>;
};
export default PaginatedLeads;
It is my mistake.
In useInfiniteQuery.js file, I passed the initial data in wrong format.
Wrong syntax -- see the last before line(initialData)
const { data, error, size, setSize } = useSWRInfinite(
(pageIndex, previousPageData) => {
// reached the end
if (previousPageData && !previousPageData.after) return null;
// first page
if (pageIndex === 0) return queryKey;
// next pages
const search = queryKey.includes("?");
return `${queryKey}${search ? "&" : "?"}cursor=${encodeURIComponent(
JSON.stringify(previousPageData.after)
)}`;
},
getFetcher(requestType),
initialData
);
Right syntax -- see the last before line({ fallbackData: initialData })
const { data, error, size, setSize } = useSWRInfinite(
(pageIndex, previousPageData) => {
// reached the end
if (previousPageData && !previousPageData.after) return null;
// first page
if (pageIndex === 0) return queryKey;
// next pages
const search = queryKey.includes("?");
return `${queryKey}${search ? "&" : "?"}cursor=${encodeURIComponent(
JSON.stringify(previousPageData.after)
)}`;
},
getFetcher(requestType),
{ fallbackData: initialData }
);

ReferenceError: Cannot access 'players' before initialization

Following suggestions from You (StackOverflow), I have rewritten my app to [several components]https://codesandbox.io/s/points-scored-forked-brnni?file=/src/components/ScoredPointsList.js:0-561
Now when I run yarn start, I get a reference error. After googling, I have tried with "everything" including:
changed from npm to yarn, changed in package.json ... but nothing seems to help.
How can I change the code to make it work?
import NewPointsScored from './components/NewPointsScored';
import ScoredPointsList from './components/ScoredPointsList';
function App() {
const [scorerNumber, setScorerNumber] = useState('');
const [totPoints, setTotPoints] = useState(0);
const [players, setPlayers] = useState([]);
const sortedPlayers = [...players].sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
const onePointHandler = () => {
// eslint-disable-next-linex
const players = [...players];
if (scorerNumber.trim() === 0) {
return;
}
const posit = players
.map((player) => player.scorerNumber)
.indexOf(+scorerNumber);
if (posit !== -1) {
setPlayers((players) =>
players.map(
(player, i) =>
(i = posit ? {...player, totPoints: player.totPoints + 1} : player)
)
);
} else {
const newScorer = {
id: Math.floor(Math.random() * 1000),
scorerNumber: +scorerNumber,
totPoints: totPoints + 1,
};
setPlayers([...players, newScorer]);
setTotPoints(totPoints);
}
setScorerNumber('');
};
const twoPointsHandler = (e) => {
e.preventDefault();
console.log('scored 2p');
};
const threePointsHandler = (e) => {
e.preventDefault();
console.log('3p Made!');
};
return (
<div className="App">
<NewPointsScored
setScorerNumber={setScorerNumber}
scorerNumber={scorerNumber}
onOneP={onePointHandler}
onTwoP={twoPointsHandler}
onThreeP={threePointsHandler}
/>
<ScoredPointsList sortedPlayers={sortedPlayers} />
</div>
);
}
export default App;
Thanks in advance
Regards
Peter
You are trying to modify players variable before it is being initialized , you can useMemo(which runs only if players value change) and modify sortPlayers after it is available and also you were trying to initalize players again that was causing issue
const sortedPlayers =useMemo(()=>{
return players.sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
},[players])
Full Code:
import NewPointsScored from './components/NewPointsScored';
import ScoredPointsList from './components/ScoredPointsList';
import {useMemo} from 'react'
function App() {
const [scorerNumber, setScorerNumber] = useState('');
const [totPoints, setTotPoints] = useState(0);
const [players, setPlayers] = useState([]);
const sortedPlayers =useMemo(()=>{
return players?.sort(
(a, b) => a.scorerNumber - b.scorerNumber
);
},[players])
const onePointHandler = () => {
const _players = [...players];
if (scorerNumber.trim() === 0) {
return;
}
const posit = _players
.map((player) => player.scorerNumber)
.indexOf(+scorerNumber);
if (posit !== -1) {
setPlayers((players) =>
players.map(
(player, i) =>
(i = posit
? { ...player, totPoints: player.totPoints + 1 }
: player)
)
);
} else {
const newScorer = {
id: Math.floor(Math.random() * 1000),
scorerNumber: +scorerNumber,
totPoints: totPoints + 1
};
setPlayers([..._players, newScorer]);
setTotPoints(totPoints);
}
setScorerNumber("");
};
const twoPointsHandler = (e) => {
e.preventDefault();
console.log('scored 2p');
};
const threePointsHandler = (e) => {
e.preventDefault();
console.log('3p Made!');
};
return (
<div className="App">
<NewPointsScored
setScorerNumber={setScorerNumber}
scorerNumber={scorerNumber}
onOneP={onePointHandler}
onTwoP={twoPointsHandler}
onThreeP={threePointsHandler}
/>
<ScoredPointsList sortedPlayers={sortedPlayers} />
</div>
);
}
export default App;
refer sandbox:

Removing d3-flame-graph when loading new data

Recently I picked up a project that has d3-flame-graph on it and the graph is displayed according to the Filters defined on another component.
My issue is that when searching with new parameters I can't seem to clean the previous chart and I was wondering if someone could help me. Basically what I'm having right now is, when I first enter the page, the loading component, then I have my graph and when I search for a new date I have the loading component but on top of that I still have the previous graph
I figured I could use flamegraph().destroy() on const updateGraph but nothing is happening
import React, { FC, useEffect, useRef, useState, useCallback } from 'react'
import { useParams } from 'react-router-dom'
import moment from 'moment'
import * as d3 from 'd3'
import { flamegraph } from 'd3-flame-graph'
import Filters, { Filter } from '../../../../../../components/Filters'
import { getFlamegraph } from '../../../../../../services/flamegraph'
import { useQueryFilter } from '../../../../../../hooks/filters'
import FlamegraphPlaceholder from '../../../../../../components/Placeholders/Flamegraph'
import css from './flamegraph.module.css'
import ToastContainer, {
useToastContainerMessage,
} from '../../../../../../components/ToastContainer'
const defaultFilters = {
startDate: moment().subtract(1, 'month'),
endDate: moment(),
text: '',
limit: 10,
}
const getOffSet = (divElement: HTMLDivElement | null) => {
if (divElement !== null) {
const padding = 100
const minGraphHeight = 450
// ensure that the graph has a min height
return Math.max(
window.innerHeight - divElement.offsetTop - padding,
minGraphHeight
)
} else {
const fallBackNavigationHeight = 300
return window.innerHeight - fallBackNavigationHeight
}
}
const Flamegraph: FC = () => {
const [queryFilters, setQueryFilters] = useQueryFilter(defaultFilters)
const [fetching, setFetching] = useState(false)
const [graphData, setGraphData] = useState()
const {
messages: toastMessages,
addMessage: addMessageToContainer,
removeMessage: removeMessageFromContainer,
} = useToastContainerMessage()
const flameContainerRef = useRef<HTMLDivElement | null>(null)
const flameRef = useRef<HTMLDivElement | null>(null)
const graphRef = useRef<any>()
const graphDataRef = useRef<any>()
const timerRef = useRef<any>()
const { projectId, functionId } = useParams()
let [sourceId, sourceLine] = ['', '']
if (functionId) {
;[sourceId, sourceLine] = functionId.split(':')
}
const createGraph = () => {
if (flameContainerRef.current && flameRef.current) {
graphRef.current = flamegraph()
.width(flameContainerRef.current.offsetWidth)
.height(getOffSet(flameRef.current))
.cellHeight(30)
.tooltip(false)
.setColorMapper(function(d, originalColor) {
// Scale green component proportionally to box width (=> the wider the redder)
let greenHex = (192 - Math.round((d.x1 - d.x0) * 128)).toString(16)
return '#FF' + ('0' + greenHex).slice(-2) + '00'
})
}
}
const updateGraph = (newData: any) => {
setGraphData(newData)
graphDataRef.current = newData
if (graphRef.current) {
if (newData === null) {
graphRef.current.destroy()
graphRef.current = null
} else {
d3.select(flameRef.current)
.datum(newData)
.call(graphRef.current)
}
}
}
const fetchGraph = (filters: Filter) => {
setFetching(true)
getFlamegraph(
Number(projectId),
filters.startDate ? filters.startDate.unix() : 0,
filters.endDate ? filters.endDate.unix() : 0,
sourceId,
sourceLine
)
.then(graphData => {
if (!graphRef.current) {
createGraph()
}
updateGraph(graphData)
})
.catch(({ response }) => {
updateGraph(null)
if (response.data) {
addMessageToContainer(response.data.message, true)
}
})
.finally(() => {
setFetching(false)
})
}
const onResize = useCallback(() => {
clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => {
if (graphRef.current && flameContainerRef.current) {
graphRef.current.width(flameContainerRef.current.offsetWidth)
d3.select(flameRef.current)
.datum(graphDataRef.current)
.call(graphRef.current)
}
}, 500)
}, [])
useEffect(() => {
fetchGraph(queryFilters)
window.addEventListener('resize', onResize)
return () => {
window.removeEventListener('resize', onResize)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const onChangeFilters = (filters: Filter) => {
setQueryFilters(filters)
fetchGraph(filters)
}
return (
<div className={css.host}>
<Filters
defaultValues={queryFilters}
searching={fetching}
onSearch={onChangeFilters}
/>
<div className={css.flameBox}>
<div className={css.flameContainer} ref={flameContainerRef}>
<div ref={flameRef} />
</div>
{fetching || !graphData ? (
<FlamegraphPlaceholder loading={fetching} />
) : null}
</div>
<ToastContainer
messages={toastMessages}
toastDismissed={removeMessageFromContainer}
/>
</div>
)
}
export default Flamegraph
Firstly, flamegraph() creates a new instance of flamegraph, you'd need to use graphref.current.destroy(). Secondly, you'd want to destroy this not when the data has already been loaded, but just as it starts to load, right? Because that's the operation that takes time.
Consider the following:
const cleanGraph = () => {
if (graphref.current !== undefined) {
graphref.current.destroy()
}
}
const fetchGraph = (filters: Filter) => {
setFetching(true)
cleanGraph()
getFlamegraph(
Number(projectId),
filters.startDate ? filters.startDate.unix() : 0,
filters.endDate ? filters.endDate.unix() : 0,
sourceId,
sourceLine
)
...
}

Added isCancelled flag in useEffect, but still getting "Can't perform a React state update on an unmounted component..."

I created follow button component which cause a problem. It displays singularly on a tag page. On first load there is no error, but when I'm clicking other tag to display other tag's page then this error appears:
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in FollowButton (at TagPage.tsx:79)
All answers I found on the internet says about adding isCancelled flag in useEffect hook, which I did, but it didn't help at all.
import React, { useEffect, useState, useContext } from "react";
import { Button } from "react-bootstrap";
import { FaRegEye } from "react-icons/fa";
import { useTranslation } from "react-i18next";
import FollowInfo from "../models/dtos/read/FollowInfo";
import UsersService from "../services/UsersService";
import Viewer from "../models/Viewer";
import { ViewerContext } from "../ViewerContext";
interface Props {
for: "user" | "tag";
withId: number;
}
const FollowButton = (props: Props) => {
//todo too much rerenders, button actually blinks at start and show wrong state
const { t } = useTranslation();
const [followInfo, setFollowInfo] = useState<FollowInfo | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
console.log("follow rerender", followInfo);
let viewer: Viewer = useContext(ViewerContext);
useEffect(() => {
let isCancelled = false;
!isCancelled && setFollowInfo(null);
const fetchData = async () => {
if (props.for === "user") {
!isCancelled &&
setFollowInfo(
await UsersService.amIFollowingUser(
props.withId,
viewer.currentUser?.token
)
);
} else {
!isCancelled &&
setFollowInfo(
await UsersService.amIFollowingTag(
props.withId,
viewer.currentUser?.token
)
);
}
};
!isCancelled && fetchData();
return () => {
isCancelled = true;
};
}, [props, viewer.currentUser]);
const follow = (what: "tag" | "user", withId: number) => {
if (what === "user") {
followUser(withId);
} else {
followTag(withId);
}
setFollowInfo((state) => {
if (state != null) {
return { ...state, doesFollow: true };
} else {
return { receiveNotifications: false, doesFollow: true };
}
});
};
const unfollow = (what: "tag" | "user", withId: number) => {
if (what === "user") {
unfollowUser(withId);
} else {
unfollowTag(withId);
}
setFollowInfo((state) => {
if (state != null) {
return { ...state, doesFollow: false };
} else {
return { receiveNotifications: false, doesFollow: false };
}
});
};
const followUser = (userId: number) =>
makeRequest(() =>
UsersService.followUser(userId, viewer.currentUser?.token)
);
const unfollowUser = (userId: number) =>
makeRequest(() =>
UsersService.unfollowUser(userId, viewer.currentUser?.token)
);
const followTag = (tagId: number) =>
makeRequest(() => UsersService.followTag(tagId, viewer.currentUser?.token));
const unfollowTag = (tagId: number) =>
makeRequest(() =>
UsersService.unfollowTag(tagId, viewer.currentUser?.token)
);
const makeRequest = (call: () => Promise<any>) => {
setIsSubmitting(true);
call().then(() => setIsSubmitting(false));
};
return (
<>
{followInfo == null ? (
t("loading")
) : followInfo.doesFollow ? (
<Button
disabled={isSubmitting}
variant="light"
onClick={() => unfollow(props.for, props.withId)}
>
<FaRegEye />
{t("following")}
</Button>
) : (
<Button
disabled={isSubmitting}
onClick={() => follow(props.for, props.withId)}
>
<FaRegEye />
{t("follow")}
</Button>
)}
</>
);
};
export default FollowButton;
!isCancelled && setFollowInfo(await...) checks the flag and schedules setFollowInfo to execute when data is ready. The flag may change during await.
Try this:
if (!isCancelled) {
const data = await UsersService.amIFollowingUser(
props.withId,
viewer.currentUser?.token
);
!isCancelled && setFollowInfo(data);
}
Also check the documentation for AbortController. It will be better to use it inside UsersService.amIFollowing*

Resources