I'm trying to display a Google char, a table, and some widgets in a react component. I'm using React and redux to store the state of the entire app. Here I have the component:
import { useEffect, useState } from "react";
import Select from 'react-select';
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { updateFilters } from "../../../services/FiltersService";
import { buildQueryStringFromObject, listHeaders } from "../../../utils";
import FeedCheckFilter from "../../Commons/Filters/Filter";
import filtersProp from "../../Commons/Filters/FiltersProperties";
import Header from "../../Commons/Header";
import LoadingSpinner from "../../Commons/LoadingSpinner";
import {
createWidget,
deleteWidget,
editWidget,
getFeatures,
getFeaturesChartData
} from "../../../services/KeyDriversService";
import { KeyDriversChart } from "./KeyDriversChart";
import KeyDriversTable from "./KeyDriversTabel";
import KeyDriversWidgets from "./KeyDriversWidgets";
import KeyDriversForm from "./KeyDriversForm";
import './KeyDrivers.css';
export default function KeyDrivers() {
const dispatch = useDispatch()
let navigate = useNavigate();
const channels = useSelector((state) => state.channels.channels)
const groups = useSelector((state) => state.groups.groups)
const products = useSelector((state) => state.products.products)
const filters = useSelector((state) => state.filters.filters)
const token = useSelector((state) => state.user.profile.token)
const username = useSelector((state) => state.user.profile.auth)
let featureSets = useSelector((state) => state.keyDrivers.featureSets)
const [isLoading, setIsLoading] = useState(true)
const [keyDriverTableData, setKeyDriverTableData] = useState([])
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showCharts, setShowCharts] = useState(true)
const [featureSet, setFeatureSet] = useState()
const listHeader = listHeaders
const loadData = async (queryUrl = filters.url) => {
setIsLoading(true)
let featureSetId = undefined
if (featureSet) {
featureSetId = featureSet.id
} else {
featureSetId = featureSets[0].id;
}
let actionKeyDrivers = await getFeatures({token, username, queryUrl, featureSetId})
setFeatureSet({
label: actionKeyDrivers.payload[0].featureSet.name,
value: actionKeyDrivers.payload[0].featureSet.name,
id: actionKeyDrivers.payload[0].featureSet.id
})
dispatch(actionKeyDrivers)
if (featureSetId) {
let actionCartData = await getFeaturesChartData({token, username, queryUrl, featureSetId})
setShowCharts(true)
setKeyDriverTableData(actionCartData.payload)
} else {
setShowCharts(false)
}
setIsLoading(false)
}
// I got this pattern from this example:
// https://isotropic.co/how-to-fix-the-useeffect-must-not-return-anything-besides-a-function-warning/
useEffect(() => {
if (token === undefined) {
navigate('/login')
}
dispatch({type: 'ROUTE', payload: '/home/key-drivers'})
loadData()
}, [featureSet])
const changeFilters = (changedFilters) => {
let queryUrl = buildQueryStringFromObject(changedFilters);
changedFilters['url'] = queryUrl;
// changeFilters:filters
let filtersAction = updateFilters({changedFilters});
dispatch(filtersAction);
loadData(queryUrl);
}
const changeSelectFeatureSet = (val) => {
setFeatureSet(val)
}
const handleDeleteWidget = async (widgetId) => {
let action = await deleteWidget({token, username, widgetId})
dispatch(action)
loadData(filters.url)
setShowDeleteModal(false)
}
const handleEditWidget = async (widget) => {
let action = await editWidget({token, username, widget})
dispatch(action)
loadData(filters.url)
}
const handleCreateWidget = async (inputs) => {
inputs = {
...inputs,
'featureSet' : {
'id': featureSet.id
}
}
let action = await createWidget({token, username, inputs})
dispatch(action)
loadData(filters.url)
}
const filterProps = filtersProp(filters, products, groups, channels)
featureSets = featureSets && featureSets.map((featureSet) => {
return { label: featureSet.name, value: featureSet.name, id: featureSet.id }
})
return (
<div>
<Header listHeader={listHeader} mainHeader={false} />
<FeedCheckFilter
productItemName={filterProps.productItemName}
productDefault={filterProps.productDefault}
product={filterProps.product}
productOptions={filterProps.productOptions}
username={username}
isLoading={isLoading}
onChange={changeFilters}
/>
{isLoading ?
<LoadingSpinner />
:
<>
<div className="featureSet-select">
<Select
value={featureSet}
options={featureSets}
onChange={changeSelectFeatureSet}
/>
</div>
{showCharts ?
<>
<KeyDriversChart
data={keyDriverTableData ? keyDriverTableData : []}
featureSet={featureSet}
selectedProduct={filterProps.product}
/>
<KeyDriversTable featuresData={keyDriverTableData} />
<KeyDriversWidgets
data={keyDriverTableData}
show={showDeleteModal}
setShow={setShowDeleteModal}
deleteHandler={handleDeleteWidget}
editHandler={handleEditWidget}
filters={filters}
/>
</>
:
<>
<h1 className="driver-missing">There are no drivers defined!</h1>
</>
}
<KeyDriversForm handleClick={handleCreateWidget}/>
</>
}
</div>
)
}
I'm using the hook useEffect to load data at every state change and when the page loads. And on useEffect hook, I'm calling my function loadData which gets all the data. I also have a select component that has feastureSets values and all the charts and all data on that page depend on that select value. When I change that select it should render the page with new updates.
The problem is that the page is continuously updating and the loading spinner is rotating again and again.
loadData
callssetFeatureSet
, which will causefeatureSet
to be updated (not necessarily changed), meaning that youruseEffect
hook will be triggered, callingloadData
once again. Either change the deps of youruseEffect
, or change it to auseMemo
, which will only be called iffeatureSet
actually changes.