Reactjs - Make the get request when loading the page - reactjs

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}
/>
)
}

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;
}}
/>
);
}

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

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.

MobX ReactJS AntD updates won't re-render

So I'm trying change some table data within my AntD Table using ReactJS and MobX. The data in my MobX observable changes, but the table doesn't re-render an update until I say.... resize the page and the table re-renders. I've recreated my issue on CodeSandbox - it's not the exact data type, but this is the EXACT issue I'm running into, any thoughts???
https://codesandbox.io/s/remove-items-from-mobx-array-forked-3nybr?file=/index.js
#action
change = (key) => {
this.data
.filter(item => key === item.key)
.forEach(piece => {
piece.key = 10;
});
console.log(this.data);
};
const FooTable = () => {
const columns = [
{
title: "ID",
dataIndex: "key"
},
{
title: "Name",
dataIndex: "name"
},
{
title: "Last Name",
dataIndex: "lastName"
},
{
title: "Actions",
render: (text, record) => {
return (
<Button
type="link"
icon="delete"
onClick={() => tableStore.change(record.key)}
>
Delete
</Button>
);
}
}
];
return useObserver(() => {
return <Table columns={columns} dataSource={tableStore.data} />;
});
};
Because AntD Table is not observer by itself you need to use toJS on your data before you pass it to AntD.
import { toJS } from "mobx";
// ...
const FooTable = () => {
const columns = [ ... ];
return useObserver(() => {
return <Table columns={columns} dataSource={toJS(tableStore.data)} />;
});
};

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