Firebase API react.js Null response? - reactjs

Hi guys so this is a search React JS web page I developed recently, and up until earlier this morning it was working fine. So, it basically uses axios to obtain json data from a Firebase Realtime Database and displays the information on the web page using a data table.
import React, { useState, useEffect } from "react";
import axios from "axios";
import DataTable from "react-data-table-component";
const Search = () => {
const [data, setData] = useState([]);
const [resetPaginationToggle, setResetPaginationToggle] = useState(false);
const [filterText, setFilterText] = useState("");
const [error, setError] = useState("");
//------------Function that fetches data from Firebase Database API/JSON data-----------
const loadData = async () => {
try {
axios
.get(
`https://ojt-thesis-e37ae-default-rtdb.asia-southeast1.firebasedatabase.app/thesis-titles.json`
)
.then((response) => {
setData(Object.values(response.data));
})
.catch((err) => console.log(err));
} catch (error) {
setError(error.message);
}
};
//--------------------------------------------------------
const columns = [
{
name: "ID",
center: true,
selector: (row) => row.id,
sortable: true,
},
{
name: "Title",
center: true,
selector: (row) => row.title,
sortable: true,
},
{
name: "Category",
center: true,
selector: (row) => row.category,
sortable: true,
},
{
name: "Department",
center: true,
selector: (row) => row.department,
sortable: true,
},
];
// ----------------------THE ERROR IS COMING FROM THIS FUNCTION---------------
const filteredItems = data.filter(
(item) =>
item.title && item.title.toLowerCase().includes(filterText.toLowerCase())
);
//------------------------------------------------------------------
const subHeaderComponentMemo = React.useMemo(() => {
return (
<>
<input
type="text"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
placeholder="Search by title"
style={{border:"1px solid black",borderRadius:"10px",padding:"15px",width:"300px"}}
/>
</>
);
}, [filterText, resetPaginationToggle]);
console.log(data);
useEffect(() => {
loadData();
}, []);
return (
<div style={{overflow:"auto"}}>
<DataTable
columns={columns}
data={filteredItems}
subHeader
pagination
paginationResetDefaultPage={resetPaginationToggle}
paginationPerPage={30}
subHeaderComponent={subHeaderComponentMemo}
persistTableHead
/>
</div>
);
};
export default Search;
It seems to have worked fine a while ago but now when I try to run it using npm Nothing appears on the screen. Upon checking the web console for errors and I got the following below which all leads to one function or variable of mine which I isolated above in the code
It keeps notifying me that item.title is null and doesn't contain data? I tested the API and it does give me the json data response as expected. But my program doesn't seem to be saving it into the "data" useState variable I declared at the top. Is this due to a cause on my API's end? or am I not updating the setData properly. I'm just confused if maybe I took something out that was fairly important. I'm kind of at a loss at this point.

Related

My cell renderer does not have any data because of an async call

So I have a component which uses a CellRenderer which gets some data:
import { useEffect, useMemo, useState } from "react";
import "ag-grid-community/dist/styles/ag-grid.min.css";
import "ag-grid-community/dist/styles/ag-theme-material.min.css";
import Grid from "../Common/Grid";
import axios from "axios";
import SelectJudetCellRenderer from "./SelectJudetCellRenderer";
function GetJudete() {
return axios
.get("http://localhost:5266/api/judete")
.then((response) => {
let data = response.data;
return data;
})
.catch((err) => {
console.log("Eroare la aducerea datelor.");
});
}
function Localitati() {
let [judete, setJudete] = useState([]);
useEffect(() => {
async function GetJudeteAsync() {
const result = await GetJudete();
setJudete(result);
}
GetJudeteAsync();
}, []);
const [columnDefs] = useState([
{ field: "nume", filter: "agTextColumnFilter", editable: true },
{ field: "judet", filter: "agTextColumnFilter", editable: true, cellRenderer: SelectJudetCellRenderer, cellRendererParams: {judete: judete} },
]);
return (
<Grid
baseLink="http://localhost:5266/api/localitati"
columnDefs={columnDefs}
/>
);
}
export default Localitati;
Here's my Cell renderer:
import { ICellRendererParams } from 'ag-grid-community';
export interface JudeteCellRendererParams extends ICellRendererParams {
judete: any[];
}
function SelectJudetCellRenderer(props: JudeteCellRendererParams) {
console.log(props.judete)
return (
<select name="judete">
{
props.judete.map((judet) =>
<option value={judet.id}>{judet.name}</option>
)
}
</select>
)
}
export default SelectJudetCellRenderer;
The problem is that after the Async call Judete is getting new data but my cell renderer does not get the new data.
The console.log() from the CellRenderer returns an empty array.
Why is this happening and how can I fix it?
Thanks.
You need to tell AG Grid to refresh the rendered cell, this is not very well documented, see https://www.ag-grid.com/javascript-data-grid/component-cell-renderer/#cell-renderer-component
Here is a simple example using Angular (should be similar for class based React)
Notice the refresh() method:
// gets called whenever the user gets the cell to refresh
refresh(params: ICellRendererParams) {
// set value into cell again
this.cellValue = this.getValueToDisplay(params);
}
https://plnkr.co/edit/yFqQHfNjxMLrPb9f.
For functional components you should explicitly call the api.refreshCells() when the data is available.
See here for more details: https://www.ag-grid.com/react-data-grid/component-cell-renderer/#component-refresh
A possible solution (although I think it would be more simple to switch to a class component renderer)
function Localitati() {
let [judete, setJudete] = useState([]);
// get hold of AG Grid gridApi
const gridApiRef = React.useRef<GridApi>();
// update the 'judete' column when new data is available (this will re-invoke the cell renderers)
useEffect(() => {
gridApiRef.current.refreshCells({columns: 'judet'});
}, [judete]);
useEffect(() => {
async function GetJudeteAsync() {
const result = await GetJudete();
setJudete(result);
}
GetJudeteAsync();
}, []);
const [columnDefs] = useState([
{ field: "nume", filter: "agTextColumnFilter", editable: true },
{ field: "judet", filter: "agTextColumnFilter", editable: true, cellRenderer: SelectJudetCellRenderer, cellRendererParams: {judete: judete} },
]);
return (
<Grid
baseLink="http://localhost:5266/api/localitati"
columnDefs={columnDefs}
onGridReady={({ api }) => {
gridApiRef.current = api;
}}
/>
);
}

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.

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

Reactjs - Make the get request when loading the page

I know that in Angular there is ngOnInit. In Reactjs is there something similar to do the get request when loading the page?
I have a table and I need to get the request when I load the page for me to display the data in the table.
export default function ListAdverts() {
const columns = [
{
label: "Título",
accessor: "title",
width: "194px"
},
{
label: "Valor",
accessor: "price_cents",
width: "131px"
},
{
label: "Anunciante",
accessor: "title",
width: "203px"
},
{
label: "Categoria",
accessor: "title",
width: "158px"
}
];
const [dataAdverts, setdDataAdverts] = React.useState([]);
return (
<Table
rows={dataAdverts}
columns={columns}
/>
)
}
Data fetching for components is usually done inside the useEffect hook
export default function ListAdverts() {
const columns = ...
const [dataAdverts, setdDataAdverts] = React.useState([]);
// fetch data here
// runs only once because of empty dependency array
React.useEffect(() => {
let isCancelled = false
const fetchSomeData = async () => {
const data = await someApiRequest()
// only update state if component isn't unmounted
// if you try to update state on an unmounted component,
// React will throw an error
if (!isCancelled) {
setdDataAdverts(data)
}
}
fetchSomeData()
// cleanup
return () => {
isCancelled = true
}
}, [])
return (
<Table
rows={dataAdverts}
columns={columns}
/>
)
}

How to loop data and print in a table using material ui

I am working with Material-UI and getting data from the backend. There is no issue with the backend, but I don't know how to loop data and print it in a table format using Material-UI.
Can anyone guide me on how to print data in a table format?
Here is my code so far:
import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { getProducts } from "../../services/products";
import MaterialTable, { MTableToolbar } from "material-table";
const productsList = props => {
const [data, setData] = useState([]);
const [state] = React.useState({
columns: [
{ title: "Brand", field: "brand" }, //assume here my backend schema is brand
{ title: "Price", field: "price" }, //here price
{ title: "Model no", field: "model" } //here model
]
});
const getProducts = async () => {
try {
const res = await getProducts();
setData(res.data);
console.log(res.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getProducts();
}, []);
return (
<MaterialTable
components={{
Toolbar: props => {
return (
<div>
<MTableToolbar {...props} />
</div>
);
}
}}
options={{
actionsColumnIndex: 5,
selection: true
}}
/>
);
};
export default function Company() {
return <productsList />;
}
You have to set the data and columns value. So try it like this:
import React, { useState, useEffect } from "react";
import MaterialTable, { MTableToolbar } from "material-table";
const fakeFetch = () => {
return new Promise(resolve => {
resolve({
data: [
{ brand: "brand 1", price: 1, model: "123" },
{ brand: "brand 2", price: 1, model: "456" },
{ brand: "brand 3", price: 1, model: "789" }
]
});
});
};
export default function App() {
const [data, setData] = useState([]);
// When the columns don't change you don't need to hold it in state
const columns = [
{ title: "Brand", field: "brand" }, //assume here my backend schema is brand
{ title: "Price", field: "price" }, //here price
{ title: "Model no", field: "model" } //here model
];
const getProducts = async () => {
try {
const res = await fakeFetch();
setData(res.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getProducts();
}, []);
return (
<MaterialTable
columns={columns} // <-- Set the columns on the table
data={data} // <-- Set the data on the table
components={{
Toolbar: props => {
return (
<div>
<MTableToolbar {...props} />
</div>
);
}
}}
options={{
actionsColumnIndex: 5,
selection: true
}}
/>
);
}
To make it even easier you could also provide your fetch function (fakeFetch in this case) as the data value;
data={fakeFetch} // <-- Using this you wouldn't need the [data, setData], getProducts and useEffect code.
Working sandbox link
As per the material-table approach, you have to put your whole fetched data on the data prop inside the MaterialTable component. So as far as I can understand, there is no looping made in this case by using the material-table library.
Assuming the attributes in your data object match the field names specified in your columns prop (if not, create an array of objects from your fetched data that matches the column fields or vice-versa).
And the code would be just the addition of the data prop in your table:
<MaterialTable
// ... existing props
data={data}
/>
Keep in mind that you could also use the remote data approach as described in the documentation which gives you the means to immediately query your data and fetch it inside the data prop of the table.

Resources