I've decided to refactor a project I've made with vanilla javascript and use typescript, I'm always in doubt about how to pass a function as a type on interface.
I took a look on the typescript documentation but I didn't understood how it works.
For instance, What type bringTransactions should have ? and the return type of the handleChange func ?
import React from 'react';
import moment from 'moment';
import { Select } from 'antd';
interface Props {
bringTransactions: any;
}
const PeriodPicker: React.FC<Props> = ({ bringTransactions }: Props) => {
const periodOptions = [
{ value: 0, label: 'Hoje' },
{ value: 3, label: 'Últimos 3 dias' },
{ value: 7, label: 'Últimos 7 dias' },
{ value: 15, label: 'Últimos 15 dias' },
{ value: 30, label: 'Últimos 30 dias' },
{ value: 5, label: 'Data específica' },
];
async function handleChange(value: number): Promise<void> {
const filter = [];
// setDataPickerStatus(false);
if (value === 5) {
// setDataPickerStatus(true);
} else {
const current = moment().format('YYYY-MM-DD HH:mm:ss');
const subtracted = moment(current).subtract(value, 'days');
const initialDate = moment(subtracted)
.utc()
.hours(0)
.minutes(0)
.seconds(0)
.milliseconds(0);
filter[0] = initialDate.format('YYYY-MM-DD HH:mm:ss');
const finatlDate = moment(current)
.utc()
.subtract(1, 'days')
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(0);
filter[1] = finatlDate.format('YYYY-MM-DD HH:mm:ss');
if (value === 0) {
const normalized = `&date=${filter[0]}`;
bringTransactions(normalized);
} else {
const normalized = `&period=${JSON.stringify(filter)}`;
bringTransactions(normalized);
}
}
}
return (
<Select
placeholder="Selecione um período"
onChange={handleChange}
style={{ width: 200 }}
>
{periodOptions.map(option => (
<Select.Option value={option.value} key={option.value}>
{option.label}
</Select.Option>
))}
</Select>
);
};
export default PeriodPicker;
It looks like bringTransactions is a function which takes a single string argument and returns nothing. That would be:
interface Props {
bringTransactions(value: string): void;
}
or you can write it with arrow syntax
interface Props {
bringTransactions: (value: string) => void;
}
Read more about functions in the typescript docs.
I usually write function types like this:
interface Props {
bringTransactions: (prop: propType) => ReturnType;
}
And the handleChange function will return void as events callbacks usually return void
Related
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 : "",
});
};
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?
// 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.
I am doing a proyect on React Native and Typescript, I am new on them and I had been having an issue lately that I couldn't solve yet. I am trying to do a login on the app and Typescript keeps giving me errors on a useReducer I did. Unfortunately, i am new with Typescript so in order to solve previous errors I just put any on most of the types. This is the code, it is the input from the login screen:
import React, { useEffect, useReducer } from "react";
import { StyleSheet, Text, TextInput, View } from "react-native";
interface InputComponentProps {
INPUT_CHANGE: any
INPUT_BLUR: any
handleBlur: any
}
const INPUT_CHANGE: any = 'INPUT_CHANGE';
const INPUT_BLUR: any = 'INPUT_BLUR';
const inputReducer: any = (state: any, action: any) => {
switch(action.type) {
case INPUT_CHANGE:
return {
...state,
value: action.value,
isValid: action.isValid,
};
case INPUT_BLUR:
return {
...state,
touched: true,
};
default:
return state;
}
};
const InputComponent: React.FC<InputComponentProps> = (props: any) => {
const [inputState, inputDispatch] = useReducer<any>(inputReducer, {
value: '',
isValid: false,
touched: false,
});
const { onInputChange, id } = props;
useEffect((): (() => void) => {
return onInputChange(id, inputState.value, inputState.isValid);
}, [onInputChange, id, inputState])
const handleChangeText = (text: { trim: () => { (): any; new(): any; length: number; }; toLowerCase: () => string; length: number; }) => {
const emailRegex = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
let isValid = true;
if (props.required && text.trim().length === 0) isValid = false;
if (props.email && !emailRegex.test(text.toLowerCase())) isValid = false;
if (props.minLength && text.length < props.minLength) isValid = false;
inputDispatch({
type: INPUT_CHANGE,
value: text,
isValid: isValid,
});
}
const handleBlur = () => inputDispatch({ type: INPUT_BLUR });
return (
<>
<View style={styles.formControl}>
<Text style={styles.label}>{props.label}</Text>
<TextInput
{...props}
style={styles.input}
value={inputState.value}
onChangeText={handleChangeText}
onBlur={handleBlur}
/>
{!inputState.isValid && inputState.touched && (
<View>
<Text style={styles.errorText}>{props.errorText}</Text>
</View>
)}
</View>
</>
);
}
const styles = StyleSheet.create({
formControl: {
width: '100%',
},
label: {
fontFamily: 'OpenSansBold',
marginVertical: 8,
},
input: {
paddingHorizontal: 2,
paddingVertical: 5,
borderBottomColor: '#ccc',
borderBottomWidth: 1,
},
errorText: {
marginVertical: 5,
color: '#cc7755'
}
});
export default InputComponent;
This is a screenshot of the errors:
If there is someone that could help me, I would really appreciate it!
You have a few too many explicit anys where you would be better off letting TypeScript infer the type. Specifically, the ones which are causing you trouble are these two:
const inputReducer: any = (
useReducer<any>(
A reducer must be a function that takes a state and an action and returns the next state. By putting any in these two places, you are telling TypeScript that your reducer is anything and is not necessarily a function.
But it is a function. If you drop those two anys, then the type which is inferred as the value of inputReducer and the generic on useReducer is (state: any, action: any) => any. This is much more descriptive than just any. We know that it's a function and that it takes the correct amount of arguments.
When you add vague types where they aren't needed, you are making your code much less type-safe then with no type annotations at all. When you do this:
const INPUT_CHANGE: any = 'INPUT_CHANGE';
You are overriding all of TypeScript's power to infer the correct type. There is no need to put a type when you are assigning a variable because TypeScript will infer that the type of the variable is the same as the type of the value that you assigned.
const INPUT_CHANGE = 'INPUT_CHANGE';
Here, the type of the INPUT_CHANGE variable is the literal string "INPUT_CHANGE" instead of any.
const handleChangeMultiple = (event: React.ChangeEvent<{ value: unknown }>) => {
const { options } = event.target as HTMLSelectElement;
const value: string[] = [];
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
value.push(options[i].value);
}
}
setPersonName(value);
};
I just started using material UI and they have this great Select component that let you select from a list.
The code above is the sample code they provided that work for a string[], but my project is selecting from an object array.
example: {label: "string", value:"string", b: boolean}
My question is how can I modify this handleChange to work for an object array?
I try changing string[] to the dataType[] I created but I get the error "Argument of type 'string' is not assignable to parameter of type 'dataType'.
const handleChangeMultiple = (event: ChangeEvent<{ value: dataType[] }>) => {
console.log(event.target.value)
}
When I try this, it console log the correct value selected, but when I change console.log to setValue(event.target.value), I get error value.map is not a function.
{value.map((item) => (
option key={item.value} value={item.label}>
{item.label}
</option>
The code above work when console.log.
Select component is using basic type to determine which options are selected (Comparing objects is not so easy). You can use the array index:
import React from 'react';
import { createStyles, makeStyles, Theme } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
maxWidth: 300,
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: 2,
},
noLabel: {
marginTop: theme.spacing(3),
},
}),
);
interface User {
value: string,
label: string,
superUser: boolean
}
const users = [{
value: 'OliverHansen',
label: 'Oliver Hansen',
superUser: true
}, {
value: 'VanHenry',
label: 'Van Henry',
superUser: false
}, {
value: 'AprilTucker',
label: 'April Tucker',
superUser: true
}, {
value: 'RalphHubbard',
label: 'Ralph Hubbard',
superUser: false
}, {
value: 'OmarAlexander',
label: 'Omar Alexander',
superUser: true
}, {
value: 'CarlosAbbott',
label: 'Carlos Abbott',
superUser: false
}];
export default function MultipleSelect() {
const classes = useStyles();
const [selectedUsers, setSelectedUsers] = React.useState<User[]>([]);
const [selectedUserIndexes, setSelectedUserIndexes] = React.useState<number[]>([]);
const handleChangeMultiple = (event: React.ChangeEvent<{ value: unknown }>) => {
const { options } = event.target as HTMLSelectElement;
const selectedUsers: User[] = [];
const selectedUserIndexes: number[] = [];
for (let i = 0, l = options.length; i < l; i += 1) {
if (options[i].selected) {
let selectedUserIndex = parseInt(options[i].value, 10);
selectedUsers.push(users[selectedUserIndex]);
selectedUserIndexes.push(selectedUserIndex);
}
}
console.log(selectedUserIndexes, selectedUsers);
setSelectedUserIndexes(selectedUserIndexes);
setSelectedUsers(selectedUsers);
};
return (
<div>
<FormControl className={classes.formControl}>
<InputLabel shrink htmlFor="select-multiple-native">
Native
</InputLabel>
<Select
multiple
native
value={selectedUserIndexes}
onChange={(e) => handleChangeMultiple(e)}
inputProps={{
id: 'select-multiple-native',
}}
>
{users.map((user, index) => (
<option key={index} value={index}>
{user.label} {}
</option>
))}
</Select>
</FormControl>
</div>
);
}