React issues with reading a value in a context - reactjs

I have this context:
FilterProvider.tsx
import React, { createContext, useRef } from "react";
interface IFiltersContext {
filters: { [key: string]: any };
updateFilter: (name: string, value: any) => void;
addFilter: (name: string, value: any) => void;
clearFilter: (name: string) => void;
}
type FilterContextProps = {
initialFilters?: any;
onFilterChange: (values: any) => void;
};
export const FiltersContext = createContext<IFiltersContext>({
filters: {},
updateFilter: () => {},
addFilter: () => {},
clearFilter: () => {}
});
export const FiltersProvider: React.FC<FilterContextProps> = ({
children,
onFilterChange,
initialFilters = {}
}) => {
const filters = useRef(initialFilters);
const updateFilter = (name: string, value: any) => {
addFilter(name, value);
onFilterChange(filters.current);
};
const addFilter = (name: string, value: any) => {
filters.current = {
...filters.current,
[name]: value
};
};
const clearFilter = (name: string) => {
if (filters.current[name] !== null && filters.current[name] !== undefined) {
updateFilter(name, null);
}
};
return (
<FiltersContext.Provider
value={{ filters, updateFilter, addFilter, clearFilter }}
>
{children}
</FiltersContext.Provider>
);
};
And to be able to use this functions I use it as follows
<FiltersProvider onFilterChange={function (values: any): void {
console.log("Function not implemented.");
} }>
<PrettyTable
tableName="table_publications"
overlayFilter={
<>
<CountryFilter />
</>
}
{...tableData}
/>
</FiltersProvider>
Now inside PrettyTable I have the following : (NOTE1****)
const { showDialog } = useDialog();
const { filters, addFilter } = useContext(FiltersContext);
console.log(filters?.current) //always "somename", "test" , only the function call in Confirm will save things. THe one inside COuntryFIlter (See below) wont
function showFilterSelector() {
showDialog({
title: "Filter Columns",
message: <DialogContent />,
cancel: {
action: () => console.log("cancelled"),
message: "Reset Filters"
},
confirm: {
action: () => addFilter("somename", "test"), //I can see this calling addFilter, and after that "filters.current" has its value.
message: "Apply Filters"
},
align: "start",
width: "100%",
height: "100%",
wide: true
});
}
useEffect(() => {
debugger;
},[filters])
const DialogContent = () => {
return (
<Grid
columns={["flex", "flex", "flex"]}
justifyContent={"around"}
gap="small"
>
{props.overlayFilter} --> Prop found in the first code snippet, content of that component is in the last code snippet (below)
</Grid>
);
};
In the code above, im able to see the confirm action calling the function inside the provider, and it works just fine
But props.overlayFIlter contain the following:
Which is also using the same context inside
export const CountryFilter = ({...}) => {
const { filters, addFilter, updateFilter, clearFilter } = useContext(FiltersContext);
return (
<SelectComponent
onChange={i => {
addFilter("filter1","x") //This one is also being called supposedly in the context, but later I cant read it anywhere
}} /> )
But the above function despite calling the same context, the added data wont be able to be read in (NOTE1*** code snippet). I will only see the filter registered in the confirm action ("somename", "test")
What am I dont wrong? Am I using the contexts wrong?

Related

How to open multiple mui DialogProvider dynamically?

I have a generic DialogProvider that written with MUI and I open all dialog modals with this generic provider.
Code is below.
import React from "react";
import { makeAutoObservable } from "mobx";
import {
BooleanValuePaths,
getValue,
setValue,
Paths,
PathValueType,
StringValuePaths,
} from "../utils/utils";
import { IAppState, useAppState } from "./app-state";
type BaseType = object;
export class DialogState<T extends BaseType> {
_data: T;
_resetFunction?: () => T;
_touched: BooleanValuePaths<Paths<T>>;
_error: StringValuePaths<Paths<T>>;
_formError?: string;
_initialData: T;
constructor(dataInitiator: () => T) {
this._data = dataInitiator();
this._initialData = dataInitiator();
this._resetFunction = dataInitiator;
this._touched = {};
this._error = {};
makeAutoObservable(this);
}
reset() {
this._resetFunction?.();
}
get data() {
return this._data;
}
get formTouched() {
const touched = Object.keys(this._touched).find(
(key) => this._touched[key as Paths<T>],
);
return touched;
}
setValue<P extends Paths<T>>(path: P, value: PathValueType<T, P>): void {
setValue(this._data, path, value as any); //TODO remove the "as any", but the typescript has problem
this._touched[path] = true;
}
getValue<P extends Paths<T>>(path: P): PathValueType<T, P> {
return getValue(this._data, path) as PathValueType<T, P>; //TODO remove the "as PathValueType<T, P>", but the typescript has problem
}
isTouched<P extends Paths<T>>(path: P) {
return this._touched[path];
}
errorOf<P extends Paths<T>>(path: P) {
return this._error[path];
}
setErrorOf<P extends Paths<T> | "">(path: P, error: string) {
this._error[path] = error;
}
resetErrors() {
this._error = {};
}
resetTouches() {
this._touched = {};
}
resetData() {
this._data = this._initialData;
}
setFormError(error: string) {
this._formError = error;
}
}
function processErrorFunction<T extends BaseType>(
state: DialogState<T>,
err: any,
appState: IAppState,
errorKeyMap?: { errorKey: string; dataKey: string }[],
) {
try {
const json = JSON.parse(err?.message);
if (Array.isArray(json?.message)) {
(json.message as any[]).forEach((m) => {
if (
typeof m?.property === "string" &&
typeof m?.constraints === "object"
) {
const msg = Object.keys(m.constraints)
.map((k) => m.constraints[k])
.join(", ");
const dataKey =
errorKeyMap?.find((erk) => erk.errorKey === m.property)?.dataKey ??
m.property;
state.setErrorOf(dataKey, msg);
}
});
} else {
appState.setSnackMessage({ message: json?.message || "unknown" });
}
} catch (_) {
appState.setSnackMessage({ message: err?.message || "unknown" });
}
}
export function useDialogStateProvider<T extends BaseType>(
stateInitiator: () => T,
saverFunction: (v: T, appState: IAppState) => Promise<any>,
closeFunction?: (value?: T) => void,
errorKeyMap?: { errorKey: string; dataKey: string }[],
) {
const appState = useAppState();
const [loading, setLoading] = React.useState(false);
const state = React.useMemo(
() => new DialogState(stateInitiator),
[stateInitiator],
);
const reset = () => {
state.reset();
};
const handleSave = async () => {
try {
setLoading(true);
state.resetErrors();
await saverFunction(state.data, appState);
state.resetTouches();
if (closeFunction) {
state.resetData();
closeFunction(state.data);
reset();
}
} catch (err: any) {
processErrorFunction(state, err, appState, errorKeyMap);
} finally {
setLoading(false);
}
};
const handleClose = () => {
try {
if (closeFunction) {
setLoading(false);
state.resetErrors();
state.resetData();
state.resetTouches();
closeFunction(state.data);
reset();
}
} catch (err: any) {
processErrorFunction(state, err, appState, errorKeyMap);
} finally {
setLoading(false);
}
};
return {
reset,
state,
loading,
handleSave,
handleClose,
};
}
interface useDialogStateProviderInterface<T extends BaseType> {
(stateProvider: () => T): {
reset: () => void;
state: DialogState<T>;
loading: boolean;
handleSave: () => Promise<void>;
handleClose?: () => void;
};
}
export type DialogStateProviderType<T extends BaseType> = ReturnType<
useDialogStateProviderInterface<T>
>;
export const DialogStateContext =
React.createContext<DialogStateProviderType<any> | null>(null);
export function DialogStateProvider<
K extends BaseType,
T extends DialogStateProviderType<K>,
>({ state, children }: { state: T; children?: React.ReactNode }) {
return (
<DialogStateContext.Provider value={state}>
{children}
</DialogStateContext.Provider>
);
}
export function useDialogState<T extends BaseType>() {
const store = React.useContext(DialogStateContext);
if (!store) {
throw new Error("useDialogState must be used within DialogStateProvider");
}
return store as DialogStateProviderType<T>;
}
Usage is like below:
<DialogStateProvider state={taskState}>
<BaseDialog open={open} handleClose={handleClose} title={title}>
<AssignedTaskEditor isSubsequent={isSubsequent} />
</BaseDialog>
</DialogStateProvider>
BaseDialog code is this:
import {
Dialog,
DialogContent,
DialogTitle,
IconButton,
SxProps,
Theme,
useMediaQuery,
useTheme,
} from "#mui/material";
import CloseIcon from "#mui/icons-material/Close";
export function BaseDialog({
open,
handleClose,
title,
children,
sx,
}: {
open: boolean;
handleClose: () => void;
title?: React.ReactNode;
children: React.ReactNode;
sx?: SxProps<Theme>;
}) {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
return (
<Dialog
fullScreen={fullScreen}
open={open}
onClose={handleClose}
maxWidth="lg"
sx={{
...sx,
"& .MuiDialog-container > .MuiPaper-root": {
height: "100%",
backgroundColor: "background.default",
...(sx as any)?.["& .MuiDialog-container > .MuiPaper-root"],
},
"& .MuiDialogContent-root": {
flexGrow: 1,
backgroundColor: "background.default",
maxHeight: "80vh",
marginBlock: 2,
...(sx as any)?.["& .MuiDialogContent-root"],
},
"& .dialogBox-Icon": {
position: "absolute",
right: (theme) => theme.spacing(2.5),
top: (theme) => theme.spacing(1),
...(sx as any)?.["& .dialogBox-Icon"],
},
}}
>
{title && <DialogTitle>{title}</DialogTitle>}
<IconButton onClick={handleClose} className="dialogBox-Icon" size="large">
<CloseIcon />
</IconButton>
<DialogContent dividers>{children}</DialogContent>
</Dialog>
);
}
I want to open multiple dialog concurrently like that:
Save and add a subsequent task button should call an API endpoint to save data and it should close current modal and open another dialog and this should go forever. There should not be an end.
I have searched on internet but I couldn't find good example for this.
How can I do that with this current generic DialogProvider?

How to cluster polygons with react-leaflet?

I'm looking for a way to cluster polygons using react-leaflet v4 and react-leaflet-markercluster. I have not found any up-to-date examples of how I can achieve this, so I'm hoping I might get some help here.
Any example code to get me started would be a great help!
This will probably not solve your problem directly but hopefully show that using markercluster is rather simple. The only thing you need is to have a createMarkerCluster function.
clusterProps has a field for polygonOptions:
/*
* Options to pass when creating the L.Polygon(points, options) to show the bounds of a cluster.
* Defaults to empty
*/
polygonOptions?: PolylineOptions | undefined;
Since you now use a plain leaflet plugin it opens up for mor information on the internet, these two might help how you should configure polygonOptions
How to make MarkerClusterGroup cluster polygons
https://gis.stackexchange.com/questions/197882/is-it-possible-to-cluster-polygons-in-leaflet
Below is my general code to make clustermarkers work with React:
import { createPathComponent } from "#react-leaflet/core";
import L, { LeafletMouseEventHandlerFn } from "leaflet";
import "leaflet.markercluster";
import { ReactElement, useMemo } from "react";
import { Building, BuildingStore, Circle } from "tabler-icons-react";
import { createLeafletIcon } from "./utils";
import styles from "./LeafletMarkerCluster.module.css";
import "leaflet.markercluster/dist/MarkerCluster.css";
type ClusterType = { [key in string]: any };
type ClusterEvents = {
onClick?: LeafletMouseEventHandlerFn;
onDblClick?: LeafletMouseEventHandlerFn;
onMouseDown?: LeafletMouseEventHandlerFn;
onMouseUp?: LeafletMouseEventHandlerFn;
onMouseOver?: LeafletMouseEventHandlerFn;
onMouseOut?: LeafletMouseEventHandlerFn;
onContextMenu?: LeafletMouseEventHandlerFn;
};
// Leaflet is badly typed, if more props needed add them to the interface.
// Look in this file to see what is available.
// node_modules/#types/leaflet.markercluster/index.d.ts
// MarkerClusterGroupOptions
export interface LeafletMarkerClusterProps {
spiderfyOnMaxZoom?: boolean;
children: React.ReactNode;
size?: number;
icon?: ReactElement;
}
const createMarkerCluster = (
{
children: _c,
size = 30,
icon = <Circle size={size} />,
...props
}: LeafletMarkerClusterProps,
context: any
) => {
const markerIcons = {
default: <Circle size={size} />,
property: <Building size={size} />,
business: <BuildingStore size={size} />,
} as { [key in string]: ReactElement };
const clusterProps: ClusterType = {
iconCreateFunction: (cluster: any) => {
const markers = cluster.getAllChildMarkers();
const types = markers.reduce(
(
acc: { [x: string]: number },
marker: {
key: string;
options: { icon: { options: { className: string } } };
}
) => {
const key = marker?.key || "";
const type =
marker.options.icon.options.className || key.split("-")[0];
const increment = (key.split("-")[1] as unknown as number) || 1;
if (type in markerIcons) {
return { ...acc, [type]: (acc[type] || 0) + increment };
}
return { ...acc, default: (acc.default || 0) + increment };
},
{}
) as { [key in string]: number };
const typeIcons = Object.entries(types).map(([type, count], index) => {
if (count > 0) {
const typeIcon = markerIcons[type];
return (
<div key={`${type}-${count}`} style={{ display: "flex" }}>
<span>{typeIcon}</span>
<span style={{ width: "max-content" }}>{count}</span>
</div>
);
}
});
const iconWidth = typeIcons.length * size;
return createLeafletIcon(
<div style={{ display: "flex" }} className={"cluster-marker"}>
{typeIcons}
</div>,
iconWidth,
undefined,
iconWidth,
30
);
},
showCoverageOnHover: false,
animate: true,
animateAddingMarkers: false,
removeOutsideVisibleBounds: false,
};
const clusterEvents: ClusterType = {};
// Splitting props and events to different objects
Object.entries(props).forEach(([propName, prop]) =>
propName.startsWith("on")
? (clusterEvents[propName] = prop)
: (clusterProps[propName] = prop)
);
const instance = new (L as any).MarkerClusterGroup(clusterProps);
instance.on("spiderfied", (e: any) => {
e.cluster._icon?.classList.add(styles.spiderfied);
});
instance.on("unspiderfied", (e: any) => {
e.cluster._icon?.classList.remove(styles.spiderfied);
});
// This is not used at the moment, but could be used to add events to the cluster.
// Initializing event listeners
Object.entries(clusterEvents).forEach(([eventAsProp, callback]) => {
const clusterEvent = `cluster${eventAsProp.substring(2).toLowerCase()}`;
instance.on(clusterEvent, callback);
});
return {
instance,
context: {
...context,
layerContainer: instance,
},
};
};
const updateMarkerCluster = (instance: any, props: any, prevProps: any) => {};
const LeafletMarkerCluster = createPathComponent(
createMarkerCluster,
updateMarkerCluster
);
const LeafletMarkerClusterWrapper: React.FC<LeafletMarkerClusterProps> = ({
children,
...props
}) => {
const markerCluster = useMemo(() => {
return <LeafletMarkerCluster>{children}</LeafletMarkerCluster>;
}, [children]);
return <>{markerCluster}</>;
};
export default LeafletMarkerClusterWrapper;
Below is my function to create a marker icon from react elements:
import { divIcon } from "leaflet";
import { ReactElement } from "react";
import { renderToString } from "react-dom/server";
export const createLeafletIcon = (
icon: ReactElement,
size: number,
className?: string,
width: number = size,
height: number = size
) => {
return divIcon({
html: renderToString(icon),
iconSize: [width, height],
iconAnchor: [width / 2, height],
popupAnchor: [0, -height],
className: className ? className : "",
});
};

how to send function in typescript interface?

// ExpenseForm.tsx
interface ExpenseData {
enteredTitle: string;
enteredAmount: number;
enteredDate: string;
}
const ExpenseForm = (props) => {
const [userInput, setUserInput] = useState<ExpenseData>({
enteredTitle: "",
enteredAmount: 10,
enteredDate: "",
});
const { register, handleSubmit, resetField } = useForm<ExpenseData>();
const onValid = (data: ExpenseData) => {
setUserInput(data);
resetField("enteredAmount");
resetField("enteredDate");
resetField("enteredTitle");
};
};
// NewExpense.tsx
const NewExpense = (props) => {
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(),
};
console.log(expenseData); // I want to see this.
props.onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
);
};
//App.tsx
const App = () => {
const expenses = [
{
id: "xxxx",
title: "xxx",
amount: xxxx,
date: new Date(2022, 5, 16),
},
];
const addExpenseHandler = (expense) => {
console.log(expense);
};
return (
<div>
<NewExpense onAddExpense={addExpenseHandler} />
</div>
);
};
I'm using react with typescript in Udemy Course.
I want to send onSaveExpenseData in NewExpense.tsx to ExpenseForm.tsx.
How do I define type onSaveExpenseData in interface?
Also I want to using reset
I tried to onSaveExpenseData:()=>void, but it doesn't work.
You can export a type for expense.
export interface Expense {
id: string;
title: string;
amount: number;
date: Date;
}
Then in your function you can type expense
const addExpenseHandler = (expense: Expense) => {
console.log(expense);
};
In your NewExpense.tsx you can type its props to this:
interface NewExpenseProps {
onAddExpense: (e: Expense) => void;
}
export const NewExpense = ({ onAddExpense }: NewExpenseProps) => {
const saveExpenseDataHandler = (enteredExpenseData: Expense) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString(),
};
console.log(expenseData); // I want to see this.
onAddExpense(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
);
};
In your expense form you can do the same thing:
interface ExpenseFormProps {
onSaveExpenseData: (v: Expense) => void;
}
export const ExpenseForm = ({ onSaveExpenseData }: ExpenseFormProps) => {
console.log(onSaveExpenseData);
return <div>ExpenseForm</div>;
};
You need to define your props on the react component.
Also the type you wrote was almost correct, but it takes 1 argument that you were missing.
The easiest way to do this is like this:
import { FC } from 'react';
interface Props {
onSaveExpenseData: (_: Expense) => void
}
const ExpenseForm: FC<Props> = ...your current function
FC is the react type for FunctionalComponent. It will define the props and return value for you. The < Props > is a generic you pass to this type. It looks pretty complex but you don't have to fully understand it to use it.

How i can reload current page in react?

Here I'm fetching data with ApiClient and after that I make operation to confirm to delete the current item. My question here is how i can reload the page after I click Yes ?
type Props = {
resourceName: string,
label: string,
accessor: string,
row: Object,
}
#withRouter
#connectAPI((apiClient: ApiClient, props: Props) => ({
deleteObject: {
send: (id: any) => apiClient.resources[props.resourceName].delete(id),
success: () => {
// TODO: ?
},
},
}))
class DeleteComponent extends React.Component<Props> {
state = {
open: false,
};
handleOpen = () => {
this.setState({ open: true });
};
handleConfirm = (props: Props) => {
const { accessor, row, deleteObject } = props;
const id = row._original[accessor];
deleteObject({
id,
});
};
render(): React.ReactNode {
const { open } = this.state;
let message = null;
if (open) {
message = (
<MessageDialog
message="Are tou sure you want to delete ?"
actions={{
yes: {
label: 'Yes',
callback: this.handleConfirm,
},
no: {
label: 'No',
callback: (e: SyntheticMouseEvent<HTMLButtonElement>) => this.setState({ open: false }),
},
}}
/>
);
}
return (
<React.Fragment>
<Button
position="right"
onClick={this.handleOpen}
hoverIndicator="light-1"
/>
{message}
</React.Fragment>
);
}
}
export default DeleteComponent;

How to set checked on item in DetailsList control

I use DetailsList component from office-ui-fabric-react library:
import {DetailsList} from 'office-ui-fabric-react/lib/DetailsList';
render () {
const item = [
{value: 'one'},
{value: 'two'}
]
return (
<DetailsList
checkboxVisibility={CheckboxVisibility.always}
items={items}
selection={selection}
/>
}
How to set checked for item with value `two?
Noticed you passed a selection to DetailsList. There's a few methods in selection to do that, including:
setAllSelected(isAllSelected: boolean)
setKeySelected(key: string, isSelected: boolean, shouldAnchor: boolean)
setIndexSelected(index: number, isSelected: boolean, shouldAnchor: boolean)
In your case, you can give each value a key. And then call setKeySelected somewhere (for example, componentDidMount) to tell DetailsList to select specific items.
I looked everywhere and could never find the answer. To set selected items basically on page load do the following:
<DetailsList
columns={columns}
items={getItems()}
selection={getSelectedItems()}
/>
const getItems = () => {
const items: any[] = [];
itemList.map((item: any, i) => {
items.push({
key: i.toString(),
Name: item.Name,
Description: item.Description
});
});
return [];
};
const getSelectedItems = (): ISelection => {
const selection: Selection = new Selection();
const items = getItems();
selection.setItems(items);
selectedItemsList.map((selectedItem:
any, s) => {
selection.setKeySelected(s.toString(), true, false);
});
return selection;
};
Simply call selection.setAllSelected(true) inside useEffect if you want all the list items to be selected by default.
const selection = new Selection();
useEffect(() => {
selection.setAllSelected(true)
}, [selection]);
React Typescript (tsx) Sample.
import { DetailsList, ISelection, Selection } from '#fluentui/react/lib/DetailsList';
// ...
export const Component = () => {
const getSelectionDetails = (): string => {
const selectionCount = selection.getSelection().length;
return selectionCount
? `${selectionCount} items selected: ${selection.getSelection().map( (i: any) => i.key ).join('; ')}`
: 'No items selected';
}
const createSelection = () => {
return new Selection({
onSelectionChanged: () => setSelectionDetails(getSelectionDetails()),
getKey: (item: any) => item.key
});
};
const [selection, setSelection] = React.useState(createSelection());
const [selectionDetails, setSelectionDetails] = React.useState('');
return <>
<DetailsList
// ...
selection={selection}
selectionMode={SelectionMode.multiple}
// ...
/>
</>;
}

Resources