Factory method implemented in react

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
0
down vote

favorite












A Factory Pattern or Factory Method Pattern says that just define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate. In other words, subclasses are responsible to create the instance of the class.



Advantage of Factory Design Pattern



Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code. That means the code interacts solely with the resultant interface or abstract class, so that it will work with any classes that implement that interface or that extends that abstract class.



A factory method is a method of a factory that builds objects.



With that said, I built a React component that uses this pattern, functionally it works perfect, but I would like to know if the pattern is properly implemented or not and what your feedback as an expert is on this scenario.



enter image description here



ListItemFactory.ts



import SPHttpClient, SPHttpClientResponse from "@microsoft/sp-http";
import IWebPartContext from "@microsoft/sp-webpart-base";
import IListItem from "./models/IListItem";
import IFactory from "./IFactory";
import INewsListItem from "./models/INewsListItem";
import IDirectoryListItem from "./models/IDirectoryListItem";
import IAnnouncementListItem from "./models/IAnnouncementListItem";

export class ListItemFactory implements IFactory
// private _listItems: IListItem;
public getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<any>
switch(listName)
case "GenericList":
let items: IListItem;
// tslint:disable-next-line:max-line-length
return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
SPHttpClient.configurations.v1,

headers:
"Accept": "application/json;odata=nometadata",
"odata-version": ""

)
.then((response: SPHttpClientResponse): Promise< value: IListItem > =>
return response.json();
)
.then((json: value: IListItem ) =>
console.log(JSON.stringify(json.value));
return items=json.value.map((v,i)=>(

// key: v.id,
id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title

));
);
case "News":
let newsitems: INewsListItem;
// tslint:disable-next-line:max-line-length
return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
SPHttpClient.configurations.v1,

headers:
"Accept": "application/json;odata=nometadata",
"odata-version": ""

)
.then((response: SPHttpClientResponse): Promise< value: INewsListItem > =>
return response.json();
)
.then((json: value: INewsListItem ) =>
return newsitems=json.value.map((v,i)=>(

id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title,
newsheader: v.newsheader,
newsbody: v.newsbody,
expiryDate: v.expiryDate

));
);
case "Announcements":
let announcementitems: IAnnouncementListItem;
return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Created,Author/Title,Modified,Editor/Title,announcementBody,expiryDate&$expand=Author,Editor`,
SPHttpClient.configurations.v1,

headers:
"Accept": "application/json;odata=nometadata",
"odata-version": ""

)
.then((response: SPHttpClientResponse): Promise< value: IAnnouncementListItem > =>
return response.json();
)
.then((json: value: IAnnouncementListItem ) =>
return announcementitems=json.value.map((v,i)=>(

id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title,
announcementBody: v.announcementBody,
expiryDate: v.expiryDate

));
);
case "Directory":
let directoryitems: IDirectoryListItem;
return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id`,
SPHttpClient.configurations.v1,

headers:
"Accept": "application/json;odata=nometadata",
"odata-version": ""

)
.then((response: SPHttpClientResponse): Promise< value: IDirectoryListItem > =>
return response.json();
)
.then((json: value: IDirectoryListItem ) =>
return directoryitems=json.value.map((v,i)=>(

id: v.Id,
title: v.Title,
created: v.Created,
createdby: v.Author.Title,
modified: v.Modified,
modifiedby: v.Editor.Title,
firstName: v.firstName,
lastName: v.lastName,
mobileNumber: v.mobileNumber,
internalNumber: v.internalNumber

));
);
default:
break;





Ifactorystate.ts



import IListItem from "./models/IListItem";
import INewsListItem from "./models/INewsListItem";
import IDirectoryListItem from "./models/IDirectoryListItem";
import IAnnouncementListItem from "./models/IAnnouncementListItem";
import
IColumn
from "office-ui-fabric-react/lib/DetailsList";

export interface IFactoryMethodState
hasError: boolean;
status: string;
columns: IColumn;
DetailsListItemState: IDetailsListItemState;
DetailsNewsListItemState: IDetailsNewsListItemState;
DetailsDirectoryListItemState : IDetailsDirectoryListItemState;
DetailsAnnouncementsListItemState : IDetailsAnnouncementListItemState;


export interface IDetailsListItemState
items: IListItem;


export interface IDetailsNewsListItemState
items: INewsListItem;


export interface IDetailsDirectoryListItemState
items: IDirectoryListItem;


export interface IDetailsAnnouncementListItemState
items: IAnnouncementListItem;



The component:



//#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,
buildColumns,
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>
constructor(props: IFactoryMethodProps, state: any)
super(props);
this.setInitialState();



// lifecycle help here: https://staminaloops.github.io/undefinedisnotafunction/understanding-react/
//#region Mouting events lifecycle
// the data returned from render is neither a string nor a DOM node.
// it's a lightweight description of what the DOM should look like.
// inspects this.state and this.props and create the markup.
// when your data changes, the render method is called again.
// react diff the return value from the previous call to render with
// the new one, and generate a minimal set of changes to be applied to the DOM.
public render(): React.ReactElement<IFactoryMethodProps>
if (this.state.hasError)
// you can render any custom fallback UI
return <h1>Something went wrong.</h1>;
else
switch(this.props.listName)
case "GenericList":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items=this.state.DetailsListItemState.items columns=this.state.columns />;
case "News":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items=this.state.DetailsNewsListItemState.items columns=this.state.columns/>;
case "Announcements":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items=this.state.DetailsAnnouncementsListItemState.items columns=this.state.columns/>;
case "Directory":
// tslint:disable-next-line:max-line-length
return <this.ListMarqueeSelection items=this.state.DetailsDirectoryListItemState.items columns=this.state.columns/>;
default:
return null;




public componentDidCatch(error: any, info: any): void
// display fallback UI
this.setState( hasError: true );
// you can also log the error to an error reporting service
console.log(error);
console.log(info);




// componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here.
// if you need to load data from a remote endpoint, this is a good place to instantiate the network request.
// this method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in componentWillUnmount().
// calling setState() in this method will trigger an extra rendering, but it is guaranteed to flush during the same tick.
// this guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state.
// use this pattern with caution because it often causes performance issues. It can, however, be necessary for cases like modals and
// tooltips when you need to measure a DOM node before rendering something that depends on its size or position.
public componentDidMount(): void
this._configureWebPart = this._configureWebPart.bind(this);
this.readItemsAndSetStatus(this.props.listName);


//#endregion
//#region Props changes lifecycle events (after a property changes from parent component)
// componentWillReceiveProps() is invoked before a mounted component receives new props.
// if you need to update the state in response to prop
// changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions
// using this.setState() in this method.
// note that React may call this method even if the props have not changed, so make sure to compare the current
// and next values if you only want to handle changes.
// this may occur when the parent component causes your component to re-render.
// react doesn’t call componentWillReceiveProps() with initial props during mounting. It only calls this
// method if some of component’s props may update
// calling this.setState() generally doesn’t trigger componentWillReceiveProps()
public componentWillReceiveProps(nextProps: IFactoryMethodProps): void
if(nextProps.listName !== this.props.listName)
this.readItemsAndSetStatus(nextProps.listName);



//#endregion
//#region private methods
private _configureWebPart(): void
this.props.configureStartCallback();


public setInitialState(): void
this.state =
hasError: false,
status: this.listNotConfigured(this.props)
? "Please configure list in Web Part properties"
: "Ready",
columns:,
DetailsListItemState:
items:
,
DetailsNewsListItemState:
items:
,
DetailsDirectoryListItemState:
items:
,
DetailsAnnouncementsListItemState:
items:
,
;


// reusable inline component
private ListMarqueeSelection = (itemState: columns: IColumn, items: IListItem ) => (
<div>
<DetailsList
items= itemState.items
columns= itemState.columns
setKey="set"
layoutMode= DetailsListLayoutMode.fixedColumns
selectionPreservedOnEmptyClick= true
compact= true >
</DetailsList>
</div>
)

// read items using factory method pattern and sets state accordingly
private readItemsAndSetStatus(listName): void

private listNotConfigured(props: IFactoryMethodProps): boolean
props.listName === null

//#endregion







share|improve this question



























    up vote
    0
    down vote

    favorite












    A Factory Pattern or Factory Method Pattern says that just define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate. In other words, subclasses are responsible to create the instance of the class.



    Advantage of Factory Design Pattern



    Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code. That means the code interacts solely with the resultant interface or abstract class, so that it will work with any classes that implement that interface or that extends that abstract class.



    A factory method is a method of a factory that builds objects.



    With that said, I built a React component that uses this pattern, functionally it works perfect, but I would like to know if the pattern is properly implemented or not and what your feedback as an expert is on this scenario.



    enter image description here



    ListItemFactory.ts



    import SPHttpClient, SPHttpClientResponse from "@microsoft/sp-http";
    import IWebPartContext from "@microsoft/sp-webpart-base";
    import IListItem from "./models/IListItem";
    import IFactory from "./IFactory";
    import INewsListItem from "./models/INewsListItem";
    import IDirectoryListItem from "./models/IDirectoryListItem";
    import IAnnouncementListItem from "./models/IAnnouncementListItem";

    export class ListItemFactory implements IFactory
    // private _listItems: IListItem;
    public getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<any>
    switch(listName)
    case "GenericList":
    let items: IListItem;
    // tslint:disable-next-line:max-line-length
    return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
    SPHttpClient.configurations.v1,

    headers:
    "Accept": "application/json;odata=nometadata",
    "odata-version": ""

    )
    .then((response: SPHttpClientResponse): Promise< value: IListItem > =>
    return response.json();
    )
    .then((json: value: IListItem ) =>
    console.log(JSON.stringify(json.value));
    return items=json.value.map((v,i)=>(

    // key: v.id,
    id: v.Id,
    title: v.Title,
    created: v.Created,
    createdby: v.Author.Title,
    modified: v.Modified,
    modifiedby: v.Editor.Title

    ));
    );
    case "News":
    let newsitems: INewsListItem;
    // tslint:disable-next-line:max-line-length
    return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
    SPHttpClient.configurations.v1,

    headers:
    "Accept": "application/json;odata=nometadata",
    "odata-version": ""

    )
    .then((response: SPHttpClientResponse): Promise< value: INewsListItem > =>
    return response.json();
    )
    .then((json: value: INewsListItem ) =>
    return newsitems=json.value.map((v,i)=>(

    id: v.Id,
    title: v.Title,
    created: v.Created,
    createdby: v.Author.Title,
    modified: v.Modified,
    modifiedby: v.Editor.Title,
    newsheader: v.newsheader,
    newsbody: v.newsbody,
    expiryDate: v.expiryDate

    ));
    );
    case "Announcements":
    let announcementitems: IAnnouncementListItem;
    return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Created,Author/Title,Modified,Editor/Title,announcementBody,expiryDate&$expand=Author,Editor`,
    SPHttpClient.configurations.v1,

    headers:
    "Accept": "application/json;odata=nometadata",
    "odata-version": ""

    )
    .then((response: SPHttpClientResponse): Promise< value: IAnnouncementListItem > =>
    return response.json();
    )
    .then((json: value: IAnnouncementListItem ) =>
    return announcementitems=json.value.map((v,i)=>(

    id: v.Id,
    title: v.Title,
    created: v.Created,
    createdby: v.Author.Title,
    modified: v.Modified,
    modifiedby: v.Editor.Title,
    announcementBody: v.announcementBody,
    expiryDate: v.expiryDate

    ));
    );
    case "Directory":
    let directoryitems: IDirectoryListItem;
    return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id`,
    SPHttpClient.configurations.v1,

    headers:
    "Accept": "application/json;odata=nometadata",
    "odata-version": ""

    )
    .then((response: SPHttpClientResponse): Promise< value: IDirectoryListItem > =>
    return response.json();
    )
    .then((json: value: IDirectoryListItem ) =>
    return directoryitems=json.value.map((v,i)=>(

    id: v.Id,
    title: v.Title,
    created: v.Created,
    createdby: v.Author.Title,
    modified: v.Modified,
    modifiedby: v.Editor.Title,
    firstName: v.firstName,
    lastName: v.lastName,
    mobileNumber: v.mobileNumber,
    internalNumber: v.internalNumber

    ));
    );
    default:
    break;





    Ifactorystate.ts



    import IListItem from "./models/IListItem";
    import INewsListItem from "./models/INewsListItem";
    import IDirectoryListItem from "./models/IDirectoryListItem";
    import IAnnouncementListItem from "./models/IAnnouncementListItem";
    import
    IColumn
    from "office-ui-fabric-react/lib/DetailsList";

    export interface IFactoryMethodState
    hasError: boolean;
    status: string;
    columns: IColumn;
    DetailsListItemState: IDetailsListItemState;
    DetailsNewsListItemState: IDetailsNewsListItemState;
    DetailsDirectoryListItemState : IDetailsDirectoryListItemState;
    DetailsAnnouncementsListItemState : IDetailsAnnouncementListItemState;


    export interface IDetailsListItemState
    items: IListItem;


    export interface IDetailsNewsListItemState
    items: INewsListItem;


    export interface IDetailsDirectoryListItemState
    items: IDirectoryListItem;


    export interface IDetailsAnnouncementListItemState
    items: IAnnouncementListItem;



    The component:



    //#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,
    buildColumns,
    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>
    constructor(props: IFactoryMethodProps, state: any)
    super(props);
    this.setInitialState();



    // lifecycle help here: https://staminaloops.github.io/undefinedisnotafunction/understanding-react/
    //#region Mouting events lifecycle
    // the data returned from render is neither a string nor a DOM node.
    // it's a lightweight description of what the DOM should look like.
    // inspects this.state and this.props and create the markup.
    // when your data changes, the render method is called again.
    // react diff the return value from the previous call to render with
    // the new one, and generate a minimal set of changes to be applied to the DOM.
    public render(): React.ReactElement<IFactoryMethodProps>
    if (this.state.hasError)
    // you can render any custom fallback UI
    return <h1>Something went wrong.</h1>;
    else
    switch(this.props.listName)
    case "GenericList":
    // tslint:disable-next-line:max-line-length
    return <this.ListMarqueeSelection items=this.state.DetailsListItemState.items columns=this.state.columns />;
    case "News":
    // tslint:disable-next-line:max-line-length
    return <this.ListMarqueeSelection items=this.state.DetailsNewsListItemState.items columns=this.state.columns/>;
    case "Announcements":
    // tslint:disable-next-line:max-line-length
    return <this.ListMarqueeSelection items=this.state.DetailsAnnouncementsListItemState.items columns=this.state.columns/>;
    case "Directory":
    // tslint:disable-next-line:max-line-length
    return <this.ListMarqueeSelection items=this.state.DetailsDirectoryListItemState.items columns=this.state.columns/>;
    default:
    return null;




    public componentDidCatch(error: any, info: any): void
    // display fallback UI
    this.setState( hasError: true );
    // you can also log the error to an error reporting service
    console.log(error);
    console.log(info);




    // componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here.
    // if you need to load data from a remote endpoint, this is a good place to instantiate the network request.
    // this method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in componentWillUnmount().
    // calling setState() in this method will trigger an extra rendering, but it is guaranteed to flush during the same tick.
    // this guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state.
    // use this pattern with caution because it often causes performance issues. It can, however, be necessary for cases like modals and
    // tooltips when you need to measure a DOM node before rendering something that depends on its size or position.
    public componentDidMount(): void
    this._configureWebPart = this._configureWebPart.bind(this);
    this.readItemsAndSetStatus(this.props.listName);


    //#endregion
    //#region Props changes lifecycle events (after a property changes from parent component)
    // componentWillReceiveProps() is invoked before a mounted component receives new props.
    // if you need to update the state in response to prop
    // changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions
    // using this.setState() in this method.
    // note that React may call this method even if the props have not changed, so make sure to compare the current
    // and next values if you only want to handle changes.
    // this may occur when the parent component causes your component to re-render.
    // react doesn’t call componentWillReceiveProps() with initial props during mounting. It only calls this
    // method if some of component’s props may update
    // calling this.setState() generally doesn’t trigger componentWillReceiveProps()
    public componentWillReceiveProps(nextProps: IFactoryMethodProps): void
    if(nextProps.listName !== this.props.listName)
    this.readItemsAndSetStatus(nextProps.listName);



    //#endregion
    //#region private methods
    private _configureWebPart(): void
    this.props.configureStartCallback();


    public setInitialState(): void
    this.state =
    hasError: false,
    status: this.listNotConfigured(this.props)
    ? "Please configure list in Web Part properties"
    : "Ready",
    columns:,
    DetailsListItemState:
    items:
    ,
    DetailsNewsListItemState:
    items:
    ,
    DetailsDirectoryListItemState:
    items:
    ,
    DetailsAnnouncementsListItemState:
    items:
    ,
    ;


    // reusable inline component
    private ListMarqueeSelection = (itemState: columns: IColumn, items: IListItem ) => (
    <div>
    <DetailsList
    items= itemState.items
    columns= itemState.columns
    setKey="set"
    layoutMode= DetailsListLayoutMode.fixedColumns
    selectionPreservedOnEmptyClick= true
    compact= true >
    </DetailsList>
    </div>
    )

    // read items using factory method pattern and sets state accordingly
    private readItemsAndSetStatus(listName): void

    private listNotConfigured(props: IFactoryMethodProps): boolean
    props.listName === null

    //#endregion







    share|improve this question























      up vote
      0
      down vote

      favorite









      up vote
      0
      down vote

      favorite











      A Factory Pattern or Factory Method Pattern says that just define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate. In other words, subclasses are responsible to create the instance of the class.



      Advantage of Factory Design Pattern



      Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code. That means the code interacts solely with the resultant interface or abstract class, so that it will work with any classes that implement that interface or that extends that abstract class.



      A factory method is a method of a factory that builds objects.



      With that said, I built a React component that uses this pattern, functionally it works perfect, but I would like to know if the pattern is properly implemented or not and what your feedback as an expert is on this scenario.



      enter image description here



      ListItemFactory.ts



      import SPHttpClient, SPHttpClientResponse from "@microsoft/sp-http";
      import IWebPartContext from "@microsoft/sp-webpart-base";
      import IListItem from "./models/IListItem";
      import IFactory from "./IFactory";
      import INewsListItem from "./models/INewsListItem";
      import IDirectoryListItem from "./models/IDirectoryListItem";
      import IAnnouncementListItem from "./models/IAnnouncementListItem";

      export class ListItemFactory implements IFactory
      // private _listItems: IListItem;
      public getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<any>
      switch(listName)
      case "GenericList":
      let items: IListItem;
      // tslint:disable-next-line:max-line-length
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: IListItem > =>
      return response.json();
      )
      .then((json: value: IListItem ) =>
      console.log(JSON.stringify(json.value));
      return items=json.value.map((v,i)=>(

      // key: v.id,
      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title

      ));
      );
      case "News":
      let newsitems: INewsListItem;
      // tslint:disable-next-line:max-line-length
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: INewsListItem > =>
      return response.json();
      )
      .then((json: value: INewsListItem ) =>
      return newsitems=json.value.map((v,i)=>(

      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title,
      newsheader: v.newsheader,
      newsbody: v.newsbody,
      expiryDate: v.expiryDate

      ));
      );
      case "Announcements":
      let announcementitems: IAnnouncementListItem;
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Created,Author/Title,Modified,Editor/Title,announcementBody,expiryDate&$expand=Author,Editor`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: IAnnouncementListItem > =>
      return response.json();
      )
      .then((json: value: IAnnouncementListItem ) =>
      return announcementitems=json.value.map((v,i)=>(

      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title,
      announcementBody: v.announcementBody,
      expiryDate: v.expiryDate

      ));
      );
      case "Directory":
      let directoryitems: IDirectoryListItem;
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: IDirectoryListItem > =>
      return response.json();
      )
      .then((json: value: IDirectoryListItem ) =>
      return directoryitems=json.value.map((v,i)=>(

      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title,
      firstName: v.firstName,
      lastName: v.lastName,
      mobileNumber: v.mobileNumber,
      internalNumber: v.internalNumber

      ));
      );
      default:
      break;





      Ifactorystate.ts



      import IListItem from "./models/IListItem";
      import INewsListItem from "./models/INewsListItem";
      import IDirectoryListItem from "./models/IDirectoryListItem";
      import IAnnouncementListItem from "./models/IAnnouncementListItem";
      import
      IColumn
      from "office-ui-fabric-react/lib/DetailsList";

      export interface IFactoryMethodState
      hasError: boolean;
      status: string;
      columns: IColumn;
      DetailsListItemState: IDetailsListItemState;
      DetailsNewsListItemState: IDetailsNewsListItemState;
      DetailsDirectoryListItemState : IDetailsDirectoryListItemState;
      DetailsAnnouncementsListItemState : IDetailsAnnouncementListItemState;


      export interface IDetailsListItemState
      items: IListItem;


      export interface IDetailsNewsListItemState
      items: INewsListItem;


      export interface IDetailsDirectoryListItemState
      items: IDirectoryListItem;


      export interface IDetailsAnnouncementListItemState
      items: IAnnouncementListItem;



      The component:



      //#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,
      buildColumns,
      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>
      constructor(props: IFactoryMethodProps, state: any)
      super(props);
      this.setInitialState();



      // lifecycle help here: https://staminaloops.github.io/undefinedisnotafunction/understanding-react/
      //#region Mouting events lifecycle
      // the data returned from render is neither a string nor a DOM node.
      // it's a lightweight description of what the DOM should look like.
      // inspects this.state and this.props and create the markup.
      // when your data changes, the render method is called again.
      // react diff the return value from the previous call to render with
      // the new one, and generate a minimal set of changes to be applied to the DOM.
      public render(): React.ReactElement<IFactoryMethodProps>
      if (this.state.hasError)
      // you can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
      else
      switch(this.props.listName)
      case "GenericList":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsListItemState.items columns=this.state.columns />;
      case "News":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsNewsListItemState.items columns=this.state.columns/>;
      case "Announcements":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsAnnouncementsListItemState.items columns=this.state.columns/>;
      case "Directory":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsDirectoryListItemState.items columns=this.state.columns/>;
      default:
      return null;




      public componentDidCatch(error: any, info: any): void
      // display fallback UI
      this.setState( hasError: true );
      // you can also log the error to an error reporting service
      console.log(error);
      console.log(info);




      // componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here.
      // if you need to load data from a remote endpoint, this is a good place to instantiate the network request.
      // this method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in componentWillUnmount().
      // calling setState() in this method will trigger an extra rendering, but it is guaranteed to flush during the same tick.
      // this guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state.
      // use this pattern with caution because it often causes performance issues. It can, however, be necessary for cases like modals and
      // tooltips when you need to measure a DOM node before rendering something that depends on its size or position.
      public componentDidMount(): void
      this._configureWebPart = this._configureWebPart.bind(this);
      this.readItemsAndSetStatus(this.props.listName);


      //#endregion
      //#region Props changes lifecycle events (after a property changes from parent component)
      // componentWillReceiveProps() is invoked before a mounted component receives new props.
      // if you need to update the state in response to prop
      // changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions
      // using this.setState() in this method.
      // note that React may call this method even if the props have not changed, so make sure to compare the current
      // and next values if you only want to handle changes.
      // this may occur when the parent component causes your component to re-render.
      // react doesn’t call componentWillReceiveProps() with initial props during mounting. It only calls this
      // method if some of component’s props may update
      // calling this.setState() generally doesn’t trigger componentWillReceiveProps()
      public componentWillReceiveProps(nextProps: IFactoryMethodProps): void
      if(nextProps.listName !== this.props.listName)
      this.readItemsAndSetStatus(nextProps.listName);



      //#endregion
      //#region private methods
      private _configureWebPart(): void
      this.props.configureStartCallback();


      public setInitialState(): void
      this.state =
      hasError: false,
      status: this.listNotConfigured(this.props)
      ? "Please configure list in Web Part properties"
      : "Ready",
      columns:,
      DetailsListItemState:
      items:
      ,
      DetailsNewsListItemState:
      items:
      ,
      DetailsDirectoryListItemState:
      items:
      ,
      DetailsAnnouncementsListItemState:
      items:
      ,
      ;


      // reusable inline component
      private ListMarqueeSelection = (itemState: columns: IColumn, items: IListItem ) => (
      <div>
      <DetailsList
      items= itemState.items
      columns= itemState.columns
      setKey="set"
      layoutMode= DetailsListLayoutMode.fixedColumns
      selectionPreservedOnEmptyClick= true
      compact= true >
      </DetailsList>
      </div>
      )

      // read items using factory method pattern and sets state accordingly
      private readItemsAndSetStatus(listName): void

      private listNotConfigured(props: IFactoryMethodProps): boolean
      props.listName === null

      //#endregion







      share|improve this question













      A Factory Pattern or Factory Method Pattern says that just define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate. In other words, subclasses are responsible to create the instance of the class.



      Advantage of Factory Design Pattern



      Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code. That means the code interacts solely with the resultant interface or abstract class, so that it will work with any classes that implement that interface or that extends that abstract class.



      A factory method is a method of a factory that builds objects.



      With that said, I built a React component that uses this pattern, functionally it works perfect, but I would like to know if the pattern is properly implemented or not and what your feedback as an expert is on this scenario.



      enter image description here



      ListItemFactory.ts



      import SPHttpClient, SPHttpClientResponse from "@microsoft/sp-http";
      import IWebPartContext from "@microsoft/sp-webpart-base";
      import IListItem from "./models/IListItem";
      import IFactory from "./IFactory";
      import INewsListItem from "./models/INewsListItem";
      import IDirectoryListItem from "./models/IDirectoryListItem";
      import IAnnouncementListItem from "./models/IAnnouncementListItem";

      export class ListItemFactory implements IFactory
      // private _listItems: IListItem;
      public getItems(requester: SPHttpClient, siteUrl: string, listName: string): Promise<any>
      switch(listName)
      case "GenericList":
      let items: IListItem;
      // tslint:disable-next-line:max-line-length
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Author/Title,Editor/Title&$expand=Author,Editor`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: IListItem > =>
      return response.json();
      )
      .then((json: value: IListItem ) =>
      console.log(JSON.stringify(json.value));
      return items=json.value.map((v,i)=>(

      // key: v.id,
      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title

      ));
      );
      case "News":
      let newsitems: INewsListItem;
      // tslint:disable-next-line:max-line-length
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Modified,Created,Created By,Modified By,newsheader,newsbody,expiryDate`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: INewsListItem > =>
      return response.json();
      )
      .then((json: value: INewsListItem ) =>
      return newsitems=json.value.map((v,i)=>(

      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title,
      newsheader: v.newsheader,
      newsbody: v.newsbody,
      expiryDate: v.expiryDate

      ));
      );
      case "Announcements":
      let announcementitems: IAnnouncementListItem;
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id,Created,Author/Title,Modified,Editor/Title,announcementBody,expiryDate&$expand=Author,Editor`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: IAnnouncementListItem > =>
      return response.json();
      )
      .then((json: value: IAnnouncementListItem ) =>
      return announcementitems=json.value.map((v,i)=>(

      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title,
      announcementBody: v.announcementBody,
      expiryDate: v.expiryDate

      ));
      );
      case "Directory":
      let directoryitems: IDirectoryListItem;
      return requester.get(`$siteUrl/_api/web/lists/getbytitle('$listName')/items?$select=Title,Id`,
      SPHttpClient.configurations.v1,

      headers:
      "Accept": "application/json;odata=nometadata",
      "odata-version": ""

      )
      .then((response: SPHttpClientResponse): Promise< value: IDirectoryListItem > =>
      return response.json();
      )
      .then((json: value: IDirectoryListItem ) =>
      return directoryitems=json.value.map((v,i)=>(

      id: v.Id,
      title: v.Title,
      created: v.Created,
      createdby: v.Author.Title,
      modified: v.Modified,
      modifiedby: v.Editor.Title,
      firstName: v.firstName,
      lastName: v.lastName,
      mobileNumber: v.mobileNumber,
      internalNumber: v.internalNumber

      ));
      );
      default:
      break;





      Ifactorystate.ts



      import IListItem from "./models/IListItem";
      import INewsListItem from "./models/INewsListItem";
      import IDirectoryListItem from "./models/IDirectoryListItem";
      import IAnnouncementListItem from "./models/IAnnouncementListItem";
      import
      IColumn
      from "office-ui-fabric-react/lib/DetailsList";

      export interface IFactoryMethodState
      hasError: boolean;
      status: string;
      columns: IColumn;
      DetailsListItemState: IDetailsListItemState;
      DetailsNewsListItemState: IDetailsNewsListItemState;
      DetailsDirectoryListItemState : IDetailsDirectoryListItemState;
      DetailsAnnouncementsListItemState : IDetailsAnnouncementListItemState;


      export interface IDetailsListItemState
      items: IListItem;


      export interface IDetailsNewsListItemState
      items: INewsListItem;


      export interface IDetailsDirectoryListItemState
      items: IDirectoryListItem;


      export interface IDetailsAnnouncementListItemState
      items: IAnnouncementListItem;



      The component:



      //#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,
      buildColumns,
      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>
      constructor(props: IFactoryMethodProps, state: any)
      super(props);
      this.setInitialState();



      // lifecycle help here: https://staminaloops.github.io/undefinedisnotafunction/understanding-react/
      //#region Mouting events lifecycle
      // the data returned from render is neither a string nor a DOM node.
      // it's a lightweight description of what the DOM should look like.
      // inspects this.state and this.props and create the markup.
      // when your data changes, the render method is called again.
      // react diff the return value from the previous call to render with
      // the new one, and generate a minimal set of changes to be applied to the DOM.
      public render(): React.ReactElement<IFactoryMethodProps>
      if (this.state.hasError)
      // you can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
      else
      switch(this.props.listName)
      case "GenericList":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsListItemState.items columns=this.state.columns />;
      case "News":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsNewsListItemState.items columns=this.state.columns/>;
      case "Announcements":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsAnnouncementsListItemState.items columns=this.state.columns/>;
      case "Directory":
      // tslint:disable-next-line:max-line-length
      return <this.ListMarqueeSelection items=this.state.DetailsDirectoryListItemState.items columns=this.state.columns/>;
      default:
      return null;




      public componentDidCatch(error: any, info: any): void
      // display fallback UI
      this.setState( hasError: true );
      // you can also log the error to an error reporting service
      console.log(error);
      console.log(info);




      // componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here.
      // if you need to load data from a remote endpoint, this is a good place to instantiate the network request.
      // this method is a good place to set up any subscriptions. If you do that, don’t forget to unsubscribe in componentWillUnmount().
      // calling setState() in this method will trigger an extra rendering, but it is guaranteed to flush during the same tick.
      // this guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state.
      // use this pattern with caution because it often causes performance issues. It can, however, be necessary for cases like modals and
      // tooltips when you need to measure a DOM node before rendering something that depends on its size or position.
      public componentDidMount(): void
      this._configureWebPart = this._configureWebPart.bind(this);
      this.readItemsAndSetStatus(this.props.listName);


      //#endregion
      //#region Props changes lifecycle events (after a property changes from parent component)
      // componentWillReceiveProps() is invoked before a mounted component receives new props.
      // if you need to update the state in response to prop
      // changes (for example, to reset it), you may compare this.props and nextProps and perform state transitions
      // using this.setState() in this method.
      // note that React may call this method even if the props have not changed, so make sure to compare the current
      // and next values if you only want to handle changes.
      // this may occur when the parent component causes your component to re-render.
      // react doesn’t call componentWillReceiveProps() with initial props during mounting. It only calls this
      // method if some of component’s props may update
      // calling this.setState() generally doesn’t trigger componentWillReceiveProps()
      public componentWillReceiveProps(nextProps: IFactoryMethodProps): void
      if(nextProps.listName !== this.props.listName)
      this.readItemsAndSetStatus(nextProps.listName);



      //#endregion
      //#region private methods
      private _configureWebPart(): void
      this.props.configureStartCallback();


      public setInitialState(): void
      this.state =
      hasError: false,
      status: this.listNotConfigured(this.props)
      ? "Please configure list in Web Part properties"
      : "Ready",
      columns:,
      DetailsListItemState:
      items:
      ,
      DetailsNewsListItemState:
      items:
      ,
      DetailsDirectoryListItemState:
      items:
      ,
      DetailsAnnouncementsListItemState:
      items:
      ,
      ;


      // reusable inline component
      private ListMarqueeSelection = (itemState: columns: IColumn, items: IListItem ) => (
      <div>
      <DetailsList
      items= itemState.items
      columns= itemState.columns
      setKey="set"
      layoutMode= DetailsListLayoutMode.fixedColumns
      selectionPreservedOnEmptyClick= true
      compact= true >
      </DetailsList>
      </div>
      )

      // read items using factory method pattern and sets state accordingly
      private readItemsAndSetStatus(listName): void

      private listNotConfigured(props: IFactoryMethodProps): boolean
      props.listName === null

      //#endregion









      share|improve this question












      share|improve this question




      share|improve this question








      edited May 28 at 1:27









      Jamal♦

      30.1k11114225




      30.1k11114225









      asked May 24 at 8:38









      Luis Valencia

      105112




      105112

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195072%2ffactory-method-implemented-in-react%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f195072%2ffactory-method-implemented-in-react%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation