skip to Main Content

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

Answers


  1. 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]);
    
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search