Home > Back-end >  UseEffect and useState problem with filter (posisible render loop)
UseEffect and useState problem with filter (posisible render loop)

Time:07-10

I'm making a small code and my code was working before. I'm trying to separate my functions from my views, but when I do it, there's something that escapes me and I can't find the solution.

This is my old code:

views/letter.js:

import React, {useState, useEffect} from 'react'

import {
  CButton,
  CCol,
  CRow,
  CImage,
  CContainer,
  CTableRow,
  CTableHead,
  CTableBody,
  CTable,
  CTableHeaderCell,
  CTableDataCell,
  CModal,
  CModalHeader,
  CFormLabel,
  CModalTitle,
  CModalBody,
  CForm,
  CFormInput,
  CFormSelect,
  CNav,
  CNavItem,
  CNavLink,
  CTabPane,
  CTabContent,
} from '@coreui/react'

import { useNavigate } from 'react-router-dom';
import { MultiSelect } from 'react-multi-select-component';
import axios from "axios";

const options = [
  { label: "Pescado", value: "pescado" },
  { label: "Frutos secos", value: "frutosSecos" },
  { label: "Lacteos", value: "lacteos" },
  { label: "Moluscos", value: "moluscos" },
  { label: "Cereales con gluten", value: "gluten" },
  { label: "Crustáceos", value: "crustaceos" },
  { label: "Huevos", value: "huevos" },
  { label: "Cacahuetes", value: "cacahuetes" },
  { label: "Soja", value: "soja" },
  { label: "Apio", value: "apio" },
  { label: "Mostaza", value: "mostaza" },
  { label: "Sésamo", value: "sesamo" },
  { label: "Altramuces", value: "altramuces" },
  { label: "Sulfitos", value: "sulfitos" },
  { label: "Ninguno", value: "ninguno" },
];


const Carta = () => {
  
  const [products, setProducts] = useState([]);
  const [product, setProduct] = useState([]);
  const [sections, setSections] = useState([]);
  const [selected, setSelected] = useState([]);
  const [activeKey, setActiveKey] = useState(1)
  const navigate = useNavigate();
  const [visible, setVisible] = useState(false)
  const [validated, setValidated] = useState(false)
  const [file, setFile] = useState();
  const [fileName, setFileName] = useState("");
  const [msg, setMsg] = useState('');
  const [visibleModify, setVisibleModify] = useState(false)
  
  useEffect(() => {
      getProducts();
      getSections();
  }, []);


  const getProducts = async () => {
    const response = await axios.get('http://192.168.1.50:9000/getProducts', {
    });
    setProducts(response.data);
    console.log(response.data)
  }

  const getProduct = async (productID) => {
    const response = await axios.post('http://192.168.1.50:9000/getProduct', {
      id: productID,
    });
    setProduct(response.data);
    console.log(response.data)
  }

  const getSections = async () => {
    const response = await axios.get('http://192.168.1.50:9000/getSections', {
    });
    setSections(response.data);
    console.log(response.data)
  }

  const saveFile = (e) => {
    setFile(e.target.files[0]);
    setFileName(e.target.files[0].name);
  };  

  function handlerButton(productID) {
    setVisibleModify(!visibleModify);
    getProduct(productID);
  }

  const deleteProduct = async (e) => {
    try {
      await axios.post('http://192.168.1.50:9000/deleteProduct', {
        id: e.currentTarget.id,
      });
      window.location.reload();
  } catch (error) {
      if (error.response) {
          setMsg(error.response.data.msg);
      }
  }
  }

  const modifyProduct = async (e, productID) => {
    const form = e.currentTarget

    if (form.checkValidity() === false) {
      e.preventDefault()
      e.stopPropagation()
    }
    setValidated(true)

    e.preventDefault();

    const formData = new FormData();
    formData.append("file", file);
    formData.append("fileName", fileName);
    console.log(formData.get("fileName"))

    try {
      const res = await axios.post(
        "http://192.168.1.50:9000/uploadImg",
        formData
      );
      console.log(res);
    } catch (ex) {
      console.log(ex);
    }

    try {
      await axios.post('http://192.168.1.50:9000/modifyProduct', {
          id: productID,
          name: productName.value,
          description: descp.value,
          price: price.value,
          allergens: JSON.stringify(selected),
          img:formData.get("fileName"),
          section: section.value
      });
      window.location.reload();
  } catch (error) {
      if (error.response) {
          setMsg(error.response.data.msg);
      }
  }
  }

  const addProduct = async (e) => {
    const form = e.currentTarget

    if (form.checkValidity() === false) {
      e.preventDefault()
      e.stopPropagation()
    }
    setValidated(true)

    e.preventDefault();


    //////////////
    const formData = new FormData();
    formData.append("file", file);
    formData.append("fileName", fileName);
    console.log(formData.get("fileName"))
    //////////////

    try {
      const res = await axios.post(
        "http://192.168.1.50:9000/uploadImg",
        formData
      );
      console.log(res);
    } catch (ex) {
      console.log(ex);
    }



    try {
        await axios.post('http://192.168.1.50:9000/addProduct', {
          name: productName.value,
          description: descp.value,
          price: price.value,
          allergens: JSON.stringify(selected),
          img:formData.get("fileName"),
          section: section.value
        });
        window.location.reload();
    } catch (error) {
        if (error.response) {
            setMsg(error.response.data.msg);
        }
    }
}


  return (
    <>
    <CContainer >
        <CNav className="justify-content-center mb-4">
          {sections.map((section,index) => {
          return (
          <CNavItem key={index}>
            <CNavLink style={{color: "black"}}
            href="javascript:void(0);"
            active={activeKey === section.id}
            onClick={() => setActiveKey(section.id)}>
              {section.name}
            </CNavLink>
          </CNavItem>
          )})}
        </CNav>
        <CTabContent>
        
        {sections.map((section,index) => {
          return (
            
            <CTabPane key={section.id} role="tabpanel" visible={activeKey === section.id}>
              <CRow>
          <CContainer fluid>
        <CTable>
              <CTableHead>
                <CTableRow>
                  <CTableHeaderCell scope="col">Producto</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Descripción</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Precio</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Alergenos</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Foto</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Acciones</CTableHeaderCell>
                </CTableRow>
              </CTableHead>
              <CTableBody>
              {products.filter(product => product.section == section.id).map((product,index) => {
                var allergens = JSON.parse(product.allergens)
                return (
                  <CTableRow key={product.id}>
                    <CTableDataCell>{product.name}</CTableDataCell>
                    <CTableDataCell>{product.description}</CTableDataCell>
                    <CTableDataCell>{product.price} €</CTableDataCell>
                    <CTableDataCell> 
                      {
                        <div>
                          {allergens.map(p => {
                            return p.label   "  "; 
                          })
                          }
                          </div>                    
                      }
                    </CTableDataCell>
                    <CTableDataCell> 
                      <CImage fluid className="clearfix" src={"http://192.168.1.50:9000/public/images/"   product.img} width={200} height={200}/>
                    </CTableDataCell>
                    <CTableDataCell>
                      <CButton id={product.id} style={{backgroundColor: "#3a8cbe", borderColor: "#3a8cbe"}} onClick={() => handlerButton(product.id)}>Editar</CButton>
                      <p></p>
                      <CButton id={product.id} style={{backgroundColor: "#e8463a", borderColor: "#e8463a"}} onClick={deleteProduct}>Eliminar</CButton>
                    </CTableDataCell>
                  </CTableRow>
                )
            })}
            </CTableBody>
          </CTable>
          </CContainer>
          </CRow>
            </CTabPane>
            
          )})}
          
    </CTabContent>
        <CRow>
          <CContainer fluid>
          <CButton className="mb-4 d-grid mx-auto" color="secondary" style={{color:"white"}} onClick={() => setVisible(!visible)}>Añadir producto</CButton>
          <CModal alignment="center" visible={visible} onClose={() => setVisible(false)}>
            <CModalHeader onClose={() => setVisible(false)}>
              <CModalTitle>Añadir producto</CModalTitle>
            </CModalHeader>
            <CModalBody>
            <CForm className="mb-4"
                  validated={validated}
                  onSubmit={addProduct}>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Nombre</CFormLabel>
                    <CCol sm={10} >
                      <CFormInput type="text" id="productName" placeholder="Nombre del producto" pattern="^[a-zA-Z ()]*$"  title="Solo puedes introducir letras a-Z, parentesis o espacios" required/>
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Descp.</CFormLabel>
                    <CCol sm={10} >
                      <CFormInput type="text" id="descp" placeholder="Descripción"/>
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Precio</CFormLabel>
                    <CCol sm={10} >
                      <CFormInput type="text" id="price" placeholder="Precio" pattern="[ -]?\d (?:[.,]\d )?" required/>
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Alergenos</CFormLabel>
                    <CCol sm={10} >
                      <MultiSelect
                        options={options}
                        value={selected}
                        onChange={setSelected}
                        labelledBy="Seleccione los alergenos"
                      />
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                  <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Sección</CFormLabel>
                    <CCol sm={10} >
                      <CFormSelect id="section" required>
                        <option>Escoja una sección</option>
                        {sections.map((section,index) => {
                        return (
                          <>
                          <option>{section.name}</option>
                          </>
                        )})}
                      </CFormSelect> 
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Foto</CFormLabel>
                    <CCol sm={10} >
                    <CFormInput type="file" onChange={saveFile} enctype="multipart/form-data" required/>
                    </CCol>
                  </CRow>
                  <CButton className='className="mb-4 d-grid gap-2 col-6 mx-auto' type="submit" color="secondary" style={{color:"white"}}>Añadir</CButton>
            </CForm>
            </CModalBody>
          
          </CModal>
          <CModal alignment="center" visible={visibleModify} onClose={() => setVisibleModify(false)}>
            <CModalHeader onClose={() => setVisibleModify(false)}>
              <CModalTitle>Modificar producto</CModalTitle>
            </CModalHeader>
            <CModalBody>
            <CForm className="mb-4"
                  validated={validated}
                  onSubmit={(e) => modifyProduct(e,product.id)}>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Nombre</CFormLabel>
                    <CCol sm={10} >
                      <CFormInput type="text" id="productName" defaultValue={product.name} pattern="^[a-zA-Z ()]*$"  title="Solo puedes introducir letras a-Z, parentesis o espacios" required/>
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Descp.</CFormLabel>
                    <CCol sm={10} >
                      <CFormInput type="text" id="descp" defaultValue={product.description}/>
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Precio</CFormLabel>
                    <CCol sm={10} >
                      <CFormInput type="text" id="price" defaultValue={product.price} pattern="[ -]?\d (?:[.,]\d )?" required/>
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Alergenos</CFormLabel>
                    <CCol sm={10} >
                      <MultiSelect
                        options={options}
                        value={selected}
                        onChange={setSelected}
                        labelledBy="Seleccione los alergenos"
                      />
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                  <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Sección</CFormLabel>
                    <CCol sm={10} >
                      <CFormSelect id="section" required>
                      <option>Escoja una sección</option>
                      {sections.map((section,index) => {
                        return (
                          <>
                          <option>{section.name}</option>
                          </>
                        )})}
                      </CFormSelect> 
                    </CCol>
                  </CRow>
                  <CRow className="mb-3">
                    <CFormLabel htmlFor="colFormLabel" className="col-sm-2 col-form-label">Foto</CFormLabel>
                    <CCol sm={10} >
                    <CFormInput type="file" onChange={saveFile} enctype="multipart/form-data" required/>
                    </CCol>
                  </CRow>
                  <CButton className='className="mb-4 d-grid gap-2 col-6 mx-auto' type="submit" color="secondary" style={{color:"white"}}>Modificar</CButton>
            </CForm>
            </CModalBody>
          </CModal>
          </CContainer>
        </CRow>
    </CContainer>
    </>
  )
}

export default Carta

Inside the view I use a function to call the backend to retrieve an array of products. This way it always worked and retrieved the products.

Now what I want to do is to separate the code of those functions to a different .js file but I can't get the setState to work, because it gives me a problem in the filter.

And here is the new code:

import React, {useState, useEffect} from 'react'

import {
  CButton,
  CCol,
  CRow,
  CImage,
  CContainer,
  CTableRow,
  CTableHead,
  CTableBody,
  CTable,
  CTableHeaderCell,
  CTableDataCell,
  CModal,
  CModalHeader,
  CFormLabel,
  CModalTitle,
  CModalBody,
  CForm,
  CFormInput,
  CFormSelect,
  CNav,
  CNavItem,
  CNavLink,
  CTabPane,
  CTabContent,
} from '@coreui/react'

import { useNavigate } from 'react-router-dom';
import { MultiSelect } from 'react-multi-select-component';
import axios from "axios";
import {getProducts} from "../../services/Product.js"

const options = [
  { label: "Pescado", value: "pescado" },
  { label: "Frutos secos", value: "frutosSecos" },
  { label: "Lacteos", value: "lacteos" },
  { label: "Moluscos", value: "moluscos" },
  { label: "Cereales con gluten", value: "gluten" },
  { label: "Crustáceos", value: "crustaceos" },
  { label: "Huevos", value: "huevos" },
  { label: "Cacahuetes", value: "cacahuetes" },
  { label: "Soja", value: "soja" },
  { label: "Apio", value: "apio" },
  { label: "Mostaza", value: "mostaza" },
  { label: "Sésamo", value: "sesamo" },
  { label: "Altramuces", value: "altramuces" },
  { label: "Sulfitos", value: "sulfitos" },
  { label: "Ninguno", value: "ninguno" },
];


const Carta = () => {
  
  const [products, setProducts] = useState([]);
  const [product, setProduct] = useState([]);
  const [sections, setSections] = useState([]);
  const [selected, setSelected] = useState([]);
  const [activeKey, setActiveKey] = useState(1)
  const navigate = useNavigate();
  const [visible, setVisible] = useState(false)
  const [validated, setValidated] = useState(false)
  const [file, setFile] = useState();
  const [fileName, setFileName] = useState("");
  const [msg, setMsg] = useState('');
  const [visibleModify, setVisibleModify] = useState(false)
  
  useEffect(() => {
      setProducts(getProducts());
      getSections();
  }, []);


/*   const getProducts = async () => {
    const response = await axios.get('http://192.168.1.50:9000/getProducts', {
    });
    setProducts(response.data);
    console.log(response.data)
  } */
   //setProducts(getProducts());
   console.log(products)

  const getProduct = async (productID) => {
    const response = await axios.post('http://192.168.1.50:9000/getProduct', {
      id: productID,
    });
    setProduct(response.data);
    console.log(response.data)
  }

  const getSections = async () => {
    const response = await axios.get('http://192.168.1.50:9000/getSections', {
    });
    setSections(response.data);
    console.log(response.data)
  }

  const saveFile = (e) => {
    setFile(e.target.files[0]);
    setFileName(e.target.files[0].name);
  };  

  function handlerButton(productID) {
    setVisibleModify(!visibleModify);
    getProduct(productID);
  }

  const deleteProduct = async (e) => {
    try {
      await axios.post('http://192.168.1.50:9000/deleteProduct', {
        id: e.currentTarget.id,
      });
      window.location.reload();
  } catch (error) {
      if (error.response) {
          setMsg(error.response.data.msg);
      }
  }
  }

  const modifyProduct = async (e, productID) => {
    const form = e.currentTarget

    if (form.checkValidity() === false) {
      e.preventDefault()
      e.stopPropagation()
    }
    setValidated(true)

    e.preventDefault();

    const formData = new FormData();
    formData.append("file", file);
    formData.append("fileName", fileName);
    console.log(formData.get("fileName"))

    try {
      const res = await axios.post(
        "http://192.168.1.50:9000/uploadImg",
        formData
      );
      console.log(res);
    } catch (ex) {
      console.log(ex);
    }

    try {
      await axios.post('http://192.168.1.50:9000/modifyProduct', {
          id: productID,
          name: productName.value,
          description: descp.value,
          price: price.value,
          allergens: JSON.stringify(selected),
          img:formData.get("fileName"),
          section: section.value
      });
      window.location.reload();
  } catch (error) {
      if (error.response) {
          setMsg(error.response.data.msg);
      }
  }
  }

  const addProduct = async (e) => {
    const form = e.currentTarget

    if (form.checkValidity() === false) {
      e.preventDefault()
      e.stopPropagation()
    }
    setValidated(true)

    e.preventDefault();


    //////////////
    const formData = new FormData();
    formData.append("file", file);
    formData.append("fileName", fileName);
    console.log(formData.get("fileName"))
    //////////////

    try {
      const res = await axios.post(
        "http://192.168.1.50:9000/uploadImg",
        formData
      );
      console.log(res);
    } catch (ex) {
      console.log(ex);
    }



    try {
        await axios.post('http://192.168.1.50:9000/addProduct', {
          name: productName.value,
          description: descp.value,
          price: price.value,
          allergens: JSON.stringify(selected),
          img:formData.get("fileName"),
          section: section.value
        });
        window.location.reload();
    } catch (error) {
        if (error.response) {
            setMsg(error.response.data.msg);
        }
    }
}


  return (
    <>
    <CContainer >
        <CNav className="justify-content-center mb-4">
          {sections.map((section,index) => {
          return (
          <CNavItem key={index}>
            <CNavLink style={{color: "black"}}
            href="javascript:void(0);"
            active={activeKey === section.id}
            onClick={() => setActiveKey(section.id)}>
              {section.name}
            </CNavLink>
          </CNavItem>
          )})}
        </CNav>
        <CTabContent>
        
        {sections.map((section,index) => {
          return (
            
            <CTabPane key={section.id} role="tabpanel" visible={activeKey === section.id}>
              <CRow>
          <CContainer fluid>
        <CTable>
              <CTableHead>
                <CTableRow>
                  <CTableHeaderCell scope="col">Producto</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Descripción</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Precio</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Alergenos</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Foto</CTableHeaderCell>
                  <CTableHeaderCell scope="col">Acciones</CTableHeaderCell>
                </CTableRow>
              </CTableHead>
              <CTableBody>
              {products.filter(product => product.section == section.id).map((product,index) => {
                var allergens = JSON.parse(product.allergens)
                return (
                  <CTableRow key={product.id}>
                    <CTableDataCell>{product.name}</CTableDataCell>
                    <CTableDataCell>{product.description}</CTableDataCell>
                    <CTableDataCell>{product.price} €</CTableDataCell>
                    <CTableDataCell> 
                      {
                        <div>
                          {allergens.map(p => {
                            return p.label   "  "; 
                          })
                          }
                          </div>                    
                      }
                    </CTableDataCell>
                    <CTableDataCell> 
                      <CImage fluid className="clearfix" src={"http://192.168.1.50:9000/public/images/"   product.img} width={200} height={200}/>
                    </CTableDataCell>
                    <CTableDataCell>
                      <CButton id={product.id} style={{backgroundColor: "#3a8cbe", borderColor: "#3a8cbe"}} onClick={() => handlerButton(product.id)}>Editar</CButton>
                      <p></p>
                      <CButton id={product.id} style={{backgroundColor: "#e8463a", borderColor: "#e8463a"}} onClick={deleteProduct}>Eliminar</CButton>
                    </CTableDataCell>
                  </CTableRow>
                )
            })}
            </CTableBody>
          </CTable>
          </CContainer>
          </CRow>
            </CTabPane>
            
          )})}
          
    </CTabContent>
        <CRow>
          <CContainer fluid>
          <CButton className="mb-4 d-grid mx-auto" color="secondary" style={{color:"white"}} onClick={() => setVisible(!visible)}>Añadir producto</CButton
          </CContainer>
        </CRow>
    </CContainer>
    </>
  )
}

export default Carta

The problem is with this useEffect:

useEffect(() => {
      setProducts(getProducts());
      getSections();
  }, []);

Here is the console: https://ibb.co/DpK9dCg

CodePudding user response:

The problem is pretty obvious:

const [products, setProducts] = useState([]);

useEffect(() => {
  setProducts(getProducts()); // <- new state is undefined
  getSections();
}, []);

const getProducts = async () => { // <- it doesn't return anything
  const response = await axios.get('http://192.168.1.50:9000/getProducts', {
  });
  setProducts(response.data);
  console.log(response.data)
}

By doing setProducts(getProducts()), your products state is now undefined, therefore the error message: products.filter is not a function.

Instead

When moving data fetching functions to a stand alone file, you can pass in the setter function like this:

// fileOne.js:
export const getProducts = async (setProducts) => {  // <- set up a setter param
  const response = await axios.get('http://192.168.1.50:9000/getProducts', {
  });
  setProducts(response.data);
  console.log(response.data)
}

// fileTwo.jsx:
import {getProducts} from "fileOne"

const [products, setProducts] = useState([]);

useEffect(() => {
  getProducts(setProducts); // <- pass in setter function
  getSections();
}, []);

This way, your code will work just like your original code. Please note that, in javascript, function is an object as well, so you can pass it in.

See sandbox for simple demo

CodePudding user response:

I think it is because you are setting promise to state. Get products is an async function, so you should write something like:

const product = await getProducts()
setProducts(products)
  • Related