I am trying to implement the prototype pattern in a Sharepoint Framework small example, I took the sample from here:
https://mertarauh.com/tutorials/typescript-design-patterns/prototype-pattern/
and adapted as below:
class Employee {
private totalperMonth: number;
constructor(public name: string, public hiredDate: Date, public dailyRate: number){
this.totalperMonth = dailyRate * 20 ;
}
public display(): string{
return "Employee " + this.name + " earns per month: " + this.totalperMonth;
}
public clone():Employee{
var cloned = Object.create(Employee || null);
Object.keys(this).map((key: string) => {
cloned[key]= this[key];
});
return <Employee>cloned;
}
}
export default Employee;
and the component
import * as React from 'react';
import styles from './Prototype.module.scss';
import { IPrototypeProps } from './IPrototypeProps';
import { escape } from '#microsoft/sp-lodash-subset';
import Employee from './Employee';
export default class Prototype extends React.Component<IPrototypeProps, {}> {
public render(): React.ReactElement<IPrototypeProps> {
const today = new Date();
let employee1: Employee = new Employee('Luis', today, 500);
let employee2 = employee1.clone();
employee2.dailyRate = 550;
return (
<div className={ styles.prototype }>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
<span className={ styles.title }>Welcome to SharePoint!</span>
<p className={ styles.subTitle }>Customize SharePoint experiences using Web Parts.</p>
<p className={ styles.description }>{escape(this.props.description)}</p>
<span className={ styles.label }>{employee1.display()}</span>
<span className={ styles.label }>{employee2.display()}</span>
</div>
</div>
</div>
</div>
);
}
}
However I get in the console this error:
What am I missing?
There are several problems with this approach.
Classes are first-class citizens in TypeScript, alternative approaches to inheritance can make type safety much less straightforward.
Object.create is low-level tool, it isn't a good idea to use it unless you know what you're doing and why. The actual mistake here is that wrong prototype was chosen (Employee instead of Employee.prototype), so name property refers to function name property, which is read-only and shouldn't be reassigned. A proper way to implement clone would be:
public clone():Employee{
return Object.assign(Object.create(Employee.prototype), this);
}
The fact that it ignores class constructor is a doubtful decision because a constructor may contain logic that differs from one applied in clone.
React promotes functional programming as a substitute to OOP, it's beneficial to maintain application state as plain objects rather than class instances, this may help to avoid design flaws that will be costly to fix in future. There's nothing that would really require a class in this case. The same thing could be expressed as:
const createEmployee = (name, hiredDate, dailyRate) => ({
name,
hiredDate,
dailyRate,
totalperMonth: dailyRate * 20
});
...
let employee1 = createEmployee('Luis', today, 500);
let employee2 = {...employee1, dailyRate: 550};
Notice that totalperMonth isn't recalculated neither in Employee class nor in createEmployee factory function. This may be undesirable (see the note on a constructor above).
Related
I've created a custom component which essentially returns html markup in order to display content based on the values passed to the component. Here's a simplified version for brevity:
interface ContainerProps {
position?: string;
content?: string;
class?: string;
}
const CardContainer: React.FC<ContainerProps> = ({ position = "right", content = "n/a", class = "" }) => {
if ( position.trim().toLowerCase() === "right" ) {
return <><div className="ion-float-right" >{content}</div><div className="clear-right"></div></>
} else if ( position.trim().toLowerCase() === "left" ) {
return <div className="ion-float-left">{content}</div>
} else {
return null
}
};
export default CardContainer;
This works great, but I now need to be able to pass a css class name to the component. However, I can't work out how to add the "class" prop to the returned html/jsx.
I tried various code such as below. However, in all cases the code was output as actual html rather than the value of the prop:
return <div className="ion-float-left" + {class}>{content}</div>
return <div className="ion-float-left {class}" >{content}</div>
return <div className="ion-float-left class">{content}</div>
I also tried a few other random things in desperation and these typically cause a compilation error. What is the best way to achieve the intended result eg:
return <div className="ion-float-left myClassNameHere">{content}</div>
its like inserting a string inside another or adding them together. You can use classname={"yourclasse" + theDynamicClass} or className={yourClass ${dynamicClass}} (inbetween ``)
return <div className=` ion-float-left ${class}``>{content}
I have created LWC for showing accounts and created one Lighting web component and one apex class shown below.
Html file
<template>
<lightning-card title="Showing Account(tile)">
<template if:true={successResponse}>
<template for:each={accounts} for:item="account">
<li key={account.id}>
<div class="slds-p-around_medium lgc-bg">
<lightning-tile label={account.Name} href="/path/to/somewhere">
<p class="slds-truncate" title={account.Phone}>Account's Phone Number is :
{account.Phone}
</p>
</lightning-tile>
</div>
</li>
</template>
</template>
</lightning-card>
</template>
Js.file
import { LightningElement, wire, track } from "lwc";
import allAccount from "#salesforce/apex/AcountManager.getAccount"
export default class AccountManagerApex extends LightningElement {
#wire(allAccount)
accountRecrd;
successResponse (){
if(this.accountRecrd){
return true;
}
return false;
}
}
and Class is:
public with sharing class AcountManager {
#AuraEnabled( cacheable = true)
public static List<Account> getAccount(){
return [SELECT Id,Name,Phone,Website FROM Account Limit 10];
}
}
When I try to deploy to my org using VSCode I'm getting the below error.
Error
force-app\main\default\lwc\accountManagerApex\accountManagerApex.js No MODULE named markup://lgc:bg found : [markup://c:accountManagerApex]
Can anyone tell me how to fix this issue/
Thanks in advance,
You have a custom css class lgc-bg. Please make sure your css file has a period before the selector
/* incorrect, lwc tries to find lgc:bg component */
lgc-bg : {}
/* correct */
.lgc-bg {}
Try setting <isExposed>true</isExposed> inside meta.xml file of the component
I'm trying to learn to handle forms in typescript and React from this blog. React specifically for the first time.
I'm observing the following is the source of Form.tsx
import * as React from "react";
interface IFormProps {
/* The http path that the form will be posted to */
action: string;
}
export interface IValues {
/* Key value pairs for all the field values with key being the field name */
[key: string]: any;
}
export interface IErrors {
/* The validation error messages for each field (key is the field name */
[key: string]: string;
}
export interface IFormState {
/* The field values */
values: IValues;
/* The field validation error messages */
errors: IErrors;
/* Whether the form has been successfully submitted */
submitSuccess?: boolean;
}
export class Form extends React.Component<IFormProps, IFormState> {
constructor(props: IFormProps) {
super(props);
const errors: IErrors = {};
const values: IValues = {};
this.state = {
errors,
values
};
}
/**
* Returns whether there are any errors in the errors object that is passed in
* #param {IErrors} errors - The field errors
*/
private haveErrors(errors: IErrors) {
let haveError: boolean = false;
Object.keys(errors).map((key: string) => {
if (errors[key].length > 0) {
haveError = true;
}
});
return haveError;
}
/**
* Handles form submission
* #param {React.FormEvent<HTMLFormElement>} e - The form event
*/
private handleSubmit = async (
e: React.FormEvent<HTMLFormElement>
): Promise<void> => {
e.preventDefault();
if (this.validateForm()) {
const submitSuccess: boolean = await this.submitForm();
this.setState({ submitSuccess });
}
};
/**
* Executes the validation rules for all the fields on the form and sets the error state
* #returns {boolean} - Whether the form is valid or not
*/
private validateForm(): boolean {
// TODO - validate form
return true;
}
/**
* Submits the form to the http api
* #returns {boolean} - Whether the form submission was successful or not
*/
private async submitForm(): Promise<boolean> {
// TODO - submit the form
return true;
}
public render() {
const { submitSuccess, errors } = this.state;
return (
<form onSubmit={this.handleSubmit} noValidate={true}>
<div className="container">
{/* TODO - render fields */}
<div className="form-group">
<button
type="submit"
className="btn btn-primary"
disabled={this.haveErrors(errors)}
>
Submit
</button>
</div>
{submitSuccess && (
<div className="alert alert-info" role="alert">
The form was successfully submitted!
</div>
)}
{submitSuccess === false &&
!this.haveErrors(errors) && (
<div className="alert alert-danger" role="alert">
Sorry, an unexpected error has occurred
</div>
)}
{submitSuccess === false &&
this.haveErrors(errors) && (
<div className="alert alert-danger" role="alert">
Sorry, the form is invalid. Please review, adjust and try again
</div>
)}
</div>
</form>
);
}
}
The thing which I'm not understanding is that the constructor of class Form have
errors
values
And both of them could have been handled as simple arrays of objects but why there is so much of declaration? As I can see:
IValues
IErrors
to represent them...
I seek help to understand the concept behind this for all these extra declarations.
both of them could have been handled as simple arrays of objects
There's more than one way to handle things and the creator of the post has decided to handle things the following way. Whether it's the best way to handle things is up to you to decide. I can only explain the reasoning behind them.
export interface IValues {
/* Key value pairs for all the field values with key being the field name */
[key: string]: any;
}
export interface IErrors {
/* The validation error messages for each field (key is the field name */
[key: string]: string;
}
The first interface(IValues) declares a contract where any object implementing it has to contain string keys coupled with a value of any type.
The second interface(IErrors) specifies a contract where any object implementing it has to contain string keys that point to string values.
This is the concept of a Key-Value pair also known as a hash, a dictionary, or a map depending on what languages you are familiar with.
Typescript actually has a built in type called Record which allows you to do the above without being nearly as verbose. It is defined as follows.
type Record<K extends keyof any, T> = { [P in K]: T }
With this definition of Record, the interfaces can be constructed as,
type IValues = Record<string, any>
type IErrors = Record<string, string>
In fact, the interfaces can be totally replaced with Record types.
As for the rest of the code, it is quite obvious that this person may not be writing the most optimized nor the most concise, i.e the least vebose, code. For example:
private haveErrors(errors: IErrors) {
let haveError: boolean = false;
Object.keys(errors).map((key: string) => {
if (errors[key].length > 0) {
haveError = true;
}
});
return haveError;
}
This check can be short-circuited to return true when the first error is encountered. By the way, this only checks for at least one error in a singular form, so it can be better named as hasError or hasErrors
private hasError(errors: IErrors) {
for (let [key, value] of Object.entries(errors))
if (value) // <---- if (value) alone is enough as an empty string '' is falsy
return true;
return false;
}
The list of improvements that can be made go on, but it can be safely said that the blog in question might not be the foremost authority on TypeScript or JavaScript for that matter. Proceed with caution.
P.S.
{submitSuccess === false && ...} = {!submitSuccess && ...}
Why? submitSuccess is defined to be a boolean there is no need to triple equals it to false.
Falsiness
When you write to an interface, you are agreeing to a contract: the form of the interface is guaranteed at compile time to be the form of the data.
When you pass around bare data structures, you have no such guarantee: the data you are given may have more or less than you imagined it would, and the only way to verify is by runtime checks. Those are more expensive than compile time checks.
I just started to learn ReactJS and encountered a problem that I can't solve.
I'm creating a basic application for TV Shows. I have a bootstrap tab for every season of a show and within this tab I want to list all the episodes of the selected season. The problem is that I have to create the tabs in a loop, and within this loop I should have another loop for the episodes.
I'm trying to do something like this:
EpisodeCards(props) {
return (
<div>
This should contain the details of the episodes
</div>
)
}
SeasonTabs(props) {
console.log('Seasons: ', props.seasons)
let tabs = [];
let episodes = [];
let currentSeason = 1;
let id;
let aria;
for(let season of props.seasons) {
episodes = [];
id="nav-season" + currentSeason;
aria = "nav-season" + currentSeason + "-tab";
tabs.push(<div className="tab-pane fade" id={id} role="tabpanel" aria-labelledby={aria}><this.EpisodeCards episodes={season}></this.EpisodeCards></div>);
currentSeason++;
}
return (
<div className="tab-content py-3 px-3 px-sm-0" id="nav-tabContent">
{tabs}
</div>
)
}
For this I am getting the following error:
Unhandled Rejection (TypeError): Cannot read property 'EpisodeCards' of undefined
How can this be done in the 'react way'? Thanks in advance.
Change
SeasonTabs(props)
to
SeasonTabs = (props) =>
You want to access a class property using this but by default its not binded to the function (ES5 only) by creating the functions using arrow () =>(new ES6 syntax) it automatically bind this to the function.
For Example:
class Test extends Component{
constructor(props){
this.testFn= this.testFn.bind(this);
}
testFn(){
console.log(this); //will output
}
testFn2(){
console.log(this); // undefined
}
testFn3 = () =>{
console.log(this); //will output
}
}
Reactjs is all about components, everything is component. If a function return a react element or Custom element then is a component.
You are creating a functional component inside of a class component, while this approach may work but this is not appropriate way to make component.
It is better to use composition, your code will be more clear, easier
to read, better maintainability and you can use component in other
components.
My solution:
function EpisodeCards(props) {
return (
<div>
This should contain the details of the episodes
</div>
)
}
SeasonTabs(props) {
console.log('Seasons: ', props.seasons)
let tabs = [];
let episodes = [];
let currentSeason = 1;
let id;
let aria;
for(let season of props.seasons) {
episodes = [];
id="nav-season" + currentSeason;
aria = "nav-season" + currentSeason + "-tab";
//There is no need for this keyword
tabs.push(<div className="tab-pane fade" id={id} role="tabpanel" aria-labelledby={aria}><EpisodeCards episodes={season}/></div>);
currentSeason++;
}
return (
<div className="tab-content py-3 px-3 px-sm-0" id="nav-tabContent">
{tabs}
</div>
)
}
I'm trying to learn typescript while following this best practices guide, but I feel like I'm missing something here.
Here's a working code, written with typescript. What's bothering me is that I don't feel like we really take advantage of typescript here. If someone where to change the signature of a function, then the compiler would just not notice it.
Let me explain:
First let's define a "thing":
// thing.interface.ts
export interface IThing {
name: string,
id: number,
}
Some component is a list of things:
// listOfThings.component.ts
import ListOfThingCtrl from './listOfThings.controller.ts'
const listOfThings = {
bindings: {
things: '<',
},
controller: ListOfThingCtrl,
template: `
<p> list of things: <p>
<ul>
<li ng-repeat="thing in $ctrl.things">
<thing thing="thing" on-delete="$ctrl.deleteThing($event)"></thing>
</li>
</ul>
`,
}
And we have a thing component:
import ThingCtrl from './thing.controller.ts'
const thing = {
bindings: {
onDelete: '&',
thing: '<',
},
controller: ThingCtrl,
template: `
<p>
thing: {{$ctrl.thing.name}}
<a ng-click='$ctrl.deleteThing()' >
Delete me
</a>
</p>
`,
}
So Far so good.
Now let's define the controllers:
//listOfThings.controller.js
import {IThing} from './thing.interface.ts'
export default class ListOfThingCtrl {
private things: Array<IThing>
public deleteThing (thing: IThing): void {
const i = this.things.map(t => t.id).indexOf(thing.id)
if (i > -1) {
this.things.splice(i, 1)
}
}
}
// thing.controller.js
import {IThing} from './thing.interface.ts'
export default class ThingCtrl {
private thing: IThing
// This line looks dangerous to me : this should be imported, not declared !
private onDelete: (e: {$event: IThing}) => void // What now if the bounded function were to change ?
public deleteThing (): void {
this.onDelete({$event: this.thing})
}
}
This code "works" : it loads, compile without complaining, and a click on some "Delete me" will actually delete a "thing" from the controller list "things"
Now what's bothering me : In this code we inject a onDelete function to the thing component, but we never inject its actual signature.
If someone were to change the deleteThing function; let' say deleteThing(thingId: number) instead of deleteThing(thing: IThing). Then nothing works anymore, but the compiler would compile just fine.
I feels to me that the whole point of using typescript is to avoid this scenario : deleteThing's signature has changed, but onDelete will still call it with old arguments without knowing it will fail at runtime.
I am however very new to typescript, so I'm probably missing something crucial here.
Is there any best practices that allow to pass function signatures through an angular component ?
Thanks for reading !
I figured out something that seems satisfying to me :
First, let's make a slight modification to the ListOfThings component template:
<thing thing="thing" on-delete="$ctrl.deleteThing({$event})"></thing>
(instead of deleteThing($event)) so that both onDelete and deleteThing have the exact same signature.
Declare a function interface in thing.interface.js :
export interface IDeleteThing {
(e: {$event: {thing: IThing}}): void
}
Now both controller can import the interface and use it :
// listOfThings.controller.ts
import {IThing, IDeleteThing} from './thing.interface.ts'
export default class ListOfThingCtrl {
private things: Array<IThing>
public deleteThing: IDeleteThing = ({$event}) => {
const i = this.things.map(t => t.id).indexOf($event.thing.id)
if (i > -1) {
this.things.splice(i, 1)
}
}
}
// thing.controller.ts
import {IThing, IDeleteThing} from './thing.interface.ts'
export default class ThingCtrl {
private thing: IThing
private onDelete: IDeleteThing
public deleteThing (): void {
this.onDelete({$event: {thing: this.thing}})
}
}
As the signature is now defined in a shared module, any incoherence between the two controllers will now be detected by the compiler \o/