React Hook useMemo has a missing dependency: 'handleClearData' - reactjs

This is my code below
const Search = ({ results }) => {
const dispatch = useDispatch();
const handleClearData = () => {
dispatch(clearData());
};
const columns = useMemo(
() => [
{
Header: 'G35 Number',
accessor: 'g35Number',
Cell: props => {
const { original } = props.row;
return (
<React.Fragment>
<Link data-tip data-for={`link-${original.sNumber}`}
to={{ pathname: `/search/${original.sNumber}`, state: { s: props.row.original } }} onClick={handleClearData}>
{original.g35Number}
</Link>
</React.Fragment>
);
},
},
{
Header: 'Regn Number',
accessor: 'regNumber',
},
{
Header: 'File',
accessor: 'file',
},
{
Header: 'Details',
accessor: 'details',
}
],
[]
);
const data = useMemo(() => results, [results]); // eslint-disable-line react-hooks/exhaustive-deps
const renderTable = () => {
return (
<ReactTable columns={columns} data={data} />
);
}
return (
<div className="card py-3 px-4">
{
results?.length ?
renderTable() :
<div>No results.</div>
}
</div>
);
}export default Search;
I am getting the below warning:
React Hook useMemo has a missing dependency: 'handleClearData'. Either include it or remove the dependency array
I tried to add handleClearData in the dependency array of the useMemo, which gave me the below warning:
The 'handleClearData' function makes the dependencies of useMemo Hook change on every render. Move it inside the useMemo callback. Alternatively, wrap the 'handleClearData' definition into its own useCallback() Hook
I did not understand what it meant when it said that I need to wrap it in its own useCallback() hook.
Can anyone help me what I am missing? I am not sure if I want to add anything in the dependency array if I just want to load it only for the first time (That if it works the same way in useEffect).

I was able to do resolve the warning by moving the function inside useMemo like below:
{
Header: 'G35 Number',
accessor: 'g35Number',
Cell: props => {
const { original } = props.row;
const dispatch = useDispatch();
const handleClearData = () => {
dispatch(clearData());
};
return (
<React.Fragment>
<Link data-tip data-for={`link-${original.sNumber}`}
to={{ pathname: `/search/${original.sNumber}`, state: { s: props.row.original } }} onClick={handleClearData}>
{original.g35Number}
</Link>
</React.Fragment>
);
},
}

Related

ReactTable component is not rendering data coming from api,If i use hardcoded data it is showing

I am using reacttable-6 to render the data in table,but the is not showing up in table.It is giving me "No data found".Data is coming from api,even i console logged the response from api,the what data i am getting from is fine.I aslo hard coded the data,then the data is showing up in the table
I am not able to figure out what the issue is.Thanks inadvance.
import React,{useState} from 'react'
import ReactTable from "react-table-6";
import 'react-table-6/react-table.css';
import axios from "axios";
export default function Inventory(){
var compdata = [
{
FirstName: "Chandu",
LastName: "Reddy",
_id: "63c5766f9d9de1b624481574",
Discription: {
_id: "63c5766f9d9de1b624481574",
DOB: "Havells",
Age: "HiBreak",
},
Address: "Something",
}
];
const handleDelete=(data)=>{
console.log(data)
}
const handleEdit=()=>{
console.log("dcdc")
}
const column = [
{
Header: "FirstName",
accessor: "FirstName",
sortable: false
},
{
Header: "LastName",
accessor: "LastName",
sortable: false
},
{
Header: "Address",
accessor: "Address",
sortable: false
},
{
Header: "Actions",
Cell: (row) => (
<div>
<a onClick={() => handleEdit(row.original)}>
Edit
</a>
<a onClick={() => handleDelete(row.original)}>
Delete
</a>
</div>
)
}
];
const [data1, setData1] = useState([]);
const [columns, setColumns] = useState(column);
const { toggle } = useContext(ThemeContext);
useEffect(()=>{
const fetch = async () => {
await axios
.get(`http://localhost:4001/api/uploadCsv/getData`)
.then((res) => {
setData1(res.data);
console.log(res.data)
});
};
fetch();
},[setData1]);
const [expanded, setExpanded] = useState({});
const onExpandedChange = (newExpanded) => {
setExpanded(newExpanded);
};
return(
<div className="container">
<div className='Table-container'>
<ReactTable
data={data1}
columns={columns}
defaultPageSize={data1.length}
showPagination={false}
resizable={false}
expanded={expanded}
// className="-striped -highlight"
getTrProps={(state, rowInfo, column, instance, expanded) => {
return rowInfo
? {
onClick: (expanded) => {
const newExpanded = { ...expanded };
newExpanded[rowInfo.viewIndex] = expanded[rowInfo.viewIndex]
? false
: true;
setExpanded(newExpanded);
}
}
: {};
}}
SubComponent={(row) => {
return (
<div style={{ padding: "20px" }}>
<em>{(row.original.Discription.Make)}</em>K <br />
</div>
);
}}
/>
<br />
</div>
</div>
)
}
Screen shot for reference
Change your useEffect like this and add another to check did data1 updated yet. Depend on data1 have value or not we will have different way to solve this
useEffect(() => {
axios.get(`http://localhost:4001/api/uploadCsv/getData`).then((res) => {
setData1(res.data);
});
}, []);
useEffect(()=>{
console.log(data1)
},[data1])

useCallback and memoization

How the memorized callback function works? In some articles I read that the function is recreated if we do not use useCallback. But if it is recreated, should it be different from the prev version? In my code I didn't notice that there was a difference in callback functions.
My question is: Why in both cases, my set size is 1?
from off doc useCallback
Returns a memoized callback.
Pass an inline callback and an array of dependencies. useCallback will
return a memoized version of the callback that only changes if one of
the dependencies has changed. This is useful when passing callbacks to
optimized child components that rely on reference equality to prevent
unnecessary renders (e.g. shouldComponentUpdate).
import { useCallback } from "react";
const dataSource = [
{
id: 1,
model: "Honda",
color: "red",
},
{
id: 2,
model: "Mazda",
color: "yellow",
},
{
id: 3,
model: "Toyota",
color: "green",
},
];
const Car = ({ model, color, set, onCarClick }) => {
const onClick = () => onCarClick(model, color);
set.add(onCarClick);
console.log(set.size);
return (
<div onClick={onClick}>
Model: {model} Color: {color}
</div>
);
};
const CarsCallback = ({ cars, set }) => {
const onCarClick = (model, color) => {
console.log(model, color);
};
console.log("CarsCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
set={set}
{...car}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
const CarsUseCallback = ({ cars, set }) => {
const onCarClick = useCallback((model, color) => {
console.log(model, color);
}, []);
console.log("CarsUseCallback");
return (
<>
{cars.map((car) => {
return (
<Car
key={car.id}
{...car}
set={set}
onCarClick={onCarClick}
/>
);
})}
</>
);
};
export default function App() {
return (
<div className="App">
<CarsCallback cars={dataSource} set={new Set()} />
<CarsUseCallback cars={dataSource} set={new Set()} />
</div>
);
}
Because CarsUseCallback and CarsCallback was triggered once.
We can see the only one log of CarsUseCallback and CarsCallback.
If we re-render the CarsUseCallback and CarsCallback, we can see the size is 1 and 2.
const CarsCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
};
const CarsUseCallback = ({ cars, set }) => {
const [count, setCount] = useState(1);
console.log('CarsUseCallback');
useEffect(() => {
setCount(2);
}, []);
// ...
}

How to make data persist on refresh React JS?

I have a code where I mount a table with some firebase data but for some reason the values disappear and I been struggling for the next 2 weeks trying to solve this issue I haven't found a solution to this and I have asked twice already and I have try everything so far but it keeps disappearing.
Important Update
I just want to clarify the following apparently I was wrong the issue wasn't because it was a nested collection as someone mentioned in another question. The issue is because my "user" is getting lost in the process when I refresh.
I bring the user from the login to the app like this:
<Estudiantes user={user} />
and then I receive it as a props
function ListadoPedidos({user})
but is getting lost and because is getting lost when I try to use my firebase as:
estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")
since the user is "lost" then the uid will be null. Since is null it will never reach the collection and the docs.
I have a simple solution for you. Simply raise the parsing of localStorage up one level, passing the preloadedState into your component as a prop, and then using that to initialize your state variable.
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
Then initialize state with the prop
const initialState = props.preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
And finally, update the useEffect hook to persist state any time it changes.
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
Full Code
import React, { useState, useEffect } from 'react';
import { db } from './firebase';
import { useHistory } from 'react-router-dom';
import './ListadoEstudiantes.css';
import {
DataGrid,
GridToolbarContainer,
GridToolbarFilterButton,
GridToolbarDensitySelector,
} from '#mui/x-data-grid';
import { Button, Container } from '#material-ui/core';
import { IconButton } from '#mui/material';
import PersonAddIcon from '#mui/icons-material/PersonAddSharp';
import ShoppingCartSharpIcon from '#mui/icons-material/ShoppingCartSharp';
import DeleteOutlinedIcon from '#mui/icons-material/DeleteOutlined';
import { Box } from '#mui/system';
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
const Listado = ({ user, preloadedState }) => {
const history = useHistory('');
const crearEstudiante = () => {
history.push('/Crear_Estudiante');
};
const initialState = preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
const parseData = {
pathname: '/Crear_Pedidos',
data: estudiantesData,
};
const realizarPedidos = () => {
if (estudiantesData == 0) {
window.alert('Seleccione al menos un estudiante');
} else {
history.push(parseData);
}
};
function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarFilterButton />
<GridToolbarDensitySelector />
</GridToolbarContainer>
);
}
const [estudiantes, setEstudiantes] = useState([]);
const [selectionModel, setSelectionModel] = useState([]);
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{ field: 'nombre', headerName: 'Nombre', width: 200 },
{ field: 'colegio', headerName: 'Colegio', width: 250 },
{ field: 'grado', headerName: 'Grado', width: 150 },
{
field: 'delete',
width: 75,
sortable: false,
disableColumnMenu: true,
renderHeader: () => {
return (
<IconButton
onClick={() => {
const selectedIDs = new Set(selectionModel);
estudiantes
.filter((x) => selectedIDs.has(x.id))
.map((x) => {
db.collection('usuarios')
.doc(user.uid)
.collection('estudiantes')
.doc(x.uid)
.delete();
});
}}
>
<DeleteOutlinedIcon />
</IconButton>
);
},
},
];
const deleteProduct = (estudiante) => {
if (window.confirm('Quiere borrar este estudiante ?')) {
db.collection('usuarios').doc(user.uid).collection('estudiantes').doc(estudiante).delete();
}
};
useEffect(() => {}, [estudiantesData]);
const estudiantesRef = db.collection('usuarios').doc(user.uid).collection('estudiantes');
useEffect(() => {
estudiantesRef.onSnapshot((snapshot) => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes);
});
}, []);
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
return (
<Container fixed>
<Box mb={5} pt={2} sx={{ textAlign: 'center' }}>
<Button
startIcon={<PersonAddIcon />}
variant="contained"
color="primary"
size="medium"
onClick={crearEstudiante}
>
Crear Estudiantes
</Button>
<Box pl={25} pt={2} mb={2} sx={{ height: '390px', width: '850px', textAlign: 'center' }}>
<DataGrid
rows={estudiantes}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
components={{
Toolbar: CustomToolbar,
}}
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange={(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = estudiantes.filter((row) => selectedIDs.has(row.id));
setEstudiantesData(selectedRowData);
}}
{...estudiantes}
/>
</Box>
<Button
startIcon={<ShoppingCartSharpIcon />}
variant="contained"
color="primary"
size="medium"
onClick={realizarPedidos}
>
Crear pedido
</Button>
</Box>
</Container>
);
};
I suspect that it's because this useEffect does not have a dependency array and is bring run on every render.
useEffect (() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
})
Try adding a dependency array as follows:
useEffect (() => {
if (estudiantes && estudiantes.length>0)
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
},[estudiantes])
This will still set the localStorage to [] when it runs on the first render. But when the data is fetched and estudiantes is set, the localStorage value will be updated. So I've added a check to check if it's not the empty array.
Change the dependency array of this useEffect to []:
estudiantesRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes)
})
}, []);
The data flow in your code is somewhat contradictory, so I modify your code, and it works fine.
You can also try delete or add button, it will modify firebase collection, then update local data.
You can click refresh button in codesandbox previewer (not browser) to observe the status of data update.
Here is the code fargment :
// Set value of `localStorage` to component state if it exist.
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("estudiantes");
localStorageEstData && setEstudiantes(JSON.parse(localStorageEstData));
}, []);
// Sync remote data from firebase to local component data state.
useEffect(() => {
// Subscribe onSnapshot
const unSubscribe = onSnapshot(
collection(db, "usuarios", user.id, "estudiantes"),
(snapshot) => {
const remoteDataSource = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
console.info(remoteDataSource);
setEstudiantes(remoteDataSource);
}
);
return () => {
//unSubscribe when component unmount.
unSubscribe();
};
}, [user.id]);
// when `estudiantes` state update, `localStorage` will update too.
useEffect(() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes));
}, [estudiantes]);
Here is the full code sample :
Hope to help you :)

React Hook useEffect has a missing dependency. Either include it or remove the dependency array react-hooks/exhaustive-deps

If i add the dependency array "fitems" in the dependecy array like its telling me to do, then it causes infinite loop. also if i dont use the spread operator on the array then the warning doesnt show but then the state change doesnt rerender.
Sidebar.tsx
import { useState, useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import axios from "axios";
import getItems from "./../services/SidebarItems";
import { sidebarInfoUrl } from "./../services/ApiLinks";
function Sidebar() {
const fItems = getItems();
const location = useLocation();
const paths = location.pathname.split("/");
const [items, setItems] = useState(fItems);
useEffect(() => {
axios.get(sidebarInfoUrl).then((response) => {
const updatedItems = [...fItems]
updatedItems.forEach((item) => {
if (item.match === "projects") item.value = response.data.projects;
else if (item.match === "contacts") item.value = response.data.contacts;
});
setItems(updatedItems);
console.log("here")
});
}, []);
return (
<div className="sidebar shadow">
{items &&
items.map((item) => (
<Link
key={item.match}
to={item.link}
className={
paths[2] === item.match ? "sidebar-item active" : "sidebar-item"
}
>
<span>
<i className={item.icon}></i> {item.title}
</span>
{item.value && <div className="pill">{item.value}</div>}
</Link>
))}
</div>
);
}
export default Sidebar;
Here is the sidebar items i am getting from getItems().
sidebarItems.ts
const items = () => {
return [
{
title: "Dashboard",
icon: "fas fa-home",
link: "/admin/dashboard",
match: "dashboard",
value: "",
},
{
title: "Main Page",
icon: "fas fa-star",
link: "/admin/main-page",
match: "main-page",
value: "",
},
{
title: "Projects",
icon: "fab fa-product-hunt",
link: "/admin/projects",
match: "projects",
value: "00",
},
{
title: "Contacts",
icon: "fas fa-envelope",
link: "/admin/contacts",
match: "contacts",
value: "00",
},
];
};
export default items;
Thank to AKX. I found my problem. I had to use useMemo Hook so that my getItem() function doesnt cause infinte loop when i add it to dependency array.
const fItems = useMemo(() => {
return getItems();
}, []);
instead of
const fItems = getItems();
Another fix is that,
If i dont send the items from SidebarItems.ts as function but as an array then it wont cause the infinte loop even if i dont use useMemo hook.

React.js: How to call hook first before first render?

I'm trying to create a dynamic menu using React
I have a JSON response which contains how my menu should look like:
[
{ id: 0,
label: "Dashboard",
link: "/app/dashboard",
icon: <HomeIcon /> },
{
id: 1,
label: "Inward",
link: "/app/inward",
icon: <InboxIcon />,
children: [
{ label: "PESONet", link: "/app/inward/pesonet" },
{ label: "PESONet Inquiry", link: "/app/inward/pesonetinquiry" },
{ label: "PDDTS", link: "/app/inward/pddts" },
{ label: "SWIFT", link: "/app/inward/swift" },
{ label: "Philpass", link: "/app/inward/philpass" },
],
}
]
I'm able to put this JSON response in the state with this:
Sidebar.js
const [sideBar, setSideBar] = useState([])
useEffect(() => {
const sidebar = customizeSidebar()
setSideBar(sidebar)
}, [])
The function customizeSidebar() can be found in my context:
UserContext.js
function customizeSidebar(dispatch, profileId, history){
ProfileMaintenanceService.retrieveSideMenu()
.then((response) => {
return response.data
}).catch((err) => {
// check first if api is down
})
}
As you can see, whenever I get a response, I return it as well.
Therefore, I can get it in the Sidebar.js.
However, problem arises when render happens first before the useEffect function.
const [sideBar, setSideBar] = useState([])
useEffect(() => {
const sidebar = customizeSidebar()
setSideBar(sidebar)
}, [])
return (
<List className={classes.sidebarList}>
{sideBar.map(link => (
<SidebarLink
key={link.id}
location={location}
isSidebarOpened={isSidebarOpened}
{...link}
/>
))}
</List>
)
Already tried using useLayoutEffect but render still happens first before my API call and assigning to state.
Is there any way I can do first my API call and assign to state before the first render?
Thank you for those who would help.
Either you return a placeholder is there is no sidebar data:
if (!sideBar.length) {
return (
<div className="sidebar-placeholder">
<Spinner/> || Loading text...
</div>
);
}
return (
<List>...
);
Or you wrap your Sidebar inside a provider component, and pass your sidebar configuration as a prop.
the best imo is to do something like this
return (
{sideBar.length&&<List className={classes.sidebarList}>
{sideBar.map(link => (
<SidebarLink
key={link.id}
location={location}
isSidebarOpened={isSidebarOpened}
{...link}
/>
))}
</List>}
)
So your sidebar will be rendered only when the sidebar data array is full.

Resources