React: Issue with fetching and updating the state in useCallback - reactjs

I am currently working on a component that makes an API call, retrieves the data, and then displays the data in the Fluent UI Datalist.
The issue is as follows:
The component loads for the first time, then it re-renders after the API call, and the component shows the correct entries within the table with the state.items being set to correct value. However, when I click on column to run the onColumnClick the items inside the function are empty, which result in an error. The columns are fine, but the state.items is just an empty collection.
How can this possibly be fixed to so that I see the items within the onColumnClick?
Here is a piece of code:
export const ListComponent = (props: ListComponentProps) => {
const fetchPeople = async () => {
const entry: ITableEntry[] = [];
//items ... sdk call
for await (const item of items) {
entry.push({
key: item.id,
name: item.name,
lastName: item.lastname
});
}
}
useEffect(() => {
fetchPeople();
.then(elementList => {
setState(
state => ({ ...state, items: elementList }),
);
});
}, [])
const onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
// PLACE WHERE THE ERROR HAPPENS
console.log(items);
}, []);
const columns: IColumn[] = [
{
key: 'column1',
name: 'First Name',
fieldName: 'name',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
{
key: 'column2',
name: 'Last Name',
fieldName: 'lastname',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
];
const [state, setState] = React.useState({
items: [] as ITableEntry[],
columns: columns,
});
return (
<>
<DetailsList
items={state.items}
columns={state.columns}
/>
</>
);
});

const onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
// PLACE WHERE THE ERROR HAPPENS
console.log(items);
}, [state]);
add dependency to the use callback to be recalculated when state changes

This is a total rewrite with some notes
import React, {useCallback, useEffect, useState} from "react";
/** Option One if the function does not requires variables from the component
* itself you can put it outside like in "api" folder */
const fetchPeople = async () => {
//items ... sdk call
// if items are already calculated and they are not async
return items.map((item)=>({
key: item.id,
name: item.name,
lastName: item.lastname
}))
// else
// return (await Promise.all(items)).map((item)=>({
// key: item.id,
// name: item.name,
// lastName: item.lastname
// }))
}
export const ListComponent = (props: ListComponentProps) => {
const [items, setItems] = useState<ITableEntry[]>([])
// Option Two: use callback this function is "saved" inside a variable with a memoization based on the
// elements inside the array at the end
// const fetchPeople = useCallback(async () => {
// ...
// }, [])
useEffect(() => {
// option three you can also leave it there so it can be used in other part of the application
// const fetchPeople = async () => {
// ...
// }
// if you like async await toy can run this
(async () => {
setItems(await fetchPeople())
})()
/** if this is not modifiable you don't need to put it there
* and this function will run after the component is "mount"
* in my case fetch people will not change and that is why you should use useCallback
*/
}, [fetchPeople]);
const onColumnClick = useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
console.log(items);
}, [items]);
const columns = [
{
key: 'column1',
name: 'First Name',
fieldName: 'name',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
{
key: 'column2',
name: 'Last Name',
fieldName: 'lastname',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: onColumnClick,
data: 'string',
isPadded: true,
},
]
return (
<>
<DetailsList
items={items}
columns={columns}
/>
</>
);
});
keep variables as simple as possible and unless something strange is required just save "datas" in State

Here is a fix that actually makes this work!
So I actually found a similar post to my issue (although I have searched for it for ages before):
React - function does not print current states
However, the solution had to be modified to this to reflect the changes in the columns.
The solution always also refreshes columns upon changes to items (see useEffects, where I set the columns), so the columns are being updated.
export const ListComponent = (props: ListComponentProps) => {
const [state, setState] = React.useState({
items: [] as IDocument[],
columns: [] as IColumn[],
});
const fetchPeople = React.useCallback(async () => {
const entry: ITableEntry[] = [];
//items ... sdk call
for await (const item of items) {
entry.push({
key: item.id,
name: item.name,
lastName: item.lastname
});
}
setState((state) => ({ ...state, items: elementsList }));
}, []);
useEffect(() => {
setState((state) => ({ ...state, columns: columns }));
}, [state.items]);
useEffect(() => {
fetchPeople();
}, []);
const _onColumnClick = React.useCallback((ev: React.MouseEvent<HTMLElement>, column: IColumn): void => {
const columns = state.columns;
const items = state.items;
console.log(items);
}, [state.items, state.columns]);
const columns: IColumn[] = [
{
key: 'column1',
name: 'First Name',
fieldName: 'name',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: _onColumnClick,
data: 'string',
isPadded: true,
},
{
key: 'column2',
name: 'Last Name',
fieldName: 'lastname',
minWidth: 210,
maxWidth: 350,
isRowHeader: true,
isResizable: true,
isSorted: true,
isSortedDescending: false,
sortAscendingAriaLabel: 'Sorted A to Z',
sortDescendingAriaLabel: 'Sorted Z to A',
onColumnClick: _onColumnClick,
data: 'string',
isPadded: true,
},
];
return (
<>
<DetailsList
items={state.items}
columns={state.columns}
/>
</>
);
});

Related

Uncaught TypeError: Cannot read properties of undefined (reading 'searchable')

I have created table using react that fetch data from Postgres SQL and render it.
cant understand what is the root cause for this error.
errors -
[enter image description here][1]
the table was create with an internal component.
import React, { useEffect, useState } from "react";
import { render } from "react-dom";
function ResultsFunction() {
// set state
const [results, setResults] = useState([]);
const columns = [{
id: 0, //mandatory field for arrange columns
dataField: 'test_name',
text: 'test_name',
sort: true,
visible: true, // is column visible for arrange
},{
id: 1,
dataField: 'dut_name',
text: 'name',
sort: true,
visible: true, // is column visible for arrange
}, {
id: 2,
dataField: 'dut_ip',
text: 'ip',
sort: true,
visible: true
}, {
id: 4,
dataField: 'load',
text: 'load',
sort: true,
visible: true // is column visible for arrange
}]
// first data grab
useEffect(() => {
fetch("http://12.12.12.12:9000/")
.then((resp) => resp.json())
.then((data) => {
//console.log(data.length)
//setResults(data)
setResults(mapResults(data))
});
}, []);
//console.log(results.length)
const mapResults = (results) => {
return (
results.map((result, index) => (
<li>
{results.id = {index}}
{result.test_name}
{result.dut_name}
{result.dut_ip}
{result.load}
</li>
))
);
}
console.log(columns)
console.log(results)
return (
<TableWithSearch
data={results}
columns={columns}
/>
);
}
export default ResultsFunction;
[1]: https://i.stack.imgur.com/49bU4.png

AgGrid cells don't re-render on refresh

This is the cell renderer for the 2 action buttons I'm passing to AgGrid. When I refresh the page, it loses its state and doesn't re-render them, but hits the function nevertheless.
const columnDefs: ColDef[] = useMemo(() => [
{
field: 'actions',
headerName: t('actions', { ns: 'common' }),
cellRendererSelector: (params: ICellRendererParams<IShop>) => ({
component: ActionsCell,
params: {
...params,
options: {
edit: {
id: 'editShop',
hasPermission: user.permissions.canEditShop,
href: (data: IShop) => appRoutes.editShop(router.query.companyId as string, data.id as string)
},
delete: {
id: 'deleteShop',
hasPermission: user.permissions.canDeleteShop,
onDeleteClick: (data: IShop) => handleClickOpen(data),
},
}
}
}),
cellStyle: agGridStyles.actionsCell
},
]
My AgGrid invocation:
<AgGridReact<IShop>
sideBar={sidebarConfig}
ref={gridRef}
containerStyle={agGridStyles.container}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
paginationPageSize={gridPageSize}
onGridReady={onGridReady}
/>
I tried spreading the options, in order to create a new reference to no avail:
options: {
...params.options,
edit: {
//...
}
}
Should I define the options object outside of the cell renderer:
const [options, setOptions] = useState<IOptions>({
edit: {
id: 'editShop',
hasPermission: user.permissions.canEditShop,
href: (data: IShop) => appRoutes.editShop(router.query.companyId as string, data.id as string)
},
delete: {
id: 'deleteShop',
hasPermission: user.permissions.canDeleteShop,
onDeleteClick: (data: IShop) => handleClickOpen(data),
},
});

Render React Component Inside of Highcharts Graph

I'd like to be able to render a React Component inside of a HighCharts Graph. I'd like to render the component DatePicker inside my HighCharts Graph, rather than being above it like it currently is? I'm using the HighCharts Official React Wrapper, and this is what my code looks like:
function Graph(props) {
const [startDate, setStartDate] = useState(new Date('1969-11-19 PST'));
const [bx, setBx] = useState([]);
const [by, setBy] = useState([]);
const [bz, setBz] = useState([]);
async function getData(table, year, month, day) {
...
}
useEffect(() => {
...
}, [startDate])
var options = {
chart: {
events: {
load: function() {
this.showLoading();
setTimeout(this.hideLoading.bind(this), 2000);
}
}
},
lang: {
noData: "Awaiting for data"
},
rangeSelector: {
inputEnabled: false,
buttons: [
{
type: 'all',
text: '1d',
title: 'View 1 Day'
},
{
type: 'minute',
count: 10,
text: '10m',
title: 'View 10 minutes'
},
{
type: 'minute',
count: 1,
text: '1m',
title: 'View 1 minutes'
},
{
type: 'second',
count: 30,
text: '30s',
title: 'View 30 seconds'
},
]
},
chart: {
zoomType: 'x'
},
title: {
text: props.title
},
xAxis: {
type: 'datetime'
},
time: {
timezoneOffset: 8*60
},
series: [
{
marker: {
enabled: true,
radius: 2
},
color: 'red',
name: 'Bx',
pointStart: startDate.getTime(),
pointInterval: 1000,
data: bx // data has to be [x, y] pairs
},
{
marker: {
enabled: true,
radius: 2
},
color: 'blue',
name: 'By',
pointStart: startDate.getTime(),
pointInterval: 1000,
data: by
},
{
marker: {
enabled: true,
radius: 2
},
color: 'green',
name: 'Bz',
pointStart: startDate.getTime(),
pointInterval: 1000,
data: bz
}
]
}
const isValidDate = (date) => {
date.getTime();
const origDate = new Date('1969-11-19 PST');
const finalDate = new Date('1970-04-03 PST');
console.log("GET DATE", date.getTime()) // getting number day of month
const bool = origDate.getTime() <= date.getTime() && date.getTime() <= finalDate.getTime();
return bool;
}
return (
<React.Fragment>
<DatePicker
selected={startDate}
filterDate={isValidDate}
onChange={(date) => setStartDate(date)}
/>
<HighchartsReact
highcharts={Highcharts}
constructorType={'stockChart'}
options={options}
/>
</React.Fragment>
);
}
I see this example from the HighCharts React Official Wrapper Github, but I'm still a bit confused by it and am wondering if I could simply do the same thing with a functional component. https://codesandbox.io/s/1o5y7r31k3
I've made a codesandbox of my code here https://codesandbox.io/s/friendly-moon-y8guc

Adding a functional component to useeffect method in react js

I'm trying to adding a sample ModalPage functional component in the useEffect method, but it is showing an error:
const [data, setData] = React.useState('');
useEffect(() => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
};
fetch('http://localhost:4000/api/users/getUserDetails', requestOptions)
.then(response => response.json())
.then(res => {
const data = {
columns: [{
label: 'First Name',
field: 'FirstName',
sort: 'asc',
width: 150
},
{
label: 'Last Name',
field: 'LastName',
sort: 'asc',
width: 270
},
{
label: 'Email',
field: 'Email',
sort: 'asc',
width: 200
},
{
label: 'Phone Number',
field: 'PhoneNumber',
sort: 'asc',
width: 100
},
{
label: 'Edit',
field: 'Edit',
width: 100
},
],
rows: res.isUser.map((data) => ({
FirstName: data.firstName,
LastName: data.LastName,
Email: data.email,
PhoneNumber: data.phoneNumber,
edit : <ModalPage/>
}))
}
setData(data);
})
}, [])
this is the modal page
export default function ModalPage() {
const [show, setShow] = React.useState(false);
function toggle () {
setShow({
show: !show
});
}
return (
<MDBContainer>
<MDBBtn onClick={toggle}>Modal</MDBBtn>
<MDBModal isOpen={show} toggle={toggle}>
<MDBModalHeader toggle={toggle}>MDBModal title</MDBModalHeader>
<MDBModalBody>
(...)
</MDBModalBody>
<MDBModalFooter>
<MDBBtn color="secondary" onClick={toggle}>Close</MDBBtn>
<MDBBtn color="primary">Save changes</MDBBtn>
</MDBModalFooter>
</MDBModal>
</MDBContainer>
);
}
As #Nisharg Shah said, you can't call <ModalPage/> inside of the useEffect hook. Try changing it to this:
rows: res.isUser.map((data) => ({
FirstName: data.firstName,
LastName: data.LastName,
Email: data.email,
PhoneNumber: data.phoneNumber,
Edit: ModalPage // Note the removal of the tags "</>"
}))
Then to render you can add this to your return statement:
<MDBModalBody>
{data.rows.map(row => <Edit/>)}
</MDBModalBody>

How to do onclick on particular table row in MaterialTable

I have two issues in MaterialTable
* I am using MaterialTable in reactjs , i want to do onClick on particular row . I am passing JSON data in table. How can i implement this?
columns: [
{ title: 'Username', field: 'username' },
{ title: 'Team Membership', field: 'teammembership' },
],
<MaterialTable
title = "Team Members"
columns={this.state.columns}
data={this.state.rowData}
/>
teammembership should be clickable
*How to add a button in particular row, rather than action? I am already using action in first row , i want to add one button not along with action, but in 3rd row.
columns: [
{ title: 'User Name', field: 'username' },
{ title: 'Role',
field: 'roles',
lookup: { 34: 'Primary', 63: 'Secondary' ,53 : 'Escallation', 54:'Override ' },
},
{ title: 'Start Date', field: 'Startdate', type: 'datetime' },
{ title: 'End Date', field: 'enddate', type: 'datetime' },
{title : 'Repeat', field:'repeat'},
],
data: [
{ username: 'Mehmet', roles: '34', Startdate: 1987, enddate: 2018,repeat:'repeat' },
{
username: 'Zerya Betül',
roles: '63',
Startdate: 2017,
enddate: 2019,
repeat:'repeat'
},
],
<MaterialTable
title = ""
columns={this.state.columns}
data={ this.state.data}
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
const data = [...this.state.data];
data.push(newData);
setState({ ...this.state, data });
}, 600);
}),
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
const data = [...this.state.data];
data[data.indexOf(oldData)] = newData;
setState({ ...this.state, data });
}, 600);
}),
onRowDelete: oldData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
const data = [...this.state.data];
data.splice(data.indexOf(oldData), 1);
setState({ ...this.state, data });
}, 600);
}),
}}
/>
in the place of repeat i want to place a button
Any help would be appreciated. Thank you.
The component has an onRowClick prop which can be used to have things happen onClick of a given row. Please see the documentation here and look specifically at the section "Detail Panel With RowClick Example".

Resources