MGT PeoplePicker selectionChanged event parameter type - reactjs

I am creating a SPFx web part with React and I am using the Microsoft Graph Toolkit PeoplePicker. It offers a selectionChanged callback. This callback gets passed a parameter of type Event (defined in typescript/lib/lib.dom.d.ts)
export type PeoplePickerProps = {
// ....
selectionChanged?: (e: Event) => void;
}
However, according to this documentation I need to access event.target.selectedPeople. But selectedPeople does not exist as property of event.target.
My code looks like this:
import * as React from 'react';
import { PeoplePicker, People } from '#microsoft/mgt-react/dist/es6/spfx';
export const EmployeePicker: React.FC = (): React.ReactElement => {
const [people, setPeople] = React.useState([]);
const handleSelectionChanged = (event: Event): void => {
console.log('Selection changed');
console.log('event', event);
setPeople(event.target.selectedPeople);
};
return (
<div>
<PeoplePicker selectionChanged={handleSelectionChanged} />
Selected People:
<People people={people} />
</div>
);
};
I'd like to avoid opting out of the type system. (I guess changing the type of event to any would work) And rather know how I can access the event.target.selectedPeople prop using typescripts type system.

You can use event.details too. You can read about it here https://learn.microsoft.com/en-us/graph/toolkit/customize-components/events. For the people picker there will be an array of the currently selected people.
To make it work with typescript you can either use any or do something like event.target["selectedPeople"] or event["details"]. Or you can create a new type like
type PeoplePickerEventTarget = EventTarget & {selectedPeople: IDynamicPerson[]};
I am not sure what the type of the array is, you can replace any with the correct one or just build your own.
And then in the code you can do something like this
<PeoplePicker
selectionChanged={(e: Event) => {
const target = e.target as PeoplePickerEventTarget;
//do something here
}}
></PeoplePicker>

Related

Typescript allows to call a function with incorrect parameter's type

There is a function with next signature:
const verify = (address?: string) => void
There is a Component with props type:
type VerifyButtonProps = { onClick: () => void; }
There is a Component with props type:
type TButtonProps = { onClick?: React.MouseEventHandler<HTMLButtonElement>; children: React.ReactNode; };
[Codesanbox example]
(https://codesandbox.io/s/react-ts-playground-forked-v24gs7?file=/src/index.tsx/)
I'm getting the runtime error when click on the button and expect typescript points out to it, but compilation passes without any errors.
How can I prevent runtime error with help of typescript on the compiling step?
Your issue is basically following case (playground):
const verify = (address?: string) => address?.toLowerCase()
const verifyEmpty: () => void = verify
const onClick: (event: object) => void = verifyEmpty
onClick({ this: 'is not a string'})
Typescript allows each of these steps, however combined it produces a runtime error. This unsoundness is known, however Typescript does not guarantee soundness (no runtime errors if if there are no type errors) and this is one case where they decided to leave it unsound.
This means it is up to you to catch such errors. In your case, you could use onClick={() => verify()} to fix the error.
To avoid this situation you can replace
() => void
with
(...args: undefined[]) => void;
With that replacement you'll explicitly tell to your component, that function doesn't allow any number of arguments.
So, you can still pass verify function to your component. But inside of the component you can't pass it down to any function props with optional arguments, e.g. <Button onClick={verify} />
From the index.tsx file, the problem with your code is that your trying to run .toLowerCase() on an event.
Here is your code:
const verify = (address?: string) => { console.log("address = ", address?.toLowerCase());};
const App = (props) => {
return <VerifyButton onClick={verify} />;
};
I suggest you look into handlers but passing your function as you have in the onClick handler means that you get every argument passed to the verify function as address.
Log the address to console and see what I mean.
You may write your change handlers this way:
onClick={(e) => yourFunction(e)}
This is useful if you need something from the event, for example a value from an input.
OR
onClick={() => yourFunction()}
This will prevent you from passing unwanted arguments to your functions. Hope this helps.
u need to correctly type the verify function to match the expected onClick prop type in each component.
For VerifyButtonProps, the verify function can be passed like:
const VerifyButton: React.FC<VerifyButtonProps> = ({ onClick }) => (
<button onClick={onClick}>Verify</button>
);
const App = () => {
const handleVerify = () => {
verify();
};
return (
<div>
<VerifyButton onClick={handleVerify} />
</div>
);
};
For TButtonProps, the verify function needs to be converted to a proper React.MouseEventHandler:
const TButton: React.FC<TButtonProps> = ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
);
const App = () => {
const handleVerify = (event: React.MouseEvent<HTMLButtonElement>) => {
verify();
};
return (
<div>
<TButton onClick={handleVerify}>Verify</TButton>
</div>
);
};
when u make these changes, TypeScript will catch the type mismatch and display an error during the compilation step, rather than at runtime.

Expose state and method of child Component in parent with React

I know it's not a good pattern to do that, but you will understand why I want to do like that.
I have a HTable, which use a third-party library (react-table)
const HTable = <T extends object>({ columns, data, tableInstance}: Props<T>) {
const instance: TableInstance<T> = useTable<T> (
// Parameters
)
React.useImperativeHandle(tableInstance, () => instance);
}
Now, I want to control columns visibility from parent. I did:
const Parent = () => {
const [tableInstance, setTableInstance] = React.useState<TableInstance<SaleItem>>();
<Table data={data} columns={columns} tableInstance={(instance) => setTableInstance(instance)}
return tableInstance.columns.map((column) => {
<Toggle active={column.isVisible} onClick={() =>column.toggleHiden()}
}
}
The column hides well, but the state doesn't update and neither does the toggle, and I don't understand why. Could you help me to understand?
EDIT:
Adding a sandbox.
https://codesandbox.io/s/react-table-imperative-ref-forked-dilx3?file=/src/App.js
Please note that I cannot use React.forwardRef, because I use typescript and React.forwardRef doesn't allow generic type like this if I use forwardRef
interface TableProps<T extends object> {
data: T[],
columns: Column<T>[],
tableInstance?: React.RefObject<TableInstance<T>>,
}
Your issue is that react-tables useTable() hook always returns the same object as instance wrapper (the ref never changes). So your parent, is re-setting tableInstance to the same object - which does not trigger an update. Actually most of the contained values are also memoized. To get it reactive grab the headerGroups property.
const {
headerGroups,
...otherProperties,
} = instance;
React.useImperativeHandle(
tableInstance,
() => ({ ...properties }), // select properties individually
[headerGroups, ...properties],
);

Is separating data retrieval code into interfaces and impl. of those interfaces a good idea in React?

My question is: is the below pattern a good idea in React or no? I come from Java world where this type of code is standard. However, I've ran into several things that, while being a good idea in Java, are NOT a good idea in ReactJS. So I want to make sure that this type of code structure does not have weird memory leaks or hidden side-effects in the react world.
Some notes on below code: I'm only putting everything in the same file for brevity purposes. In real life, the react component the interface and the class would all be in their own source files.
What I'm trying to do: 1) Separate the display logic from data access logic so that my display classes are not married to a specific implementation of talking to a database. 2) Separating DAO stuff into interface + class so that I can later use a different type of database by replacing the class implementaton of the same DAO and won't need to touch much of the rest of the code.
so, A) Is this a good idea in React? B) What sort of things should I watch out for with this type of design? and C) Are there better patterns in React for this that I'm not aware of?
Thanks!
import { useState, useEffect } from 'react';
interface Dao {
getThing: (id: string) => Promise<string>
}
class DaoSpecificImpl implements Dao {
tableName: string;
constructor(tableName: string) {
this.tableName = tableName;
}
getThing = async (id: string) => {
// use a specific database like firebase to
// get data from tabled called tablename
return "herp";
}
}
const dao: Dao = new DaoSpecificImpl("thingies");
const Display: React.FC = () => {
const [thing, setThing] = useState("derp");
useEffect(() => {
dao.getThing("123").then((newThing) =>
setThing(newThing));
});
return (
<div>{thing}</div>
)
}
export default Display;
https://codesandbox.io/s/competent-taussig-g948n?file=/src/App.tsx
The DaoSpecificImpl approach works however I would change your component to use a React hook:
export const useDAO = (initialId = "123") => {
const [thing, setThing] = useState("derp");
const [id, setId] = useState(initialId);
useEffect(() => {
const fetchThing = async () => {
try{
const data = await dao.getThing(id);
setThing(data);
}catch(e){
// Handle errors...
}
}
fetchThing();
}, [id]);
return {thing, setId};
}
using the hook in your component:
const Display = () => {
const {thing, setId} = useDao("123"); // If you don't specify initialId it'll be "123"
return <button onClick={() => setId("234")}>{thing}</button> // Pressing the button will update "thing"
}
Side note: You could also use a HOC:
const withDAO = (WrappedComponent, initialId = "123") => {
.... data logic...
return (props) => <WrappedComponent {...props} thing={thing} setId={setId}/>
};
export default withDAO;
E.g. using the HOC to wrap a component:
export default withDao(Display); // If you don't specify initialId it'll be "123"

Office ui Fabric People Picker, pass varaibles to onChangeEvent

I would like to know if there is a possibility to added a variable to the default onchange event of the office ui fabric react People Picker component.
In the onchange event default is onChange?: (items?: IPersonaProps[]) => void
I would like to pass an variable to add to an array like Key Value for using later in my code.
You can build a HOC to achieve this.
Define
// Your HOC component
interface Props {
yourCustomState: string // get custom value from props
...
return (
<>
<Select
onChange={(e: any) => onChange(e, yourCustomState)} // add custom value to callBack function
...
Usage
handleOnChangeEvent = () => (event, yourCustomStateValue: string) => {
console.log(yourCustomStateValue);
... // event
}
<YourComponent
yourCustomState={value}
onChange={this.handleOnChangeEvent()}
...

Why toggleSelection and isSelected method are receiving different key parameter?

I am using react-table in version 6.10.0. with typescript.
There is an easy way to add checkbox with hoc/selectTable
However toggleSelection an isSelected method you need to provide to manage selection are receiving different key.
toggleSelection method is receiving extra "select-" at the beginning.
I could not found any example which such a problem.
I know there is a simple workaround for this problem, but still I could not found any example which extra string at the beginning. I am new in react and it seems that I do it incorrectly.
import "bootstrap/dist/css/bootstrap.min.css";
import ReactTable, { RowInfo } from "react-table";
import "react-table/react-table.css";
import checkboxHOC, { SelectType } from "react-table/lib/hoc/selectTable";
const CheckboxTable = checkboxHOC(ReactTable);
....
render() {
...
<CheckboxTable
data={this.getData()}
columns={this.columnDefinitions()}
multiSort={false}
toggleSelection={(r,t,v) => this.toggleSelection(r,t,v)}
isSelected={(key)=> this.isSelected(key)}
/>
}
...
toggleSelection = (key: string, shiftKeyPressed: boolean, row: any): any => {
...
//implementation -over here key is always like "select-" + _id
...}
isSelected = (key: string): boolean => {
// key received here is only _id
return this.state.selection.includes(key);
}
In all examples I have seen the methods are provided with the same key.
Looking at the source, it seems like it's working as intended, or there's a bug. If you haven't found any other mention of this, it's probably the former.
This is where the SelectInputComponents are created:
rowSelector(row) {
if (!row || !row.hasOwnProperty(this.props.keyField)) return null
const { toggleSelection, selectType, keyField } = this.props
const checked = this.props.isSelected(row[this.props.keyField])
const inputProps = {
checked,
onClick: toggleSelection,
selectType,
row,
id: `select-${row[keyField]}`
}
return React.createElement(this.props.SelectInputComponent, inputProps)
}
The two handlers of interest are onClick (which maps to toggleSelection) and checked, which maps to isSelected. Notice the id here.
The SelectInputComponent looks like this:
const defaultSelectInputComponent = props => {
return (
<input
type={props.selectType || 'checkbox'}
aria-label={`${props.checked ? 'Un-select':'Select'} row with id:${props.id}` }
checked={props.checked}
id={props.id}
onClick={e => {
const { shiftKey } = e
e.stopPropagation()
props.onClick(props.id, shiftKey, props.row)
}}
onChange={() => {}}
/>
)
In the onClick (i.e. toggleSelection) handler, you can see that props.id is passed in as the first argument. So this is where the additional select- is being added.
I'm not familiar with this package so I can't tell you if it's a bug or a feature, but there is a difference in how these callback arguments are being passed. Due to the maturity of the package, it strongly suggests to me that this is intended behaviour.

Resources