React Fabric Ui Panel can't close - reactjs

I have an Sharepoint app. By clicking on a command list button I open a panel.
But when I try to close the panel I get a error saying that my _renderPanelComponent function is not a function and when trying to log it from _dismissPanel() it I get that it is undefined. But I can call it from _showPanel().
By logging from _dismissPanel() I now that I get back here after clicking close and triggered _onCancel() but I'm not sure why my _renderPanelComponent gets undefined and thats my question.
This is what the code looks like
import { override } from '#microsoft/decorators';
import { Log } from '#microsoft/sp-core-library';
import {
BaseListViewCommandSet,
Command,
IListViewCommandSetListViewUpdatedParameters,
IListViewCommandSetExecuteEventParameters
} from '#microsoft/sp-listview-extensibility';
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { assign } from '#uifabric/utilities';
import MainPanel, { IMainPanelProps } from '../MainPanel';
export interface ICreditInfoCommandSetProperties {
sampleTextOne: string;
sampleTextTwo: string;
context:object;
}
export default class CreditInfoCommandSet extends BaseListViewCommandSet<ICreditInfoCommandSetProperties> {
private panelPlaceHolder: HTMLDivElement = null;
public _renderPanelComponent(props: any) {
const element: React.ReactElement<IMainPanelProps> = React.createElement(MainPanel, assign({
isOpen: false,
onClose: null,
context: this.context
}, props));
ReactDom.render(element, this.panelPlaceHolder);
}
private _showPanel() {
this._renderPanelComponent({
isOpen: true,
onClose: this._dismissPanel,
context: this.context
});
}
private _dismissPanel() {
console.log(typeof(this._renderPanelComponent))
this._renderPanelComponent({ isOpen: false});
}
#override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, 'Initialized CreditInfoCommandSet');
this.panelPlaceHolder = document.body.appendChild(document.createElement("div"));
return Promise.resolve();
}
#override
public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
}
#override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'COMMAND_2':
this._showPanel();
break;
default:
throw new Error('Unknown command');
}
}
}
And this is what my panel looks like
import * as React from 'react';
// Custom imports
import { Panel, PanelType , TextField, Checkbox, DefaultButton, PrimaryButton, Dropdown, IDropdownOption} from 'office-ui-fabric-react';
import style from "./MainPanelStyle.module.scss";
export interface IMainPanelProps {
onClose: () => void;
isOpen: boolean;
}
export interface IMainPanelState {}
export default class MainPanel extends React.Component<IMainPanelProps, IMainPanelState> {
constructor(props: IMainPanelProps, state: IMainPanelState) {
super(props);
this.state = {}
private _onCancel = () => {
this.props.onClose();
}
public render(): React.ReactElement<IMainPanelProps> {
return (
<div>
<Panel isLightDismiss={true} isOpen={this.props.isOpen} type={PanelType.medium} onDismiss={this.props.onClose}>
<DefaultButton className={style.closeButton} text="Close" onClick={this._onCancel}/>
</Panel>
</div>
);
}
}

I ran into this too. Changing this
private _dismissPanel() {
to
private _dismissPanel = () =>{
Got this working for me, as it binds the method to the scope of its class so this can be properly resolved. I realize this is an old post but just in case any one else runs into this issue.

Related

Props undefined when calling a method in a react component

I have a component with a list of child components. When I call a method from the parent component from the child component and, in the code I am using props, I get an
"Uncaught TypeError: Cannot read property 'activeStep' of undefined"
Where activeStep is a member of props.
Here is the relevant part om my code.
Parent component
import { Component, ReactNode, createElement, ReactFragment } from "react";
import { SlidingWizardStepComponent } from "./SlidingWizardStepComponent";
export interface SlidingWizardComponentProps {
activeStep: number;
}
export class SlidingWizardComponent extends Component<SlidingWizardComponentProps> {
getWizard = (): JSX.Element[] => {
if (!this.props.activeStep && this.props.activeStep !== 0) {
return [];
}
return = this.createWizard();
};
private setActiveStep(clickedStep: number): void {
if (clickedStep !== this.props.activeStep) {
this.props.setActiveStep(clickedStep);
}
}
private createWizard(): JSX.Element[] {
const wizardSteps: JSX.Element[] = [];
for (let i = 0; i < this.props.numberOfSteps; i++) {
wizardSteps.push(
<SlidingWizardStepComponent setActiveStep={this.setActiveStep} />
);
}
return wizardSteps;
}
render(): ReactNode {
return (
<div className="slide-wizard">
{this.getWizard()}
</div>
);
}
}
Child component
import { Component, ReactNode, createElement, ReactFragment } from "react";
export interface SlidingWizardStepComponentProps {
setActiveStep: (clickedStep: number) => void;
}
export class SlidingWizardStepComponent extends Component<SlidingWizardStepComponentProps> {
private setActiveStep(): void {
this.props.setActiveStep(this.props.stepIndex);
}
render(): ReactNode {
return (
<div onClick={() => this.setActiveStep()}>
Content
</div>
);
}
}
EDIT: When I add a constructor to the code and I log my props in the constructor, they seem to be set!
EDIT 2: The parent component itself is called by another component that looks like this in its stripped down form:
import { Component, ReactNode, createElement, ReactFragment } from "react";
import { SlidingWizardComponent } from "./components/SlidingWizardComponent";
import Big from "big.js";
import { SlidingWizardContainerProps } from "../typings/SlidingWizardProps";
import "./ui/SlidingWizard.css";
export interface State {
activeStep: number;
clickedStep: number;
}
export default class SlidingWizard extends Component<SlidingWizardContainerProps, State> {
state: State = {
activeStep: parseInt(this.props.activeStep.displayValue, 10),
clickedStep: parseInt(this.props.clickedStep.displayValue, 10)
};
resetActiveStep = (): void => {
this.props.activeStep.setValue(new Big(0));
this.setState({
activeStep: 0
});
};
setActiveStep = (clickedStep: number): void => {
this.props.clickedStep.setValue(new Big(clickedStep));
if (this.props.onActiveStepChange) {
this.props.onActiveStepChange.execute();
}
};
render(): ReactNode {
return (
<SlidingWizardComponent
activeStep={this.state.activeStep}
/>
);
}
}
This widget is used in a Mendix application. The parameters used in this top component are defined through an xml file.
Although I still do not know why the original code did not work, I was able to solve it by:
In the parent component in the createWizard method, change setActiveStep={this.setActiveStep} to setActiveStep={this.props.setActiveStep}
Remove the setActiveStep method from the parent component

TypeError: Cannot read property 'vote' of null in render view

In this component I get a Promise object in the properties, I try to put it in state, but when the view is rendered, I get the message "TypeError: Cannot read property 'vote' of null", asking for a solution to my problem, I spent two hours on it and I don't see the end. What should I do differently?
import { IVoteDetailsProps } from "./IVoteDetailsProps";
import { IVoteDetailsState } from "./IVoteDetailsState";
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>{
constructor(props: IVoteDetailsProps) {
super();
console.log(props)
}
componentDidMount() {
let data = this.props.voteDetails;
data.then(result => this.setState({
vote: result
}));
};
public render(): React.ReactElement<IVoteDetailsState> {
return (
<table >
<tbody>
{this.state.vote && this.state.vote.map(el => {
<tr id={el.id.toString()}>
<td>{el.title}</td>
<td>{el.voteType}</td>
</tr>
})}
</tbody>
</table>
)
}
}
export interface IVoteDetailsProps {
voteDetails: Promise<IVoteDetailsData[]>;
}
export interface IVoteDetailsData{
id: number;
title: string;
voteType: string;
}
import React = require("react");
import { VoteDetails } from "../VoteDetails/VoteDetails";
import { IVoteListProps } from "./IVoteListProps";
export class VoteList extends React.Component<IVoteListProps, {}> {
constructor(props: IVoteListProps) {
super(props);
console.log(props)
}
public render(): React.ReactElement<IVoteListProps> {
// const { vote } = this.state;
return (
<VoteDetails voteDetails={this.props.adminServicePanel.getVotesInfo()} />
)
};
}
public render(): React.ReactElement<IVoteSecurityAppProps> {
return (
<main className="ui main text container">
<VoteList adminServicePanel={this.props.adminPanelService}/>
</main>
);
import {HttpClient} from '#microsoft/sp-http';
import { reject } from 'lodash';
import {IAdminPanelService} from './IAdminPanelService';
import {IReportData} from './IReportData'
import { IVoteDetailsData } from './IVoteDetailsData';
import {IVoteInfo} from './IVoteInfo'
import {VoteOptions} from './VoteOptions';
export class AdminPanelService implements IAdminPanelService {
//////////////////////////////MOCK////////////////////////////////////////////
private voteInfos : IVoteDetailsData[];
private reportData : IReportData[];
//////////////////////////////MOCK////////////////////////////////////////////
constructor(private httpClient: HttpClient, private serverRelativeSiteUrl: string) {
//MOCK
this.voteInfos = [
{
id : 1,
title : "xxx",
voteType : "xx"
},
{
id : 2,
title : "xxx",
voteType : "xxx"
}
];
}
public getVotesInfo () : Promise<IVoteDetailsData[]> {
return new Promise<IVoteDetailsData[]>((resolve : (voteMiniInfo : IVoteDetailsData[]) => void, reject : (error: any) => void): void =>{
resolve(this.voteInfos);
})
}
}
export interface IAdminPanelService {
getVotesInfo:() => Promise<IVoteDetailsData[]>;
}
import * as React from 'react';
import styles from './VoteSecurityApp.module.scss';
import { IVoteSecurityAppProps } from './IVoteSecurityAppProps';
import { escape } from '#microsoft/sp-lodash-subset';
import { VoteList } from './VoteList/VoteList';
export default class VoteSecurityApp extends React.Component<IVoteSecurityAppProps, {}> {
public render(): React.ReactElement<IVoteSecurityAppProps> {
return (
<main className="ui main text container">
<VoteList adminServicePanel={this.props.adminPanelService}/>
</main>
);
}
}
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>
{
state = {
vote: null,
}
// change this
componentDidMount() {
this.props.voteDetails().then(result => this.setState({
vote: result
}));
};
// rest of your codes here
}
export class VoteList extends React.Component<IVoteListProps, {}> {
constructor(props: IVoteListProps) {
super(props);
console.log(props)
}
public render(): React.ReactElement<IVoteListProps> {
// const { vote } = this.state;
return (
<VoteDetails voteDetails=
{this.props.adminServicePanel.getVotesInfo} /> // change this line
)
};
}
All errors "TypeError: Cannot read property '......' of null" in spfx components, when you call
this.state.{varname}
or
this.props.{varname}
solves one of:
Add binding 'this' in constructor to method where rise error
this.{methodname} = this.{methodname}.bind(this)
You miss initialize state in constructor (for React.Component<props,state>)
this.state = {};
You use value from props or state and miss check it for null
In question I see all of this things. For example, in this peice of code state will not be initialized, otherwise component has state
export class VoteDetails extends React.Component<IVoteDetailsProps, IVoteDetailsState>{
constructor(props: IVoteListProps) {
super(props);
console.log(props)
//this.state == null - true
}
}
Second problem is this code
<VoteDetails voteDetails={this.props.adminServicePanel.getVotesInfo()}
getVotesInfo- return promise, not data. This bad practice, use state to hold data, for example
constructor(props: ...){
super(props);
this.state{
data: null
};
this._getData = this._getData.bind(this);
}
componentDidMount(){
this._getData();
}
async _getData(){
if(this.props.adminServicePanel){
let data = await this.props.adminServicePanel.getVotesInfo();
this.setStae({data});
}
}
render():...{
const data = this.state.data;
return(
{data && data.map(...)}
);
}

TSX + React: Uncaught TypeError: Cannot read property 'setState' of undefined at Menu.handleClick

I am using typescript#2.6.1 and react and react-dom #15.6.2.
I can't seem to debug this issue:
Uncaught TypeError: Cannot read property 'setState' of undefined at Menu.handleClick.
This is at this.setState( (prevState) => {
import * as React from "react";
import * as ReactDOM from 'react-dom';
import Monaco from './Monaco';
import shiftOverlayButtons from '../common/shiftOverlayButtons';
export interface Props {
}
export interface State {
isOpen: boolean;
}
export default class Menu extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
}
public state: State = {
isOpen: false
};
public render() {
return <button id="overlay-btn" className="menu-btn" onClick={this.handleClick}></button>
}
public handleClick(): void {
this.setState( (prevState) => {
if(prevState.isOpen === false){
this.openMenu();
return {
isOpen: true
};
}
else {
this.closeMenu();
return {
isOpen: false
}
}
});
}
private openMenu(): void {
shiftOverlayButtons(true);
ReactDOM.render(<Monaco/>, document.getElementById("overlay"));
}
private closeMenu(): void {
shiftOverlayButtons(false);
//ReactDOM.unmountComponentAtNode(document.getElementById("overlay"));
}
}
ReactDOM.render(<Menu/>, document.body);
PS: When I do this.handleClick.bind(this) I get another error: GET file:///Users/kalpa/Desktop/monaco-sample/vs/loader.js net::ERR_FILE_NOT_FOUND ... I am using webpack #3.8.1
Prefer using an arrow function for handlers. Change
public handleClick(): void {
to
public handleClick = (): void => {
More
Arrow functions: https://basarat.gitbooks.io/typescript/docs/arrow-functions.html
You are missing the bind at handle click. So from
onClick={this.handleClick}
to
onClick={this.handleClick.bind(this)}
Reformated Code:
handleClick(event) {
this.setState({isOpen: !isOpen}).then(()=>{
if(!this.state.isOpen){
//do something when false
} else {
// do something when true
}}}

Property 'propTypes' does not exist on type 'typeof FactoryMethod

I have the following component and I want to validate one of the properties, but I get this error
Property 'propTypes' does not exist on type 'typeof FactoryMethod
It looks ok to me according to the documentation
//#region Imports
import * as React from "react";
import styles from "./FactoryMethod.module.scss";
import { IFactoryMethodProps } from "./IFactoryMethodProps";
import {
IDetailsListItemState,
IDetailsNewsListItemState,
IDetailsDirectoryListItemState,
IDetailsAnnouncementListItemState,
IFactoryMethodState
} from "./IFactoryMethodState";
import { IListItem } from "./models/IListItem";
import { IAnnouncementListItem } from "./models/IAnnouncementListItem";
import { INewsListItem } from "./models/INewsListItem";
import { IDirectoryListItem } from "./models/IDirectoryListItem";
import { escape } from "#microsoft/sp-lodash-subset";
import { SPHttpClient, SPHttpClientResponse } from "#microsoft/sp-http";
import { ListItemFactory} from "./ListItemFactory";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import {
DetailsList,
DetailsListLayoutMode,
Selection,
IColumn
} from "office-ui-fabric-react/lib/DetailsList";
import { MarqueeSelection } from "office-ui-fabric-react/lib/MarqueeSelection";
import { autobind } from "office-ui-fabric-react/lib/Utilities";
import PropTypes from "prop-types";
//#endregion
export default class FactoryMethod extends React.Component<IFactoryMethodProps, IFactoryMethodState> {
private listItemEntityTypeName: string = undefined;
private _selection: Selection;
constructor(props: IFactoryMethodProps, state: any) {
super(props);
this.setInitialState();
this._configureWebPart = this._configureWebPart.bind(this);
}
public componentWillReceiveProps(nextProps: IFactoryMethodProps): void {
this.listItemEntityTypeName = undefined;
this.setInitialState();
}
public componentDidMount(): void {
this.readItemsAndSetStatus();
}
public setInitialState(): void {
this.state = {
type: "ListItem",
status: this.listNotConfigured(this.props)
? "Please configure list in Web Part properties"
: "Ready",
DetailsListItemState:{
columns:[],
items:[]
},
DetailsNewsListItemState:{
columns:[],
items:[]
},
DetailsDirectoryListItemState:{
columns:[],
items:[]
},
DetailsAnnouncementListItemState:{
columns:[],
items:[]
},
};
}
private _configureWebPart(): void {
this.props.configureStartCallback();
}
// reusable inline component
public ListMarqueeSelection = (itemState: {columns: IColumn[], items: IListItem[] }) => (
<div>
<MarqueeSelection selection={ this._selection }>
<DetailsList
items={ itemState.items }
columns={ itemState.columns }
setKey="set"
layoutMode={ DetailsListLayoutMode.fixedColumns }
selection={ this._selection }
selectionPreservedOnEmptyClick={ true }
compact={ true }>
</DetailsList>
</MarqueeSelection>
</div>
)
public render(): React.ReactElement<IFactoryMethodProps> {
this.readItemsAndSetStatus();
switch(this.props.listName) {
case "GenericList":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsListItemState.items} columns={this.state.DetailsListItemState.columns} />;
case "News":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsNewsListItemState.items} columns={this.state.DetailsNewsListItemState.columns}/>;
case "Announcements":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsAnnouncementListItemState.items} columns={this.state.DetailsAnnouncementListItemState.columns}/>;
case "Directory":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items={this.state.DetailsDirectoryListItemState.items} columns={this.state.DetailsDirectoryListItemState.columns}/>;
default:
return null;
}
}
// read items using factory method pattern and sets state accordingly
private readItemsAndSetStatus(): void {
this.setState({
status: "Loading all items..."
});
const factory: ListItemFactory = new ListItemFactory();
const items: IListItem[] = factory.getItems(this.props.spHttpClient, this.props.siteUrl, this.props.listName);
const keyPart: string = this.props.listName === "GenericList" ? "" : this.props.listName;
if(items != null )
{
// the explicit specification of the type argument `keyof {}` is bad and
// it should not be required.
this.setState<keyof {}>({
status: `Successfully loaded ${items.length} items`,
["Details" + keyPart + "ListItemState"] : {
items,
columns: [
]
}
});
}
}
private listNotConfigured(props: IFactoryMethodProps): boolean {
return props.listName === undefined ||
props.listName === null ||
props.listName.length === 0;
}
}
FactoryMethod.propTypes = {
listName: React.PropTypes.oneOf(["GenericList","Announcements","News"])
}
Update 1:
Update 2
IFactoryMethodProps
import { SPHttpClient } from "#microsoft/sp-http";
import IDataProvider from "./dataproviders/IDataProvider";
export interface IFactoryMethodProps {
listName: string;
spHttpClient: SPHttpClient;
siteUrl: string;
dataProvider: IDataProvider;
configureStartCallback: () => void;
}
I think you should do:
⌄
import type { IFactoryMethodProps } from "./IFactoryMethodProps";
because it looks like you are importing a type alias.
https://flow.org/en/docs/types/modules/
It looks as though you might be exporting before attaching propTypes.
Try to change this:
export default class FactoryMethod
To this:
class FactoryMethod
Then, at the bottom do this:
FactoryMethod.propTypes = {
listName: React.PropTypes.oneOf(["GenericList","Announcements","News"])
}
export default FactoryMethod;

React/Flux store doesn't change it's state

From 2 weeks ago I'm facing a problem in my React/Flux app. It's done in ES6 and using webpack and babel.
It actually doesn't go inside the _onChange method ones the store emit the change event. So the component itself doesn't render again with the modified state.
Here you can take a look to my component:
import React from 'react';
import Item from 'components/item/item';
import Actions from './item-list.actions';
import Store from './item-list.store';
const StoreInstance = new Store();
class ItemList extends React.Component {
constructor(props) {
super(props);
this._onChange = this._onChange.bind(this);
this.state = this.getItemListState();
}
componentWillMount() {
StoreInstance.addChangeListener(this._onChange);
Actions.requestFlats(Actions.setFlats);
}
componentWillUnmount() {
StoreInstance.removeChangeListener(this._onChange);
}
_onChange() {
this.setState(this.getItemListState);
}
getItemListState() {
return {
flats: StoreInstance.getFlats()
}
}
render() {
return(
<ul className="item__list">{
this.state.flats.map((flat, index) => {
<li className="col-xs-12 col-sm-12 col-md-6 col-lg-6">
<Item key={index} flat={flat}></Item>
</li>
})
}</ul>
);
}
}
export default ItemList;
My actions:
import AppDispatcher from 'services/dispacher/dispacher';
import Constants from './item-list.constants';
let ItemListActions = {
getFlats: () => {
AppDispatcher.handleAction({
type: Constants.GET_FLATS,
data: {}
});
},
setFlats: (flats) => {
AppDispatcher.handleAction({
type: Constants.SET_FLATS,
data: {
flats
}
});
},
requestFlats: (callback) => {
AppDispatcher.handleAction({
type: Constants.REQUEST_FLATS,
data: {
callback
}
});
}
};
export default ItemListActions;
And store:
import AppDispatcher from 'services/dispacher/dispacher';
import AppStore from 'services/store/store';
import Api from './item-list.api';
import Constants from './item-list.constants';
class ItemListStore extends AppStore {
constructor() {
super();
this.flats = [];
}
requestFlats(callback) {
Api.getFlats(callback);
}
getFlats() {
return this.flats;
}
setFlats(flats) {
this.flats = flats;
}
}
const ItemListStoreInstance = new ItemListStore();
AppDispatcher.register((payload) => {
let action = payload.action;
switch (action.type) {
case Constants.GET_FLATS:
ItemListStoreInstance.getFlats(action.data);
break;
case Constants.SET_FLATS:
ItemListStoreInstance.setFlats(action.data.flats);
break;
case Constants.REQUEST_FLATS:
ItemListStoreInstance.requestFlats(action.data.callback);
break;
default:
return true;
}
ItemListStoreInstance.emitChange();
});
export default ItemListStore;
which extends of AppStore
import EventEmitter from 'events';
const CHANGE_EVENT = 'change';
class Store extends EventEmitter {
constructor() {
super();
}
emitChange() {
this.emit(CHANGE_EVENT);
}
addChangeListener(callback) {
this.on(CHANGE_EVENT, callback);
}
removeChangeListener(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
}
Store.dispatchToken = null;
export default Store;
I have check this code many times and looking at examples over the whole Internet and I got no success.
I's supposed that when I do:
StoreInstance.addChangeListener(this._onChange);
the store will listen for my change event, but looks like it doesn't.
When I got the new data from the API, I execute setFlats and _onChange is not executed, so no changes on the UI are shown.
Do you see any issue in this code? Anything that could help me to solve it?
Thanks in advance.
I don't see any usage of you ItemListStore anywhere. Your component is using the "Store" class, which only extends EventEmitter. The connection to the ItemListStore is nowhere to be found.
This line (In your ItemListStore):
ItemListStoreInstance.emitChange();
will not trigger the emitChange() method in your Store.
The problem was actually in the store which was returning the ItemListStore instead of an instance of ItemListStore and then in the component I was having another instance, that's why it wasn't communication with each other.
Here is the fixed code for the ItemListStore:
import AppDispatcher from 'services/dispacher/dispacher';
import AppStore from 'services/store/store';
import Api from './item-list.api';
import Constants from './item-list.constants';
class ItemListStore extends AppStore {
constructor() {
super();
this.flats = [];
}
requestFlats(callback) {
Api.getFlats(callback);
}
getFlats() {
return this.flats;
}
setFlats(flats) {
this.flats = flats;
}
}
const ItemListStoreInstance = new ItemListStore();
AppDispatcher.register((payload) => {
let action = payload.action;
switch (action.type) {
case Constants.GET_FLATS:
ItemListStoreInstance.getFlats(action.data);
break;
case Constants.SET_FLATS:
ItemListStoreInstance.setFlats(action.data.flats);
break;
case Constants.REQUEST_FLATS:
ItemListStoreInstance.requestFlats(action.data.callback);
break;
default:
return true;
}
ItemListStoreInstance.emitChange();
});
export default ItemListStoreInstance;

Resources