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
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
.
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.