ag-grid keyboard navigation for frameworkComponents is not working - reactjs

I'm trying to enable the keyboard action for my ag-grid.
I have few icons in my ag-grid I'm trying to click the icons through keyboard navigation.
to trigger the event via keyboard navigation I used onCellKeyPress where the user clicks enter key I'll trigger the event.
const onCellKeyPress = useCallback(
(keyPressEvent) => {
if (!isNil(keyPressEvent)) {
const {
event: { key },
colDef: { cellRenderer },
data: { indexId },
} = keyPressEvent;
if (key === ENTER_KEY) {
if (cellRenderer === 'deleteButtonSchemaRenderer') {
deleteRef?.current?.handleClickDeleteRow();
}
}
}
}
,[deleteRef?.current?.handleClickDeleteRow])
in framework component i just import my delete icon react component where i'm just doing the click event.
framework component
const frameworkComponents = useMemo(
() => ({
customNoRowsOverlay: ({
message,
iconName,
className,
}: ISystemNotification) => (
<SystemNotification
message={message}
iconName={iconName}
className={className}
/>
),
deleteButtonRenderer: (props: IDeleteRowButton) => {
const {
node: {
data: { id },
},
} = props;
return (
<DeleteRowButton
{...props}
setIsRowDeleting={setIsRowDeleting}
isDisabled={typeof id !== 'string'}
ref={deleteRef}
/>
);
},
}),
[ setIsRowDeleting]
);
deleterow component
const DeleteRowButton = forwardRef(
(
{ setIsRowDeleting, node, isDisabled = false }: IDeleteRowButton,
ref?: Ref<IUseRef | MutableRefObject<HTMLElement | null>>
) => {
useImperativeHandle(ref, () => ({ handleClickDeleteRow }));
const isRowDeleting = pathOr(false, ['data', 'isDeleting'], node);
const [isDeleting, setIsDeleting] = useState<boolean>(isRowDeleting);
const { t } = useTranslation();
const isGridDataChanged = useSelector(selectIsGridDataChanged);
const onClick = useCallback(
({ node, setIsDeleting, isRowDeleting, setIsRowDeleting, data }) => {
if (!isNil(data)) {
node.data = data;
}
const rowData = node.data;
setIsDeleting(!isRowDeleting);
let updatedData = {};
if (rowData.isDeleting) {
updatedData = { ...rowData, isDeleting: false };
} else {
updatedData = { ...rowData, isDeleting: true };
}
node.setData(updatedData);
setIsRowDeleting(true);
},
[]
);
const handleDeleteRow = useMemo(
() =>
debounce(() => {
onClick({
node,
setIsDeleting,
isRowDeleting,
setIsRowDeleting,
data: null,
});
}, 0),
[isRowDeleting, node, onClick, setIsRowDeleting]
);
const handleClickDeleteRow = useCallback(
(rowData: RowNode) => {
onClick({
node,
setIsDeleting,
isRowDeleting,
setIsRowDeleting,
data: rowData,
});
},
[isRowDeleting, node, onClick, setIsRowDeleting]
);
// Setting row active if changes was canceled
useEffect(() => {
if (!isGridDataChanged && !isRowDeleting && isDeleting) {
setIsDeleting(false);
}
}, [isDeleting, isGridDataChanged, isRowDeleting]);
return (
<Icon
disabled={isDisabled}
onClick={handleDeleteRow}
data-testid={TestIds.DeleteButtonIcon}
icon='trash'
title={t('Delete')}
/>
);
}
);
here it's working fine when the user is clicking via mouse, I'm trying to integrate keyboard action. but it always updates the last row of the ag-grid. I don't know the reason.
can any one help me in this.

Related

How to destroy google map on demand rides and deliveries in React

I was following the documentation to implement google map on demand rides and deliveries solution (ODRD) here.
And my Map component in React:
const MapComponent = ({ styles }) => {
const ref = useRef(null);
const tripId = useRef<string>('');
const locationProvider =
useRef<google.maps.journeySharing.FleetEngineTripLocationProvider>();
const [error, setError] = useState<string | undefined>();
const mapOptions = useRef<MapOptionsModel>({
showAnticipatedRoutePolyline: true,
showTakenRoutePolyline: true,
destinationMarker: ICON_OPTIONS.USE_DEFAULT,
vehicleMarker: ICON_OPTIONS.USE_DEFAULT,
});
const [trip, setTrip] = useState<TripModel>({
status: null,
dropOff: null,
waypoints: null,
});
const setTripId = (newTripId: string) => {
tripId.current = newTripId;
if (locationProvider.current) locationProvider.current.tripId = newTripId;
};
const setMapOptions = (newMapOptions: MapOptionsModel) => {
mapOptions.current.showAnticipatedRoutePolyline =
newMapOptions.showAnticipatedRoutePolyline;
mapOptions.current.showTakenRoutePolyline =
newMapOptions.showTakenRoutePolyline;
mapOptions.current.destinationMarker = newMapOptions.destinationMarker;
mapOptions.current.vehicleMarker = newMapOptions.vehicleMarker;
setTripId(tripId.current);
};
const authTokenFetcher = async () => {
const response = await fetch(
`${PROVIDER_URL}/token/consumer/${tripId.current}`
);
const responseJson = await response.json();
return {
token: responseJson.jwt,
expiresInSeconds: 3300,
};
};
useEffect(() => {
locationProvider.current =
new google.maps.journeySharing.FleetEngineTripLocationProvider({
projectId: PROVIDER_PROJECT_ID,
authTokenFetcher,
tripId: tripId.current,
pollingIntervalMillis: DEFAULT_POLLING_INTERVAL_MS,
});
locationProvider.current.addListener(
'error',
(e: google.maps.ErrorEvent) => {
setError(e.error.message);
}
);
locationProvider.current.addListener(
'update',
(
e: google.maps.journeySharing.FleetEngineTripLocationProviderUpdateEvent
) => {
if (e.trip) {
setTrip({
status: e.trip.status,
dropOff: e.trip.dropOffTime,
waypoints: e.trip.remainingWaypoints,
});
setError(undefined);
}
}
);
const mapViewOptions: google.maps.journeySharing.JourneySharingMapViewOptions =
{
element: ref.current as unknown as Element,
locationProvider: locationProvider.current,
anticipatedRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showAnticipatedRoutePolyline,
};
},
takenRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showTakenRoutePolyline,
};
},
destinationMarkerSetup: ({ defaultMarkerOptions }) => {
if (
mapOptions.current.destinationMarker !== ICON_OPTIONS.USE_DEFAULT
) {
defaultMarkerOptions.icon =
mapOptions.current.destinationMarker.icon;
}
return { markerOptions: defaultMarkerOptions };
},
vehicleMarkerSetup: ({ defaultMarkerOptions }) => {
if (mapOptions.current.vehicleMarker !== ICON_OPTIONS.USE_DEFAULT) {
// Preserve some default icon properties.
if (defaultMarkerOptions.icon) {
defaultMarkerOptions.icon = Object.assign(
defaultMarkerOptions.icon,
mapOptions.current.vehicleMarker.icon
);
}
}
return { markerOptions: defaultMarkerOptions };
},
};
const mapView = new google.maps.journeySharing.JourneySharingMapView(
mapViewOptions
);
// Provide default zoom & center so the map loads even if trip ID is bad or stale.
mapView.map.setOptions(DEFAULT_MAP_OPTIONS);
}, []);
return (
<div style={styles.map} ref={ref} />
);
};
And my App component like this:
import React from 'react';
import { Wrapper, Status } from '#googlemaps/react-wrapper';
import MapComponent from './src/components/MapComponent';
import { API_KEY } from './src/utils/consts';
const render = (status: Status) => <Text>{status}</Text>;
const App = () => {
return (
<Wrapper
apiKey={API_KEY}
render={render}
version={'beta'}
// #ts-ignore
libraries={['journeySharing']}
>
<MapComponent />
</Wrapper>
);
};
Everything will works fine but I do not know how to destroy the map when component unmount in React. That's why my App always call API update the trip info.
I was tried to use clean up function in useEffect:
useEffect(() => {
locationProvider.current =
new google.maps.journeySharing.FleetEngineTripLocationProvider({
projectId: PROVIDER_PROJECT_ID,
authTokenFetcher,
tripId: tripId.current,
pollingIntervalMillis: DEFAULT_POLLING_INTERVAL_MS,
});
locationProvider.current.addListener(
'error',
(e: google.maps.ErrorEvent) => {
setError(e.error.message);
}
);
const updateEvent = locationProvider.current.addListener(
'update',
(
e: google.maps.journeySharing.FleetEngineTripLocationProviderUpdateEvent
) => {
if (e.trip) {
setTrip({
status: e.trip.status,
dropOff: e.trip.dropOffTime,
waypoints: e.trip.remainingWaypoints,
});
setError(undefined);
}
}
);
const mapViewOptions: google.maps.journeySharing.JourneySharingMapViewOptions =
{
element: ref.current as unknown as Element,
locationProvider: locationProvider.current,
anticipatedRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showAnticipatedRoutePolyline,
};
},
takenRoutePolylineSetup: ({ defaultPolylineOptions }) => {
return {
polylineOptions: defaultPolylineOptions,
visible: mapOptions.current.showTakenRoutePolyline,
};
},
destinationMarkerSetup: ({ defaultMarkerOptions }) => {
if (
mapOptions.current.destinationMarker !== ICON_OPTIONS.USE_DEFAULT
) {
defaultMarkerOptions.icon =
mapOptions.current.destinationMarker.icon;
}
return { markerOptions: defaultMarkerOptions };
},
vehicleMarkerSetup: ({ defaultMarkerOptions }) => {
if (mapOptions.current.vehicleMarker !== ICON_OPTIONS.USE_DEFAULT) {
// Preserve some default icon properties.
if (defaultMarkerOptions.icon) {
defaultMarkerOptions.icon = Object.assign(
defaultMarkerOptions.icon,
mapOptions.current.vehicleMarker.icon
);
}
}
return { markerOptions: defaultMarkerOptions };
},
};
const mapView = new google.maps.journeySharing.JourneySharingMapView(
mapViewOptions
);
// Provide default zoom & center so the map loads even if trip ID is bad or stale.
mapView.map.setOptions(DEFAULT_MAP_OPTIONS);
return () => {
mapView.map = null // or mapView.map.setmap(null);
google.maps.event.removeListener(updateEvent);
};
}, []);
But it was not working. Hope anyone can help me find out this. Thanks

Cannot pass selected date from MUI DateTimePicker into SWR hook

I have a component that holds a MUI DataGrid.
Now, for any row of the DataGrid I need to render a DateTimePicker column. There is data coming
in with a SWR call which data could contain a datetime for the column or not.
mainPage/sdk.js
export const useSomeData = (
params: TUseFetchOptions['params']
) => {
const { data, isLoading, mutate } = useFetch<TPagination<TSomeData>>('/some-data/');
const approve = useCallback(
(someData: TSomeData) => {
const data = { published_at: someData.published_at }
return requestHandler(
post(`/some-data/update/`, data)
)
.then((someData) => {
mutate((prev) => {
if (!prev) {
return prev;
}
// some irrelevant mutation for swr caching happens here
return prev;
}
return prev;
}, false);
})
.catch((error) =>
// some irrelevant alerting happens here
);
},
[mutate]
);
return useMemo(
() => ({ someData: data, isLoading, approve,mutate }),
[data, isLoading, mutate, approve]
);
};
mainPage/index.tsx
import {useSomeData} from './sdk'
const SomeDataPublish = () => {
// const {params} = ....
// const dataGridProps = ...
const { someData, isLoading, approve } = useSomeData(params);
return (
<Stack>
{someData && (
<SomeDataDataGrid
someData={someData}
params={params}
DataGridProps={dataGridProps}
handleApprove={approve}
/>
)}
</Stack>
);
};
export default SomeDataPublish;
mainPage/componenets/someDataDataGrid.tsx
export const columns: GridColumns = [
{
// some field
},
{
// some field
},
{
// some field
},
// ...
];
const buildColumnsData = (
handleApprove: ReturnType<typeof useSomeData>['approve'],
): GridColumns => {
return [
...columns,
{
field: 'published_at',
headerName: 'Publish at',
flex: 0.75,
renderCell: (params: any) => <RowDatePicker params={params} />
},
{
field: '',
type: 'actions',
flex: 0.4,
getActions: (params: any) => [
<RowActions
params={params}
handleApprove={handleApprove}
/>
]
}
];
};
const buildRows = (someData: TSomeData[]): GridRowsProp => {
return someData.map((row) => ({
id: row.id,
// ...
published_at: _.get(row, 'published_at'),
}));
};
const SomeDataDataGrid: FC<{
someData: TPagination<TSomeData>;
params: TUseFetchOptions['params'];
DataGridProps: Partial<MuiDataGridProps>;
handleApprove: ReturnType<typeof useSomeData>['approve'];
}> = ({ someData, params, DataGridProps, handleApprove }) => {
return (
<Paper>
<DataGrid
// ...
columns={buildColumnsData(handleApprove)}
rows={someData ? buildRows(someData.results) : []}
// ...
{...DataGridProps}
/>
</Paper>
);
};
export default SomeDataDataGrid;
mainPage/componenets/rowDatePicker.tsx
const RowDatePicker: React.FC<{
params: GridRowParams;
}> = ({ params }) => {
const [publishedAt, setPublishedAt] = React.useState(params.row.published_at);
return (
<>
<DateTimeField
label={'Pick Date'}
value={publishedAt}
onChange={setPublishedAt}
/>
</>
);
};
export default RowDatePicker;
mainPage/componenets/rowAction.tsx
const RowActions: React.FC<{
params: GridRowParams;
handleApprove: ReturnType<typeof useSomeData>['approve'];
}> = ({ params, handleApprove }) => {
return (
<>
<Tooltip title="Approve">
<IconButton
color="success"
disabled={false}
onClick={(e) => {
console.log(params.row)}
handleApprove(params.row)
>
<AppIcons.CheckCircle />
</IconButton>
</Tooltip>
</>
);
};
export default RowActions;
The problem that I have - if I change the date from the date picker, on clicking the <AppIcons.CheckCircle /> in the RowActions component I expect the row.published_at to be updated with the new value. Then I pass the new updated object (with the updated published_at attribute) to the handleApprove hook so I can make some mutations and pass the updated object (with new published_at value) to the back end.
However, on examining the someData object that is passed to the approve hook the published_at field has its old value ( the one that came from the SWR fetcher).
I know that I need to mutate somehow params.row.published_at = publishedAt in the onChange callback of the RowDatePicker.DateTimePicker, but I am not sure how to do it. Any help would be appreciated.

How to handle old state reducer react

I am creating a cart where the client has multiple checkboxes, I can update the cart when the checkbox is checked and update it when is unchecked, so far so good, but the problem is when the client wants to create another order, I think the reducer is bringing me the old order.
This is my reducer
if (action.type === ADD_ITEM_TO_CART_DETAILS) {
return {
...state,
cart: [...state.cart, action.payload]
}
}
This is my action
export function addItemDetail(value){
return {
type : ADD_ITEM_TO_CART_DETAILS,
payload: value
}
}
and this is the JS:
export default function DetailProduct() {
const dispatch = useDispatch();
const { id } = useParams();
const history = useHistory();
useEffect(() => {
dispatch(getDetail(id));
}, [dispatch, id]);
const detail = useSelector((state) => state.detail); // details of the products
const { options, setOptions } = useContext(OrderContext);
const BackToProducts = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/productos");
}
};
const seeCart = () => {
if (options.salsa.length) {
dispatch(addItemDetail(options));
} else {
history.push("/carrito");
}
};
const handleComments = (e) => {
setOptions((prev) => ({
...prev,
Comments: e.target.value,
}));
};
const handleSalsa = (e) => {
const { name, checked } = e.target;
if (options.salsa.length <= 2) {
e.target.checked = false;
} else if (checked === true) {
setOptions((prev) => ({
...prev,
salsa: [...prev.salsa, name],
picture_url: detail.picture_url,
id: uuidv4(),
price: detail.price,
title: detail.title,
}));
}
if (checked === false) {
setOptions((prev) => ({
...prev,
salsa: prev.salsa.filter((p) => p !== name),
}));
}
};
const handleToppings = async (e) => {
const { name, checked } = e.target;
if (checked === true) {
setOptions({ ...options, toppings: [...options.toppings, name] });
}
if (checked === false) {
setOptions((prev) => ({
...prev,
toppings: prev.toppings.filter((p) => p !== name),
}));
}
};
useEffect(() => {
// useEffect to update the total amount
const productPrice = options.price; // price of the single product
const toppingPrice = options.priceTopping; // price of the topping
const total = toppingPrice ? productPrice + toppingPrice : productPrice;
setOptions((prev) => ({ ...prev, unit_price: total })); // set total amount product plus toppings
}, [options.price, options.priceTopping, setOptions]);
useEffect(() => {
// useEffect to update total amount of the toppings
const numberOfToppings = options.toppings.length;
const totalPriceTopping = numberOfToppings !== 0 ? numberOfToppings * 119 : 0;
setOptions((prev) => ({ ...prev, priceTopping: totalPriceTopping }));
}, [options.toppings, setOptions]);
return (
<MainContainer>
{detail.picture_url ? <PhotoProduct src={`https://hit-pasta.herokuapp.com/${detail.picture_url}`} /> : <Loading />}
<Like
onClick={() => {
history.push("/productos");
}}
/>
<ContainerOption>
{detail &&
detail?.salsas?.map((p, index) => {
return (
<ContainerOptionChild key={index}>
<div>
<Drop />
<LabelProductName>{p.sauce}</LabelProductName>
</div>
<InputOptions type="checkbox" checked={options.salsa.index} key={index} name={p.sauce} value={p.sauce} onChange={handleSalsa} />
<Description>{p.description}</Description>
</ContainerOptionChild>
);
})}
</ContainerOption>
<MainBoxComments>
<h3>Comentarios</h3>
<BoxComentario type="text" value={options.Comments} onChange={handleComments} placeholder="Agrega instrucciones o comentarios a tu orden" />
</MainBoxComments>
<MainBoxBtns>
<Okay onClick={seeCart}>
OKAY <CartIcon />
</Okay>
<BtnArmarOtroHit onClick={BackToProducts}>ARMAR OTRO HIT</BtnArmarOtroHit>
</MainBoxBtns>{" "}
</MainContainer>
);
}

Context variable not updating in child component React Context

I have multiple child components (component generate through recursion) inside my main component. Now the problem is when I updated the context variable in the parent component the child component doesn't render the updated value
here is my context provider
function MainLayoutProvider({ children }) {
const [mainJson, setMainJson] = useState([
{
component:'section',
id:'1111',
content:null,
type:'section',
cmType:'normal',
class:'',
style:{},
props:{}
}
]);
return (
<MainLayout.Provider value={mainJson}>
<MainDispatchLayout.Provider value={setMainJson}>
{children}
</MainDispatchLayout.Provider>
</MainLayout.Provider>
);
}
Here I included it on my main component
function App() {
return (
<DndProvider backend={HTML5Backend}>
<MainLayoutProvider>
<PlayGround></PlayGround>
</MainLayoutProvider>
</DndProvider>
);
}
inside the PlayGround component, there is another component name DropSection, where I am updating the 'mainJson' value
function DropSection() {
const board = useContext(MainLayout);
const setBoard = useContext(MainDispatchLayout);
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item, monitor) {
const didDrop = monitor.didDrop();
if (!didDrop ) {
addItemToBoard(item.sectionName)
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const addItemToBoard = (sectionName) => {
let newJson = {
component:sectionName,
id:IdGenerator(),
content:null,
type:'text',
cmType:'normal',
class:'',
style:{},
props:{}
}
setBoard((board) => [...board, newJson]);
};
return (
<div ref={drop}>
<h4 className="text-center">DropZone</h4>
{board.map((config,index) => <RenderCard key={config.id} config={config} />)}
</div>
);
}
but in the RenderCard component, the value of 'mainJson' is not updating or rendering, I am getting the old value which initializes in MainLayoutContext
function RenderCard({config}) {
const board =useContext(MainLayout);
const setBoard = useContext(MainDispatchLayout);
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item, monitor) {
const didDrop = monitor.didDrop();
if (!didDrop ) {
addItemToBoard(item.sectionName)
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
}));
const addItemToBoard = async (sectionName) => {
let newJson = {
component:sectionName,
id:IdGenerator(),
content:null,
type:'text',
cmType:'normal',
class:'',
style:{},
props:{}
}
setBoard((board) => [...board, newJson]);
};
if(config.cmType == 'complex'){
return RenderComplexCard(config)
}
var configProperty = {
style: config.style,
id:config.id,
};
return React.createElement(
config.component,
configProperty,
config.content &&
( config.type == "section" && Array.isArray(config.content) ? config.content.map(c => <RenderCard key={c.id} config={c} />) : config.content )
);
}
I had similar issue and solved by using useRef hook, if you do not want to use useState.
Do not forget to reference the variable by using .current when accessing it.

Unable to update nested state react

I have a reusable drop down menu component and i render it twice with two different lists and it should update the state with the id of the first element.
the first drop down of the layout update the state without any issue but the second one does not(i switched the order and it always seems the first one updates the state the second doesn't).
please see code
dashbord
const initializeData = {
actionStatuses: [],
actionCategories: [],
actionGroups: [],
actionEvents: [],
actionEventsWithFilter: [],
selectedFilters: {actionStatusId: "", actionCategoryId:""},
};
const Dashboard = ({ selectedPracticeAndFy }) => {
const [data, setData] = useState(initializeData);
const getSelectedStatus = ({ key }) => {
const actionStatusId = key;
const selectedFilters = { ...data.selectedFilters, actionStatusId };
setData((prevState) => {
return { ...prevState, selectedFilters }
});
};
const getSelectedCategory = ({ key }) => {
const actionCategoryId = key;
const selectedFilters = { ...data.selectedFilters, actionCategoryId };
setData((prevState) => {
return { ...prevState, selectedFilters }
});
};
}
result filter:
const ResultFilter = ({actionStatuses, actionCategories, getSelectedStatus, getSelectedCategory}) => {
return (
<Grid
justify="flex-start"
container
>
<Grid item >
<Typography component="div" style={{padding:"3px 9px 0px 0px"}}>
<Box fontWeight="fontWeightBold" m={1}>
Result Filter:
</Box>
</Typography>
</Grid>
<Grid >
<DropdownList payload={actionCategories} onChange={getSelectedCategory} widthSize= {dropdownStyle.medium}/>
<DropdownList payload={actionStatuses} onChange={getSelectedStatus} widthSize= {dropdownStyle.medium}/>
</Grid>
</Grid>
);
}
DropdownList:
const DropdownList = ({ label, payload, onChange, widthSize, heightSize, withBorders, initialData }) => {
const { selectedData, setSelectedData, handelInputChange } = useForm(
payload
);
useEffect(() => {
if (Object.entries(selectedData).length === 0 && payload.length !== 0) {
setSelectedData(payload[0]);
}
}, [payload]);
useEffect(() => {
if (Object.entries(selectedData).length !== 0) {
onChange(selectedData);
}
}, [selectedData]);
return (
<div style={widthSize}>
<div className="ct-select-group ct-js-select-group" style={heightSize}>
<select
className="ct-select ct-js-select"
id={label}
value={JSON.stringify(selectedData)}
onChange={handelInputChange}
style={withBorders}
>
{label && <option value={JSON.stringify({key: "", value: ""})}>{label}</option>}
{payload.map((item, i) => (
<option key={i} value={JSON.stringify(item)} title={item.value}>
{item.value}
</option>
))}
</select>
</div>
</div>
);
};
It may be a stale closure issue, could you try the following:
const getSelectedStatus = ({ key }) => {
setData((data) => {
const actionStatusId = key;
const selectedFilters = {
...data.selectedFilters,
actionStatusId,
};
return { ...data, selectedFilters };
});
};
const getSelectedCategory = ({ key }) => {
setData((data) => {
const actionCategoryId = key;
const selectedFilters = {
...data.selectedFilters,
actionCategoryId,
};
return { ...data, selectedFilters };
});
};

Resources