0

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.

2
  • 1
    You have an infinite loop. loadData calls setFeatureSet, which will cause featureSet to be updated (not necessarily changed), meaning that your useEffect hook will be triggered, calling loadData once again. Either change the deps of your useEffect, or change it to a useMemo, which will only be called if featureSet actually changes.
    – Stitt
    Commented May 26, 2023 at 9:07
  • thanks, Indeed the main problem was that there was an infinite loop
    – OnlyAUser
    Commented May 26, 2023 at 10:48

2 Answers 2

1

In your code, the featureSet value is being updated within the loadData function, which is called inside the useEffect hook. This creates a loop where changing featureSet triggers the effect, which calls loadData, which in turn updates featureSet, and so on.

update your useEffect code in the following way:

useEffect(() => {
    if (token === undefined) {
        navigate('/login');
    }
    dispatch({ type: 'ROUTE', payload: '/home/key-drivers' });
    loadData();
}, [token, username, filters.url]);

In this code, I've removed featureSet from the dependency array and added token, username, and filters.url instead. This ensures that the effect is triggered when any of these values change, allowing the data to be loaded accordingly.

1
  • The problem is now that component doesn't re-render when I change the select component. I want the chart and table on the page to be updated with the configuration from select.
    – OnlyAUser
    Commented May 26, 2023 at 9:28
0

You are creating an infinite loop with your useEffect call.

This effect is calling loadData()

useEffect(() => {
   if (token === undefined) {
      navigate('/login')
   }
   dispatch({type: 'ROUTE', payload: '/home/key-drivers'})
   loadData()
}, [featureSet /* <- here is the problem */])

and loadData is in turn calling setFeatureSet but since your effect is dependent on featureSet which is being updated by setFeatureSet you are creating a loop. Every call to loadData will rerun the effect forever.

The solution would be to remove featureSet from the dependency array.

//Edit:

Update your useEffect in the following way:

useEffect(() => {
    if (token === undefined) {
        navigate('/login');
    }
    dispatch({ type: 'ROUTE', payload: '/home/key-drivers' });
    loadData();
}, [token, username, filters.url]);
1
  • but if I remove the featureSet param from the dependency array then the component won't re-render when the select component changes.
    – OnlyAUser
    Commented May 26, 2023 at 9:14

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.