Hi,
Pretty new to PCF and React, I'm more an angular guy ;-)
Goal is to create a password control PCF, simply masking the input for the enduser.
Because I want it to look like a standard control, I decided to use a React/FluentUI and it's TextField.
Also using the new virtual control feature to have a more optimal package size.
Problem is on the onChange hook, my control does not generate in the sandbox with following message :The error "Invalid hook call. Hooks can only be called inside the body of a function component" occurs for multiple reasons ..."
I made sure that my react version is the same as the one specified as platform-library in the manifest file.
Couldn't find any real life examples of the use of a TextField that is bound to a value :-(
This is my control.tsx file :
export interface IPasswordInputControlClassProps {
context: ComponentFramework.Context<IInputs>;
value?: string;
onChange: (newValue?: string) => void;
}
export class PasswordInputControlClass extends React.Component<IPasswordInputControlClassProps> {
onChange = React.useCallback(
(ev: React.FormEvent<HTMLInputElement|HTMLTextAreaElement>,newValue?: string) => {
if (this.props.onChange) {
this.props.onChange(newValue);
}
},
[this.props.value]
);
public render(): React.ReactNode {
return (
<TextField type="password" styles={textFieldStyle} placeholder='---' value={this.props.value} onChange={this.onChange}/>
)
}
}
And this is my index.ts :
export class PasswordInputControl implements ComponentFramework.ReactControl<IInputs, IOutputs> {
private theComponent: ComponentFramework.ReactControl<IInputs, IOutputs>;
private notifyOutputChanged: () => void;
_currentValue: string | undefined;
/**
* Empty constructor.
*/
constructor() { }
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
*/
public init(
context: ComponentFramework.Context<IInputs>,
notifyOutputChanged: () => void,
state: ComponentFramework.Dictionary
): void {
this.notifyOutputChanged = notifyOutputChanged;
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
* @returns ReactElement root react element for the control
*/
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
console.log("entered UpdateView", context.updatedProperties);
debugger;
this._currentValue = context.parameters.value.raw ?? undefined;
const props: IPasswordInputControlClassProps = { value: this._currentValue , context: context, onChange: this.onChangeValue.bind(this) };
return React.createElement(
PasswordInputControlClass, props
);
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as “bound” or “output”
*/
public getOutputs(): IOutputs {
return {
value: this._currentValue ?? ''
};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void {
// Add code to cleanup control if necessary
}
private onChangeValue(newValue?: string) {
this._currentValue = newValue;
if (this.notifyOutputChanged) {
this.notifyOutputChanged();
}
}
}