AgGrid cells don't re-render on refresh - reactjs

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

Related

How to update nested state in redux

I'm a bit stuck with redux. I want to create reducer that can update state onClick with data that provided in each button.
Here's my TabSlice.ts
interface ITabContext {
tabIndex: number,
posterUrlFetch: string,
tabData: {
fetchUrl: string;
title: string;
}[]
}
const initialState = {
tabIndex: 0,
posterUrlFetch: 'movie/popular',
tabData: [
{ fetchUrl: 'movie/popular', title: 'Trending' },
{ fetchUrl: 'movie/upcoming', title: 'Upcoming' },
{ fetchUrl: 'tv/popular', title: 'TV Series' },
]
}
const tabSlice = createSlice({
name: 'tab',
initialState: initialState as ITabContext,
reducers: {
changeTab(state, action: PayloadAction<ITab>) {
const newItem = action.payload;
return state = {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: [
{ fetchUrl: newItem.posterUrlFetch, title: newItem.posterUrlFetch },
]
}
}
}
})
Then I dispatch changeTab in my component and create function onClick:
const click = () => dispatch(changeTab({
tabIndex: 1,
posterUrlFetch: 'movie/popular',
tabData: [
{
fetchUrl: 'tv/latest',
title: 'TV Latest'
},
{
fetchUrl: 'movie/popular',
title: 'Popular'
},
{
fetchUrl: 'movie/latest',
title: 'Latest'
},
]
}));
As i click some info updates, but in tabData I have only first object. How to make it to push all data to tabData, not only first one? Thanks!
Remove return state = {} from your reducer function and instead return the object as a whole.
return {
tabIndex: newItem.tabIndex,
posterUrlFetch: newItem.posterUrlFetch,
tabData: newItem.tabData,
};
For the payload's tabData you can pass newItem.tabData

React Material-UI: Populating Rows in DataGrid returns undefined

In my React application, I want to fill the rows of a Material-UI Data Grid with backend data.
To achieve this, I make a GET request to the backend, which returns an AxiosResponse containing the data.
This AxiosResponse, I store in a variable named list, using useState().
Afterwards, I iterate over list to map the data to an Array of Objects, named testrows.
In the return method with DataGrid, I want to set property row to testrows.
However on rendering, it returns an error saying that testrow is undefined.
What am I doing wrong here?
import ...
export default function FruitList() {
const [list, setList] = useState<AxiosResponse>();
const columns: GridColDef[] = [
{
field: 'id',
headerName: 'Fruit ID',
type: 'number',
},
{
field: 'type',
headerName: 'Fruit Type',
type: 'string',
},
{
field: 'harvest',
headerName: 'Harvest Date',
type: "date",
},
{
field: 'entryDate',
headerName: 'Entry Date',
type: "date",
},
];
// Get list of fruits and store in variable "list"
useEffect(() => {
axios.get('http://localhost:3000/v1/fruit/'
.then(result => {
setList(result);
})
}, [])
let testrows = list?.data.map((element: { id: any, type: any, harvest: any, entryDate: any }) => {
return {
id: element.id,
type: element.type,
harvest: element.harvest,
entryDate: element.entryDate
}
}
);
// also tried useMemo:
const testrows = useMemo(() =>
list?.data.map((element: { id: any, type: any, harvest: any, entryDate: any }) => {
return {
id: element.id,
type: element.type,
harvest: element.harvest,
entryDate: element.entryDate
}
}),
[list?.data]
);
// console output: "test: undefined"
console.log("test: ", testrows);
return (
<div>
<div>
<DataGrid
rows={testrows}
columns={columns}
/>
</div>
</div>
);
}

Antd Tree component, how to add cache data

I have a problem with the Tree component of the Antd library. The problem is presented on the video. Namely that the component after changing the data tree renders anew and after selecting an option the previous ones are cleared. Does anyone have any idea how to solve this, so that after changing the data tree, the previous options that were selected are still selected?
import React, { useState } from 'react';
import { Tree } from 'antd';
import { Wrapper, TreeContainer } from './Tree.styled';
const treeData = [
{
title: '0-1',
key: '0-1',
children: [
{ title: '0-1-0-0', key: '0-0-0-0', },
{ title: '0-1-0-1', key: '0-0-0-1' },
{ title: '0-1-0-2', key: '0-0-0-2' },
],
},
{
title: '0-2',
key: '0-2',
},
{
title: '0-3',
key: '0-3',
children: [
{ title: '0-2-0-0', key: '0-2-0-0', },
{ title: '0-2-0-1', key: '0-2-0-1' },
{ title: '0-2-0-2', key: '0-2-0-2' },
],
},
{
title: '0-4',
key: '0-4',
children: [
{ title: '0-3-0-0', key: '0-3-0-0', },
{ title: '0-3-0-1', key: '0-3-0-1' },
{ title: '0-3-0-2', key: '0-3-0-2' },
],
},
{
title: '0-5',
key: '0-5',
children: [
{ title: '0-4-0-0', key: '0-4-0-0', },
{ title: '0-4-0-1', key: '0-4-0-1' },
{ title: '0-4-0-2', key: '0-4-0-2' },
],
},
];
const AntdTree = () => {
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
const [optionValue, setOptionValue] = useState<any>();
const tree = treeData.filter(filterValue => filterValue.key == (!!optionValue ? optionValue : filterValue.key)).map(data => data)
const onCheck = (checkedKeysValue: any) => {
console.log('onCheck', checkedKeysValue);
setCheckedKeys(checkedKeysValue);
};
return (
<>
<Wrapper>
<TreeContainer>
<select value={optionValue} onChange={e => setOptionValue(e.target.value)} >
<option value={'0-1'}>0-1</option>
<option value={'0-2'}>0-2</option>
<option value={'0-3'}>0-3</option>
<option value={'0-4'}>0-4</option>
<option value={'0-5'}>0-5</option>
</select>
<button onClick={() => setOptionValue('')} >Delete</button>
<Tree
checkable
onCheck={onCheck}
checkedKeys={checkedKeys}
treeData={tree}
/>
</TreeContainer>
</Wrapper>
</>
);
};
export default AntdTree;
Link to video with problem: https://youtu.be/BLEzQck3cZo
Thanks for your help !😊
onCheck callback in your case rewrite new values. You can save previous values using prevState in useState hook like so:
const onCheck = (checkedKeysValue: React.Key[], info: any) => {
console.log('onCheck', checkedKeysValue);
console.log('info', info);
if (info.checked) { // handle check case
setCheckedKeys(prevState => [...prevState, ...checkedKeysValue]);
} else { // handle uncheck case
setCheckedKeys(checkedKeysValue);
}
};

React: Issue with fetching and updating the state in useCallback

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

Add new row with a key press like 'enter'

I would like to add a key listener with 'enter' key to "onRowAdd" action from material-table on react,
Could be able to call the function outside the table props and add a normal js listener to key enter for launch the function?
So i searched on the doc of material-table and nothing similar can help me, anyone have some idea how i can fix it?
thanks
import React, { useEffect, useState } from 'react'
import MaterialTable from 'material-table'
import axios from 'axios'
var emitter = require('../config/global_emitter')
export default function MaterialTableDemo(props) {
const [state, setState] = useState({
columns: [
{ title: 'Item', field: 'item' },
{ title: 'Quantity', field: 'quantity', type: 'numeric' },
{ title: 'Description', field: 'description' },
{ title: 'Price', field: 'price', type: 'numeric' },
{
title: 'Location',
field: 'location',
lookup: { 34: 'Batman', 63: 'Back Store' },
},
],
data: []
});
useEffect(() => {
setState({...state, data:[]})
const user = {
name: props.userSelected
}
axios.post('api', user)
.then(e => {
if (e.data !== ''){
setState({...state, data: e.data})
}
})
},[props.userSelected])
const handleUpdate = (data) => {
const upload = {
items: data,
name: props.userSelected
}
axios.post('api', upload)
.then((e) => {
if (e.status === 200){
emitter.emit('confirmMessage', 'Updated list correctly')
}
})
}
return (
<MaterialTable
title={props.userSelected + '´s items ordered'}
columns={state.columns}
data={state.data}
options={{
headerStyle: {
backgroundColor: '#01579b',
color: '#FFF'
},
rowStyle: {
opacity: 1,
animationName: 'fadeInOpacity',
animationIterationCount: 1,
animationTimingFunction: 'ease-in',
animationDuration: '2s'
}
}}
onKeyDown={(ev) => {
console.log(`Pressed keyCode ${ev.key}`);
if (ev.key === 'Enter') {
// Do code here
ev.preventDefault();
}
}}
editable={{
onRowAdd: newData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
addRow(newData)
}, 600);
}),
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
const data = [...state.data];
data[data.indexOf(oldData)] = newData;
handleUpdate(data)
setState({ ...state, data });
}, 600);
}),
onRowDelete: oldData =>
new Promise(resolve => {
setTimeout(() => {
resolve();
const data = [...state.data];
data.splice(data.indexOf(oldData), 1);
handleUpdate(data)
setState({ ...state, data });
}, 600);
}),
}}
/>
);
}

Resources