How to make data persist on refresh React JS? - reactjs

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 :)

Related

The user aborted a request - ReactJS

I got an issue on my javascript program
my project separate to 2 files:
backend -> NodeJS (work fine I check the routes using POSTMAN and I got the Data.
frontend -> I do get data from backend but I got an issue when I want to display all
users (that located in DB).
on the frontend I got 2 files that got the issue:
my hook -> http-hook.js:
import { useState, useCallback, useRef, useEffect } from "react";
export const useHttpClient = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState();
const actieveHttpRequests = useRef([]); //store data accross re render cycle (if the user change page before we finish the proccess with the DB)
//we use useRef and not useState because we dont wont to update the UI.
//when the component that use this hook re render
//useCallback make sure that there are no infinity loops when ever the component is rerender
const sendRequest = useCallback(
async (url, method = "GET", body = null, headers = {}) => {
setIsLoading(true);
const httpAbortCtrl = new AbortController(); //API Supported in modern browser
//current properties -> access to the data in useRef
actieveHttpRequests.current.push(httpAbortCtrl);
try {
const response = await fetch(url, {
method, //method: method
body,
headers,
signal: httpAbortCtrl.signal, //link the AbortController to this request
});
const responseData = await response.json();
actieveHttpRequests.current = actieveHttpRequests.current.filter(
(reqCtrl) => reqCtrl !== httpAbortCtrl
); //filter all the old request controller
if (!response.ok) {
//the response.ok will be true if the status code is 200
// not ok is mean we got 400 or 500
throw new Error(responseData.message); //trigger the catch block
}
setIsLoading(false);
console.log(responseData);
return responseData;
} catch (err) {
console.log("there is the error: " + err);
setError(err.message);
setIsLoading(false);
throw err;
}
},
[] //no specific dependecie so we add an empty array
);
const clearError = () => {
setError(null);
};
useEffect(() => {
//when we return a function in this first function than the return function is excecuted as a clean function
//before the next time use effect runs again or also when the component that uses useEffect unmounts
return () => {
actieveHttpRequests.current.forEach((abortCtrl) => abortCtrl.abort());
};
}, []);
return { isLoading, error, sendRequest, clearError }; //isLoading:isLoading , error:error
};
by using this hook I can send request to the backend and get my results.
You can Note that I put 2 line of code of console.log so on the image you can see that I do got the error, but than I got the results from the DataBase
on the other file UserList.js (here I wish to display my users, but the error in only on the useEffect function, I haven't try to display the data on the DataGrid, , I just try at first to get the DATA from the backend):
**
UserList.js:
**
import React, { useState, useEffect } from "react";
import "./userList.css";
import { DataGrid } from "#mui/x-data-grid";
import { DeleteOutline } from "#material-ui/icons";
import { userRows } from "../../dummyData";
import { Link } from "react-router-dom";
import ErrorModal from "../../shared/components/UIElements/ErrorModal";
import LoadingSpinner from "../../shared/components/UIElements/LoadingSpinner";
import { useHttpClient } from "../../shared/hooks/http-hook";
const UserList = () => {
const [data, setData] = useState(userRows);
const { isLoading, error, sendRequest, clearError } = useHttpClient();
const [loadedUsers, setLoadedUsers] = useState();
const handleDelete = (id) => {
setData(data.filter((item) => item.id !== id));
};
useEffect(() => {
const fetchUsers = async () => {
try {
//with fetch, the default request type is GET request
const responseData = await sendRequest(
process.env.REACT_APP_BACKEND_URL + "/users"
);
setLoadedUsers(responseData.users); //users propeties is the given value from the backend (user-controllers.js on getUsers())
} catch (err) {}
};
fetchUsers();
}, [sendRequest]);
const columns = [
{ field: "id", headerName: "ID", width: 90 },
{
field: "user",
headerName: "Username",
width: 200,
renderCell: (params) => {
return (
<div className="userListUser">
<img className="userListImg" src={params.row.avatar} alt="" />
{params.row.username}
</div>
);
},
},
{ field: "email", headerName: "Email", width: 200 },
{ field: "status", headerName: "status", width: 120 },
{ field: "transaction", headerName: "Transaction Volume", width: 160 },
{
field: "action",
headerName: "Action",
width: 150,
renderCell: (params) => {
return (
<>
<Link to={"/users/" + params.row.id}>
<button className="userListEdit">Edit</button>
</Link>
<DeleteOutline
className="userListDelete"
onClick={() => handleDelete(params.row.id)}
/>
</>
);
},
},
];
return (
<React.Fragment>
<ErrorModal error={error} onClear={clearError} />
{isLoading && (
<div className="center">
<LoadingSpinner />
</div>
)}
{/* we need to render loadedUsers only if not empty*/}
{!isLoading && loadedUsers && (
<div className="userList">
<span className="Title">User List</span>
<div style={{ display: "flex", height: "80%" }}>
<div style={{ flexGrow: 1 }}>
<DataGrid
disableSelectionOnClick
rows={data}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
checkboxSelection
/>
</div>
</div>
</div>
)}
</React.Fragment>
);
};
export default UserList;
to remind, on http-hoolk I put those 2 line of code:
on the error:
console.log("there is the error: " + err);
on the results:
console.log(responseData);
The Image of the error on the web tools:
I got no idea why but on my index.js I had this code:
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
All I had to do is remove the React.StrictMode and the error remove!
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<App />
);
Effects firing twice is an expected behavior in development mode of React 18 with StrictMode enabled.
More info.

React MUI ProgressBar not rendering on value change

I have a functional component where I am using the MUI progress bar that I want to display but when the progress bar loads its still at the progress I set at the first step.
Also I am calling an API and processing the results in one of the functions. What am I doing wrong ?
function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" {...props} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round(
props.value,
)}%`}</Typography>
</Box>
</Box>
);
}
export const Search = (props) => {
const { onSearchComplete } = props;
const [msgBox, setMsgBox] = useState(null);
const [loading, setLoading] = useState(false);
const [progress, setProgress] = useState(10);
const onSearch = async () => {
setLoading(true);
const emails = contacts
.filter(x => x.isChecked)
.map(item => item.emailAddress);
setProgress(30); //this is where I am manually setting the progress.
try {
const searchResults = await AppApi.search(emails);
let userList = [];
setProgress(70); // I want to manually set the percentage here
for (let i = 0; i < searchResults.length; i++) {
//processing the list here
}
onSearchComplete(userList); //passing on the results to another component
} catch (err) {
console.log({ err });
setMsgBox({ message: `${err.message}`, type: 'error' });
}
setLoading(false);
}
useEffect(() => {
onSearch();
}, [progress]);
return (
<Box>
{loading ? <LinearProgressWithLabel value={progress} />:
<Box>{msgBox && (<a style={{ cursor: 'pointer' }} onClick={() => setMsgBox(null)} title="Click to dismiss"><MessageBox type={msgBox.type || 'info'}>{msgBox.message}</MessageBox></a>)}</Box>}
</Box>
);
}
At the moment, your useEffect hook has the wrong dependencies. onSearch looks like it has two dependencies that could change - contacts and onSearchComplete, so the effect hook should actually be written as:
useEffect(() => {
onSearch();
}, [contacts, onSearchComplete]);
Depending on how onSearchComplete is defined, you might find that your effect re-runs more frequently than it should; you can either solve this by making onSearchComplete a callback:
const OtherComponent = () => {
const onSearchComplete = useCallback(userList => {
// ----- 8< -----
}, [...]);
}
Or wrapping the callback in a ref -
const Search = ({ onSearchComplete }) => {
const onSearchCompleteRef = useRef();
onSearchCompleteRef.current = onSearchComplete;
const onSearch = async () => {
// ----- 8< -----
onSearchCompleteRef.current(userList);
}
// Now you don't need onSearchComplete as a dependency
useEffect(() => {
onSearch();
}, [contacts]);
};
Edit
The reason you're not seeing the "updated" progress is because the processing of the results happens on the same render cycle as you updating the progress bar. The only way to get around that would be to introduce an artificial delay when you're processing the results:
setTimeout(() => {
onSearchCompleteRef.current();
setLoading(false);
}, 100);
I created a CodeSandbox demo to show this.

How to console.log the

I have a simple React component and inside of it I am fetching data from a remote API, and I want to console.log it in useEffect. I am trying to do it but nothing doesn't get logged into the console, why? What am I missing here? Here is the component:
import React, { useState, useEffect } from 'react';
import { useLocalization } from '#progress/kendo-react-intl';
import { Card, CardHeader, Avatar, CardTitle, CardSubtitle } from '#progress/kendo-react-layout';
import { guid } from '#progress/kendo-react-common';
import { Scheduler } from './../components/Scheduler';
import { employees } from './../resources/employees';
import { images } from './../resources/images';
import { orders, ordersModelFields } from './../resources/orders';
import { teams } from './../resources/teams';
// const orderEmployees = employees.filter(employee => employee.jobTitle === 'Sales Representative');
// const initialFilterState = { };
// orderEmployees.forEach(employee => {
// if(employee.fullName === 'Wait Peperell') {
// initialFilterState[employee.id] = false;
// } else {
// initialFilterState[employee.id] = true;
// }
// });
const Planning = () => {
const localizationService = useLocalization();
const [filterState, setFilterState] = React.useState(initialFilterState);
const [data, setData] = React.useState(orders);
const [fetchedData, setFetchedData] = React.useState(null);
useEffect(() => {
fetch("https://mocki.io/v1/29b83c0b-1a55-430d-a173-92b3632e04aa")
.then(response => response.json())
// 4. Setting *dogImage* to the image url that we received from the response above
.then(data => setFetchedData(data))
console.log(fetchedData)
},[])
// console.log(fetchedData)
const onDataChange = React.useCallback(
({ created, updated, deleted }) => {
setData(old => old
// Filter the deleted items
.filter((item) => deleted.find(current => current[ordersModelFields.id] === item[ordersModelFields.id]) === undefined)
// Find and replace the updated items
.map((item) => updated.find(current => current[ordersModelFields.id] === item[ordersModelFields.id]) || item)
// Add the newly created items and assign an `id`.
.concat(created.map((item) => Object.assign({}, item, { [ordersModelFields.id]: guid() }))))
},
[]
);
const onEmployeeClick = React.useCallback(
(employeeId) => {
setFilterState({
...filterState,
[employeeId]: !filterState[employeeId]
});
},
[filterState, setFilterState]
);
return (
<div id="Planning" className="planning-page main-content">
<div className="card-container grid">
<h3 className="card-title">{localizationService.toLanguageString('custom.teamCalendar')}</h3>
{
orderEmployees.map(employee => {
return (
<div
key={employee.id}
onClick={() => onEmployeeClick(employee.id)}
style={!filterState[employee.id] ? {opacity: .5} : {}}
>
<Card style={{ borderWidth: 0, cursor: 'pointer'}}>
<CardHeader className="k-hbox" >
<Avatar type='image' shape='circle' size={'large'} style={{
borderWidth: 2,
borderColor: teams.find(({teamID}) => teamID === employee.teamId).teamColor,
}}>
<div className="k-avatar-image" style={{
backgroundImage: images[employee.imgId + employee.gender],
backgroundSize: 'cover',
backgroundPosition: 'center center',
}}
/>
</Avatar>
<div>
<CardTitle style={{color: teams.find(({teamID}) => teamID === employee.teamId).teamColor}}>{employee.fullName}</CardTitle>
<CardSubtitle>{employee.jobTitle}</CardSubtitle>
</div>
</CardHeader>
</Card>
</div>
);
})
}
<div className="card-component" >
<Scheduler
data={data.filter(event => filterState[event.employeeID])}
onDataChange={onDataChange}
modelFields={ordersModelFields}
resources={[
{
name: 'Teams',
data: teams,
field: 'teamID',
valueField: 'teamID',
textField: 'teamName',
colorField: 'teamColor'
}
]}
/>
</div>
</div>
</div>
);
}
export default Planning;
I also tried to place the console.log outside of useEffect but still, nothing gets console.logged.
You need to look how useEffect work, setFetchedData is async.
Create another useEffect only for console.log.
useEffect(() => {
console.log(fetchedData);
},[fetchedData]); // Update at the first render + when fetchedData state change.
You can do it like this
useEffect(() => {
fetch("https://mocki.io/v1/29b83c0b-1a55-430d-a173-92b3632e04aa")
.then((response) => response.json())
// 4. Setting *dogImage* to the image url that we received from the response above
.then((data) => {
setFetchedData(data);
console.log(data);
});
}, []);
or juste create another useEffect that listens to fetchedData change, like this
useEffect(() => {
console.log(fetchedData);
}, [fetchedData]);

How to delete a row from a DataGrid that is connected to Firebase?

I have a DataGrid table with data that comes from Firebase and I wanted to know how can I delete and update the firebase information ?
I have this piece of code that deletes the row and it does works BUT because I haven't add anything to update the firebase it will not delete it permanently (which makes perfect sense):
Edit: Deleted unnecessary piece of code to just leave delete function
this is just after a row has been check then it let's you delete that checked row (and it works) but I don't see space (it brings out compile errors) to add the firebase delete() function in that piece of code.
<IconButton
onClick={() => {
const selectedIDs = new Set(selectionModel);
setEstudiantes((r) =>
r.filter((x) =>
!selectedIDs.has(x.id)
));
}}
>
<DeleteOutlinedIcon />
</IconButton>
This is how I do the check of the rows (and it does work):
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}
However I do have the delete function that connects with my firebase and deletes documents that I did before migrating to MUI DataGrid but I do not know how to integrated it. This is how you delete something in firebase usually
db.collection("usuarios")
.doc(user.uid)
.collection("estudiantes")
.doc(document name variable)
.delete();
Thank you any tip/help is welcome.
*UPDATE this is how it looks
it does the delete as intended but it doesn't update the firebase and idk where to add the code that does that because w/e I try to add it it comes out as an error:
if I just refresh it comes back:
UPDATE Adding the code of the DataGrid:
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>
)
Update Adding picture of the error I get when I try to add the logic to update the firebase, doesn't matter where I put it inside the delete logic it just gives me an error, I honestly do not know where to put it since I don't understand very well the selection of MUI on DataGrid
Update Adding my whole 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';
function ListadoEstudiantes({user}) {
const history = useHistory("");
const crearEstudiante = () => {
history.push("/Crear_Estudiante");
};
const [estudiantesData, setEstudiantesData] = useState([])
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);
setEstudiantes((r) =>
r.filter((x) =>
!selectedIDs.has(x.id)
));
}}
>
<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 (() => {
const estData = window.localStorage.getItem("estudiantes");
setEstudiantes(JSON.parse(estData))
}, [])
useEffect (() => {
window.localStorage.setItem("estudiantes", JSON.stringify(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>
)
}
export default ListadoEstudiantes
So after you filter estudiantes, you're left with the items that the user does not want to delete. But before we do that, we're going to have to get the items that the user wants to delete so we can delete them from Firebase.
You could replace the onClick function of the delete button with:
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()
})
)
////If NOT updating from db add this
setEstudiantes(estudiantes.filter((x) =>
!selectedIDs.has(x.id)))
}}
Items that the user wants to delete are in selectedIDs so we need to use selectedIDs.has(x.id) instead of !selectedIDs.has(x.id). Now we're left with an array that includes only the items for deletion. We map this array to delete each one of these selected items. By using the map method.
After this, you can filter estudiantes since now we don't need the items we just deleted from Firebase. (you can skip this one if you're updating from firebase as the new data won't include deleted items).
Please let me know if this is what you were looking for.

How to make dynamic tab view screen in react native

I am trying to add tabs in my react native app. Here on tab i want to show all the data coming from an api. This gives a array of string. And when user click on any tab it should show respective data. Here is an example image.
Here below header I want to display the array of string coming from the ap.
Below the search field I want to display the data which is coming from different api.
I am using a package https://www.npmjs.com/package/react-native-tab-view . I am not sure how to achieve this with this.
Here is the code I have
import { TabView, SceneMap } from "react-native-tab-view";
import { connect } from "react-redux";
import { getAllState } from "../../actions/hubActions";
interface CommunityMemberProps {
getStates: () => void;
allStates: [];
}
const styles = StyleSheet.create({
scene: {
flex: 1,
},
});
const FirstRoute = () => (
<View style={[styles.scene, { backgroundColor: "#ff4081" }]} />
);
const SecondRoute = () => (
<View style={[styles.scene, { backgroundColor: "#673ab7" }]} />
);
const initialLayout = { width: Dimensions.get("window").width };
const CommunityMember = ({ getStates, allStates }: CommunityMemberProps) => {
useEffect(() => {
getStates();
}, []);
const [searchText, setSearchText] = useState<string>("");
const handleChangeText = (text: string) => {
setSearchText(text);
};
console.log("allStates", allStates); <-- this gives data ["India", "newDelhi"]
const [index, setIndex] = React.useState(0);
const [routes] = React.useState([
{ key: "First", title: "First" },
{ key: "Second", title: "Second" },
]);
const renderScene = SceneMap({
first: FirstRoute,
second: SecondRoute,
});
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={initialLayout}
/>
);
};
function mapStateToProps(state: any) {
return {
allStates: state.hub.allStates,
};
}
const mapDispatchToProps = (dispatch: any) => ({
getStates: () => dispatch(getAllState()),
});
export default connect(mapStateToProps, mapDispatchToProps)(CommunityMember);
Since you are using redux this can be easily done. Whenever you select a tab update the redux state with the tab selected. Keep the component same for all tabs and retrieve the tab from redux state using mapStateToProps and fetch data dynamically and useEffect or componentDidMount() hook.

Resources