2

Ok then. I have this code and is just for practice so it could be a bit messy (please ignore the cammelCase convention or bad practices unless they are the cause of the problem, beacause I've already take note of it), howerver i'm going to describe what it does and I'll show the code.

I have this componet that is rendered without data at first time, then when the users click a button (in a parrent component) an API it's called (also in the parrent) and the response is sended to this problematic component through props. After de data from the API is recived (props.rows), a useEffect is executed to format this data and store it in state (Rows). When Rows value changes, another useEffect should be executed and manipulate Rows (without mutating it) to create another kind of data (TotalPages). The thing is that when I call setTotalPages() this produce an infinite loop and I get the error Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

I'll show the code now

This is the problematic component

import '../Css/RGrid.css';
//import {useLayoutEffect} from 'react';
import React, {useCallback, useState} from 'react';
import {useEffect} from 'react';
//import {useLayoutEffect} from 'react';

//import {useLayoutEffect} from 'react';
//import {Row} from 'react-bootstrap';
//import {act} from 'react-dom/cjs/react-dom-test-utils.production.min';

const RGridTest = props => {
  const [Rows, setRows] = useState([]);
  const [rowsPerPage, setRowsPerPage] = useState(10);
  const [TotalRows, setTotalRows] = useState(0);
  const [actualPageIndex, setActualPageIndex] = useState(0);
  const [TotalPages, setTotalPages] = useState(0);
  const [counter, setCounter] = useState(0);

  const ddlPages_OnChange = value => {
    try {
      setRowsPerPage(value);
    } catch (e) {
      console.log(e.message);
    }
  };

  const Export = value => {
    console.log(value);
  };

  const EnabledPaging = () => {
    let b = false;
    try {
      b = Rows.length > 0 && Rows.length < 9999 && rowsPerPage < 9999;
    } catch (e) {
      console.log(e);
    }
    return b;
  };

  const PrevPage = () => {
    try {
      if (actualPageIndex > 1) {
        setActualPageIndex(actualPageIndex - 1);
      }
    } catch (e) {
      console.log(e.message);
    }
  };

  const NextPage = () => {
    try {
      if (actualPageIndex < TotalPages) {
        setActualPageIndex(actualPageIndex + 1);
      }
    } catch (e) {
      console.log(e.message);
    }
  };

  const calcularPaginas = useCallback(() => {
    if (Rows.length === 0) {
      console.log('Sin filas');
      return false;
    }

    console.log('llegaron las filas');
    const cantidadFilas = Rows.length;
    let iTotal = 0;

    console.log(cantidadFilas);
    if (cantidadFilas % rowsPerPage === 0) {
      console.log('Division perfecta');
      iTotal = Math.ceil(cantidadFilas / rowsPerPage);
    }
    console.log('Division imperfecta');
    iTotal = Math.ceil(cantidadFilas / rowsPerPage);
    console.log(cantidadFilas, rowsPerPage);
    console.log(iTotal);
    setTotalPages(iTotal);
  }, [Rows]);

  const ChangeId = () => {
    let sText;
    let oComplete;
    try {
      if (props.rows.length === 0) {
        console.log('No change id');
        return;
      }
      sText = JSON.stringify(props.rows);
      sText = sText.replace('"' + props.ConfigurationId + '":', '"RowId":');
      oComplete = JSON.parse(sText);
      setRows(oComplete);
      setTotalRows(oComplete.length);

      console.log('ChangeId Rows');
      console.log(oComplete.length);
    } catch (e) {
      console.log(e.message);
    }
  };

  useEffect(() => {
    ChangeId();
  }, [props.rows]);

  useEffect(() => {
    calcularPaginas();
  }, [Rows, calcularPaginas]);

  return (
    <div>
      {props.isLoading ? (
        <h2>Loading ...</h2>
      ) : (
        <React.Fragment>
          <span>
            <select
              className="Select"
              name="ddlPages"
              id="ddlPages"
              key="ddlPages"
              onChange={e => ddlPages_OnChange(e.target.value)}
            >
              <option value="10"> 10 </option>
              <option value="25"> 25 </option>
              <option value="50"> 50 </option>
              <option value="100"> 100 </option>
              <option value="9999"> All </option>
            </select>
          </span>
          {props?.Export && (
            <span>
              <button value="csv" className="btn-2" onClick={e => Export(e.target.value)}>
                CSV
              </button>
              <button value="xls" className="btn-2" onClick={e => Export(e.target.value)}>
                Excel
              </button>
              <button value="pdf" className="btn-2" onClick={e => Export(e.target.value)}>
                PDF
              </button>
            </span>
          )}
          <table width="99%" border="0" align="center">
            <tr className="TrTittle">
              <td className="TdTittle" align="center">
                <a>{props.Tittle}</a>
              </td>
            </tr>
          </table>
          <table className="Table" key="tgrid" width="99%" align="center">
            <thead key="thead">
              <tr key="trHead">
                {props.columns.map((column, idx) => {
                  return (
                    <th className="TableCellBold" width={column.WidthColumn}>
                      {column.Titulo}
                    </th>
                  );
                })}
                {props.ShowDelete && (
                  <th width="1%" className="TableCellBold">
                    Action
                  </th>
                )}
              </tr>
            </thead>
            <tbody>
              {Rows.map((row, idx) => {
                if (idx <= rowsPerPage) {
                  return (
                    <tr key={'tr' + idx}>
                      {props.columns.map((column, colx) => {
                        return (
                          <td className="TableCell" width={column.WidthColumn}>
                            {column.Selector(row)}
                          </td>
                        );
                      })}
                      {props.ShowDelete && (
                        <td className="TableCellBold" align="center">
                          <a href="#" onClick={() => props.DeleteId(row.RowId)}>
                            <img
                              className="imgDelete"
                              title="Next"
                              border="0"
                              width="18px"
                              height="18px"
                            ></img>
                          </a>
                        </td>
                      )}
                    </tr>
                  );
                }
              })}
            </tbody>
            <tfoot>
              <tr key="trFoot">
                <td
                  key="tdFoot"
                  align="right"
                  colSpan={props.columns.length + 1}
                  className="TableCellBold"
                >
                  {EnabledPaging() && (
                    <div className="DivFooter">
                      <a href="#" onClick={PrevPage()}>
                        <img
                          className="imgPrev"
                          title="Next"
                          border="0"
                          width="18px"
                          height="18px"
                        ></img>
                      </a>

                      <a>
                        {' '}
                        Page {actualPageIndex + 1} / {TotalPages}{' '}
                      </a>

                      <a href="#" onClick={NextPage()}>
                        <img
                          className="imgNext"
                          title="Next"
                          border="0"
                          width="18px"
                          height="18px"
                        ></img>
                      </a>
                    </div>
                  )}
                </td>
              </tr>
            </tfoot>
          </table>
        </React.Fragment>
      )}
      <div>
        <button onClick={() => setCounter(prev => prev + 1)}>Mostrar totalRows</button>
      </div>
    </div>
  );
};

export default RGridTest;


This is the parrent, were the button that calls the API is pressed


import './Css/App.css';
import 'bootstrap/dist/css/bootstrap.css';
import GrillaCompleta from './Components/GrillaCompleta';
import {BrowserRouter as Router, Switch, Route, Link} from 'react-router-dom';
import RGrid from './Components/RGrid';
import RGridTest from './Components/RGridTest';
import {ListAll} from './Components/Helpers';
import React, {useState} from 'react';
import TestSocialReducer from './Components/TestSocialReducer';

function App() {
  const [Dogs, setDogs] = useState([]);
  const [isCargando, setIsCargando] = useState(false);

  function FindDogs() {
    setIsCargando(true);
    ListAll().then(lDog => {
      setDogs(lDog);
      setIsCargando(false);
    });
  }

  /*
  useEffect(() => {

  }, []);
  */

  function Topics() {
    return (
      <div>
        <h2>Topic</h2>
      </div>
    );
  }

  const GrillaConfiguracion = [
    {
      Titulo: 'Nombre',
      Selector: fila => fila.name,
      WidthColumn: '30%',
      Ordenable: true,
    },
    {
      Titulo: 'Tamaño',
      Selector: fila => fila.breed_group,
      WidthColumn: '30%',
    },
  ];

  return (
    <Router>
      <div>
        <ul>
          <li key="home">
            <Link to="/">Home</Link>
          </li>
          <li key="GrillaCompleta">
            <Link to="/GrillaCompletaPrueba"> Grilla Completa Dog Test </Link>
          </li>
          <li key="Topic">
            <Link to="/topics">Topics</Link>
          </li>
          <li key="RGrilla">
            <Link to="/RGrilla">RGrilla</Link>
          </li>
          <li key="GrillaTest">
            <Link to="/RGrillaTest">RGrillaTest</Link>
          </li>
          <li key="Reducer Test">
            <Link to="/TestSocialReducer">TestSocialReducer</Link>
          </li>
        </ul>

        <Switch>
          <Route path="/rGrilla">
            <button key="btnFind" id="btnFind" onClick={FindDogs}>
              Ver Perros
            </button>
            <br></br>
            <RGrid
              key="RGrid1"
              Tittle="Grilla Dogs"
              rows={Dogs}
              columns={GrillaConfiguracion}
              ShowDelete
              Export
              DeleteId={id => console.log(id)}
              isLoading={isCargando}
            />
          </Route>
          <Route path="/rGrillaTest">
            <button key="btnFindTest" id="btnFindTest" onClick={FindDogs}>
              Ver Perros Test
            </button>
            <br></br>
            <RGridTest
              key="RGridTest"
              Tittle="Grilla Dogs Test"
              rows={Dogs}
              columns={GrillaConfiguracion}
              ShowDelete="true"
              Export="true"
              DeleteId={id => console.log(id)}
              isLoading={isCargando}
              ConfigurationId="id"
            />
          </Route>

          <Route path="/GrillaCompletaPrueba">
            <GrillaCompleta></GrillaCompleta>
          </Route>
          <Route path="/topics">
            <Topics />
          </Route>
          <Route path="/TestSocialReducer">
            <TestSocialReducer></TestSocialReducer>
          </Route>
          <Route path="/">
            <h2>Home</h2>
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

export default App;



Here the problematic component it's called


          <Route path="/rGrillaTest">
            <button key="btnFindTest" id="btnFindTest" onClick={FindDogs}>
              Ver Perros Test
            </button>
            <br></br>
            <RGridTest
              key="RGridTest"
              Tittle="Grilla Dogs Test"
              rows={Dogs}
              columns={GrillaConfiguracion}
              ShowDelete="true"
              Export="true"
              DeleteId={id => console.log(id)}
              isLoading={isCargando}
              ConfigurationId="id"
            />
          </Route>

It is what it is, there's no more than that, no redux, no external packages, nothing.

I hope I was clear and thank you for your next answers

Nahuel M.
  • 146
  • 8
  • i'm not sure but i think if you delete props.rows from second parameter of the first useEffect it will be ok. – Ali Navidi Feb 14 '22 at 21:27
  • 1
    Well, not really, because tath parameter lets me know when the rows property changes. Remeber that initialy that props is undefined, then at some point the parrent (App) sends the data through that prop – Nahuel M. Feb 14 '22 at 23:13
  • 1
    yeah i thought that the rows change a lot and this makes it an infinite loop, my bad. – Ali Navidi Feb 15 '22 at 06:44

1 Answers1

0

Well, the error was such a pain in the **s but it was quite simple, in the JSX of RgridTest you can see that there is a button that calls a callback right there

<a href="#" onClick={NextPage()}>

Nahuel M.
  • 146
  • 8