Problem with infinite loop using ngrx library - javascript

cart.service.ts
import { Tools } from './tools';
import { Cart, cartSelector } from './../_store/state/cart/cart.state';
import {
AddProduct,
RemoveProduct,
AddMultiple,
CartMsg,
TypeMessageAlreadyCart,
TypeEmptyCart,
AddAdress,
UpdateProduct
} from './../_store/state/cart/cart.actions';
import { CartItem, Variant } from './../_store/models/cart-item.model';
import { Store } from '#ngrx/store';
import { Injectable, OnDestroy } from '#angular/core';
import { RootState } from '../_store/state/root/root.state';
import { Product } from 'src/app/pages/_model/product.model';
import { Subscription } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class CartService implements OnDestroy {
products: CartItem[];
cartStoreId = 'cart';
idAddress = 0;
subscription = new Subscription();
constructor(
private store: Store<RootState>,
private tools: Tools,
) {
// this.tools.storeInfo({}, this.cartStoreId); // Line to debug Cart
this.recoverCartInfo();
const select = this.store.select(cartSelector).subscribe(
cart => {
this.products = JSON.parse(JSON.stringify(cart.products));
this.idAddress = cart.idAddress;
});
this.subscription.add(select);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
storeCartInfo() {
const cartStored = {
products: this.products,
idAddress: this.idAddress
};
this.tools.storeInfo(cartStored, this.cartStoreId);
}
updateCartPrices(idProduct: number, variants) {
let idx = this.tools.findProduct(idProduct, this.products);
if (idx !== -1) {
let total = 0;
this.products[idx].variants.forEach(variant => {
for (let item of variants) {
if (variant.idVariant === item.ID_VARIANT) {
variant.unitPrice = item.PRICE;
total += variant.unitPrice * variant.qty;
continue;
}
}
})
this.products[idx].totalPrice = total;
}
//this.store.dispatch(new UpdateProduct(this.products));
this.storeCartInfo();
}
recoverCartInfo() {
const cart = this.tools.getInfo(this.cartStoreId);
if (cart && (cart.products) && (cart.products.length > 0)) {
const result: Cart = {
totalProducts: cart.products.length,
idAddress: cart.idAddress,
products: cart.products,
msgType: '',
currency: this.tools.getInfo('currency_default')
};
this.store.dispatch(new AddMultiple(result));
}
}
addProductToCart(product: Product, variants?: Variant[]) {
if (this.changeProductInCart(product, variants)) {
this.store.dispatch(new AddProduct(this.products));
this.storeCartInfo();
return true;
} else {
this.store.dispatch(new TypeMessageAlreadyCart());
return false;
}
}
changeProductInCart(product: Product, variants?: Variant[]) {
const index = this.tools.findProduct(product.ID_PRODUCT, this.products);
const attrAdded = (variants && (variants.length > 0)) ? this.mergeVariantsCart(product.ID_PRODUCT, variants) : [];
const totals = this.counterTotals(attrAdded);
if (index === - 1) {
const item = {
idProduct: product.ID_PRODUCT,
reference: product.REFERENCE,
idProductImage: (product.ID_PRODUCT_IMAGE) ? product.ID_PRODUCT_IMAGE : 0,
name: product.NAME,
totalQty: totals.tQty,
totalPrice: totals.tPrice,
variants: attrAdded
};
this.products.push(item);
return true;
} else if (variants && (variants.length > 0)) {
this.products[index].variants = attrAdded;
this.products[index].totalQty = totals.tQty;
this.products[index].totalPrice = totals.tPrice;
return true;
}
return false;
}
mergeVariantsCart(idProduct: number, variants: Variant[]) {
if (this.products.length > 0) {
const index = this.tools.findProduct(idProduct, this.products);
if (index >= 0) {
const varInCart = this.products[index].variants;
if (varInCart.length > 0) {
variants.forEach(variant => {
const idx = this.tools.findVariant(variant.idVariant, varInCart);
if (variant.qty > 0) {
if (idx >= 0) {
varInCart[idx].qty = variant.qty;
varInCart[idx].unitPrice = variant.unitPrice;
} else {
varInCart.push(variant);
}
} else if (idx >= 0) {
varInCart.splice(idx, 1);
}
});
} else {
variants.forEach(variant => {
if (variant.qty > 0) {
varInCart.push(variant);
}
});
}
return varInCart;
} else {
return variants;
}
} else {
return variants;
}
}
counterTotals(variants: Variant[]) {
let totalQty = 0;
let totalPrice = 0;
if (!this.tools.isArrayEmpty(variants)) {
variants.forEach(variant => {
totalQty += (variant.qty * 1);
totalPrice += (variant.qty * variant.unitPrice);
});
}
return {
tQty : totalQty,
tPrice: totalPrice
};
}
addMultipleProductsToCart(products: any[]) {
let added = false;
products.forEach(p => {
if (this.changeProductInCart(p.product, p.variants)) {
added = true;
}
});
if (added) {
const result: Cart = {
totalProducts: this.products.length,
idAddress: this.idAddress,
products: this.products,
msgType: CartMsg.msgOk,
currency: this.tools.getInfo('currency_default')
};
this.store.dispatch(new AddMultiple(result));
this.storeCartInfo();
return true;
} else {
this.store.dispatch(new TypeMessageAlreadyCart());
}
return false;
}
removeProductFromCart(id: number) {
if (this.products.length > 0) {
const index = this.tools.findProduct(id, this.products);
if (index >= 0) {
this.products.splice(index, 1);
this.store.dispatch(new RemoveProduct(this.products));
this.storeCartInfo();
return true;
}
} else {
this.store.dispatch(new TypeEmptyCart());
}
return false;
}
destroyCart() {
this.products = [];
const cart: Cart = {
totalProducts: 0,
idAddress: 0,
products: this.products,
msgType: '',
currency: this.tools.getInfo('currency_default')
};
this.store.dispatch(new AddMultiple(cart));
this.storeCartInfo();
}
addAddress(idAddress: number) {
this.idAddress = idAddress;
this.store.dispatch(new AddAdress(idAddress));
this.storeCartInfo();
}
}
variant-selector.ts
import { Tools } from './../../../../core/_services/tools';
import { CONFIG } from './../../../../app.config';
import {
Component,
Input,
Output,
EventEmitter,
Renderer2,
ElementRef,
ViewChild,
OnChanges,
AfterViewInit,
OnDestroy
} from '#angular/core';
import { Variant } from 'src/app/core/_store/models/cart-item.model';
import { ResponsiveService } from 'src/app/core/_services/resposive.service';
import { Subscription, Observable } from 'rxjs';
import { Store } from '#ngrx/store';
import { RootState } from 'src/app/core/_store/state/root/root.state';
import {
hidePricesSelector,
hideNoStockSelector
} from 'src/app/core/_store/state/config/config.state';
import { UpdateProduct } from 'src/app/core/_store/state/cart/cart.actions';
import { CartService } from 'src/app/core/_services/cart.service';
#Component({
selector: 'b2b-variants-selector',
templateUrl: './variants-selector.component.html'
})
export class VariantsSelectorComponent
implements OnChanges, AfterViewInit, OnDestroy {
#Input() idProduct: number;
#Input() variants: any[];
#Input() variantsInCart: Variant[];
#Input() attributes: any[];
#Input() reference: string;
#Input() isCheckout: boolean;
#Output() variantChange = new EventEmitter<Variant>();
#Output() getValue = new EventEmitter<any>();
#ViewChild('attTable') attTable: ElementRef;
#ViewChild('variantTable') variantTable: ElementRef;
showMatrix = CONFIG.showMatrix;
pricesBySize = CONFIG.pricesBySize;
colors: any;
sizes: any;
tableRealWidth = 0;
tableRotate = 0;
hideArrows = false;
variantSelected: any;
groupSelection: any[] = [];
imgPrd = CONFIG.productImg;
subscription = new Subscription();
private hidePrices$: Observable<boolean>;
private hideNoStock$: Observable<boolean>;
constructor(
private tools: Tools,
private renderer: Renderer2,
private responsiveService: ResponsiveService,
private store: Store<RootState>,
private cartService: CartService
) {
const getRes = this.responsiveService
.getResponsiveDeviceStatus$()
.subscribe(() => this.checkVisibility());
this.subscription.add(getRes);
this.hidePrices$ = this.store.select(hidePricesSelector);
this.hideNoStock$ = this.store.select(hideNoStockSelector);
}
ngOnInit() {
this.cartService.updateCartPrices(this.idProduct,this.variants);
}
ngOnChanges() {
if (this.showMatrix && this.attributes.length !== 0) {
this.calculateMatrixAttrs();
}
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
ngAfterViewInit() {
setTimeout(() => {
this.checkVisibility();
}, 10);
}
checkColourOutofStock(color) {
for (const size of this.sizes.ATTR_VALUES) {
const variant = this.getVariant(
this.colors.ID_ATTRIBUTE_GROUP +
'-' +
color.ID_ATTRIBUTE +
'-' +
this.sizes.ID_ATTRIBUTE_GROUP +
'-' +
size.ID_ATTRIBUTE
);
if (
variant &&
(variant.OUT_OF_STOCK === true ||
variant.QUANTITY > 0 ||
variant.QUANTITY_PRODUCTION > 0)
) {
return false;
}
}
return true;
}
calculateMatrixAttrs() {
this.attributes.forEach((attrGroup: any) => {
if (attrGroup.IS_COLOR_GROUP) {
for (const item of attrGroup.ATTR_VALUES) {
item.ACTIVE = false;
}
this.colors = attrGroup;
} else {
this.sizes = attrGroup;
this.tableRealWidth =
this.tools.getObjectLength(this.sizes.ATTR_VALUES) *
(this.pricesBySize ? 75 : 115) +
45;
}
});
}
getMatrixPrice(attrIds: string) {
const variant = this.getVariant(attrIds);
return variant.PRICE;
}
getMatrixDiscount(attrIds: string) {
const variant = this.getVariant(attrIds);
return variant.DISCOUNT ? variant.DISCOUNT : null;
}
getMatrixDiscountType(attrIds: string) {
const variant = this.getVariant(attrIds);
return variant.DISCOUNT_TYPE ? variant.DISCOUNT_TYPE : null;
}
checkVisibility() {
const visibleWidth = this.attTable.nativeElement.offsetWidth - 80;
if (visibleWidth > this.tableRealWidth) {
this.hideArrows = true;
} else {
this.hideArrows = false;
}
}
rotateTable(direction: string) {
const visibleWidth = this.attTable.nativeElement.offsetWidth - 95;
if (visibleWidth < this.tableRealWidth) {
let move = this.tableRotate;
if (direction === 'left') {
move += 240;
if (move > 0) {
move = 0;
}
} else {
const offsetRigth = visibleWidth - this.tableRealWidth;
move -= 240;
if (move < offsetRigth) {
move = offsetRigth;
}
}
this.tableRotate = move;
this.renderer.setStyle(
this.variantTable.nativeElement,
'transform',
'translate3d(' + this.tableRotate + 'px, 0px, 0px)'
);
}
}
findGroup(id: number) {
if (!this.tools.isArrayEmpty(this.groupSelection)) {
for (let i = 0; i < this.groupSelection.length; i++) {
if (this.groupSelection[i].idGroup === id) {
return i;
}
}
}
return -1;
}
checkSelected(groupId: number, variantId: number) {
const idx = this.findGroup(groupId);
if (idx >= 0 && this.groupSelection[idx].idVariant === variantId) {
return true;
}
return false;
}
changeVariantSelection(groupId: number, variantId: number) {
const idx = this.findGroup(groupId);
if (idx === -1) {
this.groupSelection.push({
idGroup: groupId,
idVariant: variantId
});
} else {
this.groupSelection[idx].idVariant = variantId;
}
this.getVariantSelected();
}
getVariantSelected() {
const sortGroup = this.groupSelection.sort((a, b) =>
a.idGroup > b.idGroup ? 1 : -1
);
let hassIds = '';
let counter = 0;
sortGroup.forEach(g => {
if (counter > 0) {
hassIds += '-';
}
hassIds += g.idGroup + '-' + g.idVariant;
counter++;
});
this.variantSelected = this.getVariant(hassIds);
}
sendVariantAdded(variant: Variant) {
this.variantChange.next(variant);
}
getQtyincart(idVariant: number) {
const idx = this.tools.findVariant(idVariant, this.variantsInCart);
return idx >= 0 ? this.variantsInCart[idx].qty : 0;
}
getVariant(attrIds: string) {
let variantData = null;
this.variants.forEach(variant => {
if (variant.ATTRIDS === attrIds) {
variantData = { ...variant };
variantData.qtyIncArt = this.getQtyincart(variantData.ID_VARIANT);
}
});
return variantData;
}
changeImage(color, active) {
const tempActive = active;
let init = false;
for (const item of this.colors.ATTR_VALUES) {
item.ACTIVE = false;
}
if (this.colors.ATTR_VALUES.find(x => x.ATTR_REFERENCE === color)) {
if (tempActive) {
this.colors.ATTR_VALUES.find(
x => x.ATTR_REFERENCE === color
).ACTIVE = false;
init = true;
} else if (
this.colors.ATTR_VALUES.find(x => x.ATTR_REFERENCE === color)
.IMAGES[0] !== ''
) {
this.colors.ATTR_VALUES.find(
x => x.ATTR_REFERENCE === color
).ACTIVE = true;
}
}
this.getValue.emit({ color, init });
}
}
In the code above I try to call the function updateCartPrices but it enters into an infinite loop because of the commented dispatch. I'm new to angular and It would be great if you could help me fix this issue
I think the problem is because when the state is updated causes the view to reload, by I don't understand why

Related

React component not carrying over functions from class when re-rendering

I'm attempting to build a Minesweeper game as practice using React. I have a main Minesweeper.js file with all of the functionality.
export class Tile {
constructor(board, pos) {
this.board = board;
this.pos = pos;
this.bombed = false;
this.explored = false;
this.flagged = false;
}
adjacentBombCount() {
let bombCount = 0;
this.neighbors().forEach(neighbor => {
if (neighbor.bombed) {
bombCount++;
}
});
return bombCount;
}
explore() {
if (this.flagged || this.explored) {
return this;
}
this.explored = true;
if (!this.bombed && this.adjacentBombCount() === 0) {
this.neighbors().forEach(tile => {
tile.explore();
});
}
}
neighbors() {
const adjacentCoords = [];
Tile.DELTAS.forEach(delta => {
const newPos = [delta[0] + this.pos[0], delta[1] + this.pos[1]];
if (this.board.onBoard(newPos)) {
adjacentCoords.push(newPos);
}
});
return adjacentCoords.map(coord => this.board.grid[coord[0]][coord[1]]);
}
plantBomb() {
this.bombed = true;
}
toggleFlag() {
if (!this.explored) {
this.flagged = !this.flagged;
return true;
}
return false;
}
}
Tile.DELTAS = [[-1, -1], [-1, 0], [-1, 1], [ 0, -1],
[ 0, 1], [ 1, -1], [ 1, 0], [ 1, 1]];
export class Board {
constructor(gridSize, numBombs) {
this.gridSize = gridSize;
this.grid = [];
this.numBombs = numBombs;
this.generateBoard();
this.plantBombs();
}
generateBoard() {
for (let i = 0; i < this.gridSize; i++) {
this.grid.push([]);
for (let j = 0; j < this.gridSize; j++) {
const tile = new Tile(this, [i, j]);
this.grid[i].push(tile);
}
}
}
onBoard(pos) {
return (
pos[0] >= 0 && pos[0] < this.gridSize &&
pos[1] >= 0 && pos[1] < this.gridSize
);
}
plantBombs() {
let totalPlantedBombs = 0;
while (totalPlantedBombs < this.numBombs) {
const row = Math.floor(Math.random() * (this.gridSize - 1));
const col = Math.floor(Math.random() * (this.gridSize - 1));
let tile = this.grid[row][col];
if (!tile.bombed) {
tile.plantBomb();
totalPlantedBombs++;
}
}
}
lost() {
let lost = false;
this.grid.forEach(row => {
row.forEach(tile => {
if (tile.bombed && tile.explored) {
lost = true;
}
});
});
return lost;
}
won() {
let won = true;
this.grid.forEach(row => {
row.forEach(tile => {
if (tile.flagged === tile.revealed || tile.flagged !== tile.bombed) {
won = false;
}
});
});
return won;
}
}
Then I have three React components: Board.jsx
import React from "react";
import Tile from "./tile";
class Board extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<div>
{this.props.board.grid.map((row, idx) => {
return (<div key={idx}>
{row.map((tile, idx) => {
return (
<div key={idx}>
<Tile
tile={tile}
updateGame={this.props.updateGame}/>
</div>
)
})}
</div>)
})}
</div>
)
}
}
export default Board;
Tile.jsx
import React from "react";
class Tile extends React.Component {
constructor(props) {
super(props)
this.checkState = this.checkState.bind(this)
}
checkState() {
console.log(this)
if (this.props.tile.bombed) {
return "💣"
} else if (this.props.tile.explored) {
return (this.props.tile.adjacentBombCount())
} else if (this.props.tile.flagged) {
return "⚐"
} else {
return ""
}
}
render() {
return(
<div>{this.checkState()}</div>
)
}
}
export default Tile;
and finally a Game.jsx
import React from "react";
import * as Minesweeper from "../minesweeper"
import Board from "./board";
class Game extends React.Component {
constructor(props) {
super(props);
let board = new Minesweeper.Board(10, 5)
this.state = {
board: board
}
this.updateGame = this.updateGame.bind(this);
}
updateGame() {
}
render() {
return(
<Board
board={this.state.board}
updateGame={this.updateGame}/>
)
}
}
export default Game
Within Tile.jsx, I want the render to display adjacent bomb counts using the function from the main Tile js class (in minesweeper.js). This function is accessible when I first render my Tiles as React Components; however, if I change something (by going into components and updating), the function is no longer accessible. To help explain, here is the console.log of this.props.tile on the first render:
However, after re-rendering from updating a component, this is what this.props.tile looks like .
Therefore, it will cause an error when I try to add the bomb count, as it cannot find the function. Can someone explain why the function is disappearing and how I can access it when Components change? Thanks!
This was an issue with the React DevTools specifically, and it will be addressed in an update, see: https://github.com/facebook/react/issues/24781

Error with this NullInjector and have something like that: Uncaught (in promise): NullInjectorError: R3InjectorError(n)[n -> n -> n]

Here is the code.
component.ts
Here I guess the reason is these imports or maybe something wrong with routing.
import { Component, OnInit, ViewChild } from '#angular/core';
import { FormBuilder, Validators } from '#angular/forms';
import { AlbumService } from 'src/app/_services/entity-services/album-service';
import { Album } from 'src/app/models/album';
import { ToastrService } from 'ngx-toastr';
import { Observable } from 'rxjs';
// import { Router } from '#angular/router';
import { MatSnackBar, MatSnackBarHorizontalPosition, MatSnackBarVerticalPosition } from '#angular/material/snack-bar';
import { MatDialog } from '#angular/material/dialog';
import { MatTableDataSource, } from '#angular/material/table';
import { MatPaginator } from '#angular/material/paginator';
import { MatSort } from '#angular/material/sort';
import { SelectionModel } from '#angular/cdk/collections';
interface Artist {
Id: string;
Name: string;
}
interface Label {
Id: string;
Name: string;
}
#Component({
selector: 'app-album-crud',
templateUrl: './album.component.html',
styles: []
})
export class AlbumComponent implements OnInit {
dataSaved = false;
albumForm: any;
allAlbums: Observable<Album[]>;
dataSource: MatTableDataSource<Album>;
selection = new SelectionModel<Album>(true, []);
albumIdUpdate = null;
massage = null;
allArtist: Observable<Artist[]>;
allLabel: Observable<Label[]>;
ArtistId = null;
LabelId = null;
SelectedDate = null;
isMale = true;
isFeMale = false;
horizontalPosition: MatSnackBarHorizontalPosition = 'center';
verticalPosition: MatSnackBarVerticalPosition = 'bottom';
displayedColumns: string[] = ['select', 'Title', 'Year', 'Artist', 'Label', 'Edit', 'Delete'];
#ViewChild(MatPaginator) paginator: MatPaginator;
#ViewChild(MatSort) sort: MatSort;
constructor(private formbulider: FormBuilder, public service: AlbumService,
private toastr: ToastrService, private _snackBar: MatSnackBar, public dialog: MatDialog) {
this.service.getAllAlbum().subscribe(data => {
this.dataSource = new MatTableDataSource(data);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}
ngOnInit(): void {
this.albumForm = this.formbulider.group({
Title: ['', [Validators.required]],
Year: ['', [Validators.required]],
Artist: ['', [Validators.required]],
Label: ['', [Validators.required]]
});
this.FillArtistDDL();
this.FillLabelDDL();
this.loadAllAlbums();
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = !!this.dataSource && this.dataSource.data.length;
return numSelected === numRows;
}
masterToggle() {
this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach(r => this.selection.select(r));
}
checkboxLabel(row: Album): string {
if (!row) {
return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
}
return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.Id + 1}`;
}
applyFilter(filterValue: string) {
this.dataSource.filter = filterValue.trim().toLowerCase();
if (this.dataSource.paginator) {
this.dataSource.paginator.firstPage();
}
}
loadAllAlbums() {
this.service.getAllAlbum().subscribe(data => {
this.dataSource = new MatTableDataSource(data);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
});
}
onFormSubmit() {
this.dataSaved = false;
const album = this.albumForm.value;
this.CreateAlbum(album);
this.albumForm.reset();
this.toastr.success("Created successfully!");
}
loadAlbumToEdit(albumId: string) {
this.service.getAlbumById(albumId).subscribe(album => {
this.massage = null;
this.dataSaved = false;
this.albumIdUpdate = album.Id;
this.albumForm.controls['Title'].setValue(album.Title);
this.albumForm.controls['Year'].setValue(album.Year);
this.albumForm.controls['Artist'].setValue(album.ArtistId);
this.albumForm.controls['Label'].setValue(album.LabelId);
});
}
CreateAlbum(album: Album) {
console.log(album.Title);
if (this.albumIdUpdate == null) {
album.ArtistId = this.ArtistId;
album.LabelId = this.LabelId;
this.service.createAlbum(album).subscribe(
() => {
this.dataSaved = true;
this.SavedSuccessful(1);
this.loadAllAlbums();
this.albumIdUpdate = null;
this.albumForm.reset();
}
);
} else {
album.Id = this.albumIdUpdate;
album.ArtistId = this.ArtistId;
album.LabelId = this.LabelId;
this.service.updateAlbum(album).subscribe(() => {
this.dataSaved = true;
this.SavedSuccessful(0);
this.loadAllAlbums();
this.albumIdUpdate = null;
this.albumForm.reset();
});
}
}
deleteAlbum(albumId: string) {
if (confirm("Are you sure you want to delete this ?")) {
this.service.deleteAlbum(albumId).subscribe(() => {
this.dataSaved = true;
this.SavedSuccessful(2);
this.loadAllAlbums();
this.albumIdUpdate = null;
this.albumForm.reset();
});
}
}
FillArtistDDL() {
this.allArtist = this.service.getAllArtist();
}
FillLabelDDL() {
this.allLabel = this.service.getAllLabel();
}
GetSelectedArtist(Artist) {
this.ArtistId = Artist.value;
}
GetSelectedLabel(Label){
this.LabelId=Label.value;
}
resetForm() {
this.albumForm.reset();
this.massage = null;
this.dataSaved = false;
this.loadAllAlbums();
}
SavedSuccessful(isUpdate) {
if (isUpdate == 0) {
this._snackBar.open('Record Updated Successfully!', 'Close', {
duration: 2000,
horizontalPosition: this.horizontalPosition,
verticalPosition: this.verticalPosition,
});
}
else if (isUpdate == 1) {
this._snackBar.open('Record Saved Successfully!', 'Close', {
duration: 2000,
horizontalPosition: this.horizontalPosition,
verticalPosition: this.verticalPosition,
});
}
else if (isUpdate == 2) {
this._snackBar.open('Record Deleted Successfully!', 'Close', {
duration: 2000,
horizontalPosition: this.horizontalPosition,
verticalPosition: this.verticalPosition,
});
}
}
}
I did everything with imports , something wrong with kind of injection but where? I`m truly exhausted. Because it does not go to this component URL and trim it and throws an error as mentioned above.

adding more than 1 number value to payload

I am playing around with a redux tutorial code, I am trying to add another number value to createPolicy action creator, it is showing some error, please tell me what am I missing here
console.clear()
const createPolicy = (name, amount) => {
return {
type : 'CREATEPOLICY',
payload : {
name, amount, bill
}
}
}
const deletePolicy = (name) => {
return {
type : 'DELETEPOLICY',
payload : {
name
}
}
}
const claimRequest = (name, amountofclaim, fees) => {
return {
type : 'CLAIMREQUEST',
payload : {
name, amountofclaim, fees
}
}
}
const creteReducer = (previesdelte = [], action) => {
if(action.type === "CREATEPOLICY") {return [...previesdelte, action.payload ]}
else{return previesdelte}
}
const clamReducer = (totalamount=200, action) => {
if(action.type === "CLAIMREQUEST") {
return totalamount - (action.payload.amountofclaim + action.payload.fees)}
else if(action.type === "CREATEPOLICY") {
return totalamount + (action.payload.amount + action.payload.bill)}
else{return totalamount}
}
const deleteReducer = (userlist = [], action) => {
if(action.type === "CREATEPOLICY"){
return [...userlist, action.payload]
}
else if(action.type === "DELETEPOLICY") {
return userlist.filter( name => name !== action.payload.name)
}
else {
return userlist
}
}
const {createStore, combineReducers} = Redux;
const allReducers = combineReducers({
creteReducer:creteReducer,
clamReducer:clamReducer,
deleteReducer:deleteReducer
})
const store = createStore(allReducers)
store.dispatch(createPolicy('fff', 20, 3))
store.dispatch(claimRequest('fff', 150, 5))
console.log(store.getState())
A parameter bill is probably missing from your createPolicy function.

React infinite scroll component performance

I have written the following infinite scroll component in React:
import React from 'react'
import { uniqueId, isUndefined, hasVerticalScrollbar, hasHorizontalScrollbar, isInt, throttle } from '../../../js/utils';
export default class BlSimpleInfiniteScroll extends React.Component {
constructor(props) {
super(props)
this.handleScroll = this.handleScroll.bind(this)
this.itemsIdsRefsMap = {}
this.isLoading = false
this.node = React.createRef()
}
componentDidMount() {
const {
initialId
} = this.props
let id
if (initialId) {
if (typeof initialId === "function") {
id = initialId()
}
else {
id = initialId
}
this.scrollToId(id)
}
}
componentDidUpdate(prevProps) {
if (
this.isLoading
&&
prevProps.isInfiniteLoading
&&
!this.props.isInfiniteLoading
) {
const axis = this.axis()
const scrollProperty = this.scrollProperty(axis)
const offsetProperty = this.offsetProperty(axis)
this.scrollTo(scrollProperty, this.node.current[offsetProperty])
this.isLoading = false
}
}
itemsRenderer(items) {
const length = items.length
let i = 0
const renderedItems = []
for (const item of items) {
renderedItems[i] = this.itemRenderer(item.id, i, length)
i++
}
return renderedItems
}
itemRenderer(id, i, length) {
const {
itemRenderer,
isInfiniteLoading,
displayInverse
} = this.props
let renderedItem = itemRenderer(id, i)
if (isInfiniteLoading) {
if (!displayInverse && (i == length - 1)) {
renderedItem = this.standardLoadingComponentWrapperRenderer(id, renderedItem)
}
else if (i == 0) {
renderedItem = this.inverseLoadingComponentWrapperRenderer(id, renderedItem)
}
}
const ref = this.itemsIdsRefsMap[id] || (this.itemsIdsRefsMap[id] = React.createRef())
return (
<div className="bl-simple-infinite-scroll-item"
key={id}
ref={ref}>
{renderedItem}
</div>
)
}
loadingComponentRenderer() {
const {
loadingComponent
} = this.props
return (
<div className="bl-simple-infinite-scroll-loading-component"
key={uniqueId()}>
{loadingComponent}
</div>
)
}
loadingComponentWrapperRenderer(id, children) {
return (
<div className="bl-simple-infinite-scroll-loading-component-wrapper"
key={id}>
{children}
</div>
)
}
standardLoadingComponentWrapperRenderer(id, renderedItem) {
return this.loadingComponentWrapperRenderer(id, [
renderedItem,
this.loadingComponentRenderer()
])
}
inverseLoadingComponentWrapperRenderer(id, renderedItem) {
return this.loadingComponentWrapperRenderer(id, [
this.loadingComponentRenderer(),
renderedItem
])
}
axis() {
return this.props.axis === 'x' ? 'x' : 'y'
}
scrollProperty(axis) {
return axis == 'y' ? 'scrollTop' : 'scrollLeft'
}
offsetProperty(axis) {
return axis == 'y' ? 'offsetHeight' : 'offsetWidth'
}
scrollDimProperty(axis) {
return axis == 'y' ? 'scrollHeight' : 'scrollWidth'
}
hasScrollbarFunction(axis) {
return axis == 'y' ? hasVerticalScrollbar : hasHorizontalScrollbar
}
scrollToStart() {
const axis = this.axis()
this.scrollTo(
this.scrollProperty(axis),
!this.props.displayInverse ?
0
:
this.scrollDimProperty(axis)
)
}
scrollToEnd() {
const axis = this.axis()
this.scrollTo(
this.scrollProperty(axis),
!this.props.displayInverse ?
this.scrollDimProperty(axis)
:
0
)
}
scrollTo(scrollProperty, scrollPositionOrPropertyOfScrollable) {
const scrollableContentNode = this.node.current
if (scrollableContentNode) {
scrollableContentNode[scrollProperty] = isInt(scrollPositionOrPropertyOfScrollable) ?
scrollPositionOrPropertyOfScrollable
:
scrollableContentNode[scrollPositionOrPropertyOfScrollable]
}
}
scrollToId(id) {
if (this.itemsIdsRefsMap[id] && this.itemsIdsRefsMap[id].current) {
this.itemsIdsRefsMap[id].current.scrollIntoView()
}
}
handleScroll() {
const {
isInfiniteLoading,
infiniteLoadBeginEdgeOffset,
displayInverse
} = this.props
if (
this.props.onInfiniteLoad
&&
!isInfiniteLoading
&&
this.node.current
&&
!this.isLoading
) {
const axis = this.axis()
const scrollableContentNode = this.node.current
const scrollProperty = this.scrollProperty(axis)
const offsetProperty = this.offsetProperty(axis)
const scrollDimProperty = this.scrollDimProperty(axis)
const currentScroll = scrollableContentNode[scrollProperty]
const currentDim = scrollableContentNode[offsetProperty]
const scrollDim = scrollableContentNode[scrollDimProperty]
const finalInfiniteLoadBeginEdgeOffset = !isUndefined(infiniteLoadBeginEdgeOffset) ?
infiniteLoadBeginEdgeOffset
:
currentDim / 2
let thresoldWasReached = false
let memorizeLastElementBeforeInfiniteLoad = () => { }
if (!displayInverse) {
thresoldWasReached = currentScroll >= (scrollDim - finalInfiniteLoadBeginEdgeOffset)
}
else {
memorizeLastElementBeforeInfiniteLoad = () => {
// TODO
}
thresoldWasReached = currentScroll <= finalInfiniteLoadBeginEdgeOffset
}
if (thresoldWasReached) {
this.isLoading = true
memorizeLastElementBeforeInfiniteLoad()
this.props.onInfiniteLoad()
}
}
}
render() {
const {
items
} = this.props
return (
<div className="bl-simple-infinite-scroll"
ref={this.node}
onScroll={this.handleScroll}
onMouseOver={this.props.onInfiniteScrollMouseOver}
onMouseOut={this.props.onInfiniteScrollMouseOut}
onMouseEnter={this.props.onInfiniteScrollMouseEnter}
onMouseLeave={this.props.onInfiniteScrollMouseLeave}>
{this.itemsRenderer(items)}
</div>
)
}
}
And I use it like this in a chat app I am writing:
...
<BlSimpleInfiniteScroll items={chat.messages}
ref={this.infiniteScrollComponentRef}
initialId={() => lastOfArray(chat.messages).id}
itemRenderer={(id, i) => this.messageRenderer(id, i, chat.messages)}
loadingComponent={<BlLoadingSpinnerContainer />}
isInfiniteLoading={isChatLoading}
displayInverse
infiniteLoadBeginEdgeOffset={void 0}
infiniteLoadingBeginBottomOffset={void 0}
onInfiniteLoad={() => this.props.onLoadPreviousChatMessages(chat.id)}
onInfiniteScrollMouseEnter={this.handleInfiniteScrollMouseEnter}
onInfiniteScrollMouseLeave={this.handleInfiniteScrollMouseLeave} />
...
The problem is that as soon as I scroll until the thresold and onInfiniteLoad is called, before the loading spinner is showed and after the data has been loaded the scroll freezes and the component becomes unresponsive.
How can I resolve this issue?
When I render the spinner container and after the previous loaded messages, shouldn't React just append the new divs retaining the previously added items in order to maintain the component performant?
If not, what key concepts of React I am missing?
Thank you for your attention!
UPDATE: Here are the additional components:
BlOrderChat represents a chat window and renders BlSimpleInfiniteScroll:
import React from 'react'
import BlOrderChatMessage from './BlOrderChatMessage';
import { isEmpty, uniqueId } from '../../../js/utils';
import { chatSelector } from '../selectors';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';
import BlSimpleInfiniteScroll from '../../core/scroll/BlSimpleInfiniteScroll';
export default class BlOrderChat extends React.Component {
static BL_ORDER_CHAT_MESSAGE_ID_ATTR_PREFIX = 'blOrderChatMessage'
constructor(props) {
super(props)
this.messageRenderer = this.messageRenderer.bind(this)
this.infiniteScrollComponentRef = React.createRef()
}
scrollToBottom() {
this.infiniteScrollComponentRef.current && this.infiniteScrollComponentRef.current.scrollToStart()
}
messageRenderer(messageId, index, messages) {
const {
currentUser, chat
} = this.props
const message = messages[index]
const length = messages.length
const fromUser = chat.users.items[message.from_user_id]
const itemComponentRender = (children) => (
<div key={messageId}>
{children}
</div>
)
const messageIdAttr = `${BlOrderChat.BL_ORDER_CHAT_MESSAGE_ID_ATTR_PREFIX}${message.id}`
const renderMessageComponent = () => (
<BlOrderChatMessage id={messageIdAttr}
key={uniqueId() + message.id}
message={message.message}
sentUnixTs={message.sent_unix_ts}
currentUser={currentUser}
fromUser={fromUser}
usersInvolvedInChatLength={chat.users.order.length} />
)
let children = []
if (index === 0) {
// First message.
children = [
<div key={uniqueId()} className="bl-padding"></div>,
renderMessageComponent()
]
}
else if (index === length - 1) {
// Last message.
children = [
renderMessageComponent(onComponentDidMount),
<div key={uniqueId()} className="bl-padding"></div>
]
}
else {
// Message in the middle.
children = [
renderMessageComponent()
]
}
return itemComponentRender(children)
}
render() {
const {
chat: propsChat, isChatLoading,
currentUser
} = this.props
const chat = chatSelector(propsChat, currentUser)
const chatHasMessages = chat && !isEmpty(chat.messages)
return (
<div className="bl-order-chat">
<div className="bl-order-chat-header">
// ...
</div>
<div className="bl-order-chat-content">
{
(chatHasMessages &&
<div className="bl-order-chat-content-inner">
<div className="bl-order-chat-infinite-scroll">
<BlSimpleInfiniteScroll items={chat.messages}
ref={this.infiniteScrollComponentRef}
initialId={() => lastOfArray(chat.messages).id}
itemRenderer={(id, i) => this.messageRenderer(id, i, chat.messages)}
loadingComponent={<BlLoadingSpinnerContainer />}
isInfiniteLoading={isChatLoading}
displayInverse
infiniteLoadBeginEdgeOffset={void 0}
infiniteLoadingBeginBottomOffset={void 0}
onInfiniteLoad={() => this.props.onLoadPreviousChatMessages(chat.id)}
onInfiniteScrollMouseEnter={this.handleInfiniteScrollMouseEnter}
onInfiniteScrollMouseLeave={this.handleInfiniteScrollMouseLeave} />
</div>
</div>
)
||
(isChatLoading &&
<BlLoadingSpinnerContainer />
)
}
</div>
<div className="bl-order-chat-footer">
// ...
</div>
</div>
)
}
}
BlOrderChatBox, contains BlOrderChat:
import React from 'react'
import BlOrderChat from './BlOrderChat';
import BlAlert from '../../core/alert/BlAlert';
import BlLoadingSpinnerContainer from '../../core/animation/loading/BlLoadingSpinnerContainer';
export default class BlOrderChatBox extends React.Component {
constructor(props) {
super(props)
this.node = React.createRef()
}
render() {
const {
ordId, currentChat,
isCurrentChatLoading, currentUser,
err
} = this.props
return (
<div className="bl-order-chat-box" ref={this.node}>
<div className="bl-order-chat-box-inner">
{
(err &&
<BlAlert type="error" message={err} />)
||
(currentChat && (
// ...
<div className="bl-order-chat-box-inner-chat-content">
<BlOrderChat ordId={ordId}
chat={currentChat}
isChatLoading={isCurrentChatLoading}
onLoadPreviousChatMessages={this.props.onLoadPreviousChatMessages}
currentUser={currentUser} />
</div>
))
||
<BlLoadingSpinnerContainer />
}
</div>
</div>
)
}
}
And here is the component which renders BlOrderChatBox (it is the topmost stateful component):
import React from 'react'
import { POSTJSON } from '../../../js/ajax';
import config from '../../../config/config';
import { newEmptyArrayAble, arrayToArrayAbleItemsOrder, arrayAbleItemsOrderToArray, mergeArrayAbles, newArrayAble, firstOfArrayAble, isArrayAble } from '../../../js/data_structures/arrayable';
export default class BlOrderChatApp extends React.Component {
static NEW_CHAT_ID = 0
static MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX = 30
constructor(props) {
super(props)
this.currentUser = globals.USER
this.lastHandleSendMessagePromise = Promise.resolve()
this.newMessagesMap = {}
this.typingUsersDebouncedMap = {}
// Imagine this comes from a database.
const chat = {
// ...
}
const initialState = {
chats: newArrayAble(this.newChat(chat)),
currentChatId: null,
shouldSelectUserForNewChat: false,
newChatReceivingUsers: newEmptyArrayAble(),
isChatListLoading: false,
isCurrentChatLoading: false,
popoverIsOpen: false,
popoverHasOpened: false,
err: void 0,
focusSendMessageTextarea: false,
newChatsIdsMap: {},
currentChatAuthActs: {},
BlOrderChatComponent: null,
}
this.state = initialState
this.handleLoadPreviousChatMessages = this.handleLoadPreviousChatMessages.bind(this)
}
POST(jsonData, callback) {
let requestJSONData
if (typeof jsonData === "string") {
requestJSONData = {
action: jsonData
}
}
else {
requestJSONData = jsonData
}
return POSTJSON(config.ORDER_CHAT_ENDPOINT_URI, {
...requestJSONData,
order_chat_type: this.props.orderChatType,
}).then(response => response.json()).then(json => {
this.POSTResponseData(json, callback)
})
}
POSTResponseData(data, callback) {
if (data.err) {
this.setState({
err: data.err
})
}
else {
callback && callback(data)
}
}
newChat(chat) {
const newChat = {
id: (chat && chat.id) || BlOrderChatApp.NEW_CHAT_ID,
ord_id: this.props.ordId,
users: (chat && chat.users && (isArrayAble(chat.users) ? chat.users : arrayToArrayAbleItemsOrder(chat.users))) || newEmptyArrayAble(),
messages: (chat && chat.messages && (isArrayAble(chat.messages) ? chat.messages : arrayToArrayAbleItemsOrder(chat.messages))) || newEmptyArrayAble(),
first_message_id: (chat && chat.first_message_id) || null,
typing_users_ids_map: (chat && chat.typing_users_ids_map) || {},
}
return newChat
}
isChatNew(chat) {
return (
chat
&&
(chat.id == BlOrderChatApp.NEW_CHAT_ID || this.state.newChatsIdsMap[chat.id])
)
}
loadPreviousChatMessages(chatId, lowestMessageIdOrNull, makeChatIdCurrent) {
this.POST({
act: 'loadPreviousChatMessages',
chat_id: chatId,
lowest_message_id: lowestMessageIdOrNull,
max_number_of_messages_to_load: BlOrderChatApp.MAX_NUMBER_OF_MESSAGES_TO_LOAD_PER_AJAX
}, json => {
this.setState(prevState => {
const chat = prevState.chats.items[chatId]
const messages = arrayToArrayAbleItemsOrder(json.messages)
const newChat = {
...chat,
messages: mergeArrayAbles(messages, chat.messages)
}
const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat))
return {
...(makeChatIdCurrent ?
{
currentChatId: chatId,
focusSendMessageTextarea: true,
}
:
{
currentChatId: prevState.currentChatId,
}
),
chats,
isCurrentChatLoading: false,
}
})
})
}
loadPreviousChatMessagesIfNotAllLoaded(chatId) {
let lowestMessageIdOrNull
const chat = this.state.chats.items[chatId]
if (
!this.isChatNew(chat)
&&
(lowestMessageIdOrNull = (chat.messages.order.length && firstOfArrayAble(chat.messages).id) || null)
&&
lowestMessageIdOrNull != chat.first_message_id
) {
this.setState({
isCurrentChatLoading: true
}, () => {
this.loadPreviousChatMessages(chat.id, lowestMessageIdOrNull)
})
}
}
handleLoadPreviousChatMessages(chatId) {
this.loadPreviousChatMessagesIfNotAllLoaded(chatId)
}
// ...
render() {
const currentChat = this.state.chats.items[this.state.currentChatId] || null
const err = this.state.err
return (
<div className="bl-order-chat-app">
<BlOrderChatBox currentUser={this.currentUser}
chats={arrayAbleItemsOrderToArray(this.state.chats)}
currentChat={currentChat}
isCurrentChatLoading={this.state.isCurrentChatLoading}
onLoadPreviousChatMessages={this.handleLoadPreviousChatMessages}
err={err} />
</div>
)
}
}
I tried to remove all the irrelevant code to simplify the reading. Also here is the file which contains the chatSelector function (normalizes the chat array-able object) and the *ArrayAble* functions (an array-able object to me is basically an object which maps objects through their ids in items and has an order property which keeps the sort):
import { isUndefined, unshiftArray, findIndex } from "../utils";
export function chatSelector(chat, currentUser) {
const newChat = { ...chat }
newChat.messages = arrayAbleItemsOrderToArray(chat.messages).sort((a, b) => {
const sortByUnixTs = a.sent_unix_ts - b.sent_unix_ts
if (sortByUnixTs == 0) {
return a.id - b.id
}
return sortByUnixTs
})
newChat.users = arrayAbleItemsOrderToArray(chat.users).filter(user => user.id != currentUser.id)
return newChat
}
/**
* Given an array-able object, returns its array representation using an order property.
* This function acts as a selector function.
*
* The array-able object MUST have the following shape:
*
* {
* items: {},
* order: []
* }
*
* Where "items" is the object containing the elements of the array mapped by values found in "order"
* in order.
*
* #see https://medium.com/javascript-in-plain-english/https-medium-com-javascript-in-plain-english-why-you-should-use-an-object-not-an-array-for-lists-bee4a1fbc8bd
* #see https://medium.com/#antonytuft/maybe-you-would-do-something-like-this-a1ab7f436808
*
* #param {Object} obj An object.
* #param {Object} obj.items The items of the object mapped by keys.
* #param {Array} obj.order The ordered keys.
* #return {Array} The ordered array representation of the given object.
*/
export function arrayAbleItemsOrderToArray(obj) {
const ret = []
for (const key of obj.order) {
if (!isUndefined(obj.items[key])) {
ret[ret.length] = obj.items[key]
}
}
return ret
}
export function arrayToArrayAbleItemsOrder(array, keyProp = "id") {
const obj = newEmptyArrayAble()
for (const elem of array) {
const key = elem[keyProp]
obj.items[key] = elem
obj.order[obj.order.length] = key
}
return obj
}
export function newEmptyArrayAble() {
return {
items: {},
order: []
}
}
export function isEmptyArrayAble(arrayAbleObj) {
return !arrayAbleObj.order.length
}
export function mergeArrayAbles(arrayAbleObj1, arrayAbleObj2, prependObj2 = false) {
const obj = newEmptyArrayAble()
for (const key of arrayAbleObj1.order) {
if (isUndefined(arrayAbleObj1.items[key])) {
continue
}
obj.items[key] = arrayAbleObj1.items[key]
obj.order[obj.order.length] = key
}
for (const key of arrayAbleObj2.order) {
if (isUndefined(arrayAbleObj2.items[key])) {
continue
}
if (!(key in obj.items)) {
if (!prependObj2) {
// Default.
obj.order[obj.order.length] = key
}
else {
unshiftArray(obj.order, key)
}
}
obj.items[key] = arrayAbleObj2.items[key]
}
return obj
}
export function newArrayAble(initialItem = void 0, keyProp = "id") {
const arrayAble = newEmptyArrayAble()
if (initialItem) {
arrayAble.items[initialItem[keyProp]] = initialItem
arrayAble.order[arrayAble.order.length] = initialItem[keyProp]
}
return arrayAble
}
export function lastOfArrayAble(obj) {
return (
(
obj.order.length
&&
obj.items[obj.order[obj.order.length - 1]]
)
||
void 0
)
}
Thank you for your help. If there's something missing which I should have included, please, let me know!
UPDATE: Thanks to Sultan H. it has improved, though the scroll still blocks as soon as I get the reply from the server. See it here: https://streamable.com/3nzu0
Any idea on how to improve this behaviour further?
Thanks!
Here is an attempt to resolve the performance issue, it's not preferrable to do tasks inside the Arrow Function that calculates the new state, in this case, at loadPreviousChatMessages you are calculating stuff in the callback, which may yeild to a load while setting the state on that context.
Preferrable Changes, replace this.setState inside your function with this code, all I've done here is clear the context by moving all the tasks out:
const chat = this.state.chats.items[chatId];
const messages = arrayToArrayAbleItemsOrder(json.messages);
const newChat = {
...chat,
messages: mergeArrayAbles(messages, chat.messages);
}
const chats = mergeArrayAbles(prevState.chats, newArrayAble(newChat));
const newState = {
...(
makeChatIdCurrent ?
{
currentChatId: chatId,
focusSendMessageTextarea: true,
}
:
{
currentChatId: this.state.currentChatId,
}
),
chats,
isCurrentChatLoading: false,
};
this.setState(() => newState);
If that doesn't entirely solve the issue, can you tell if there was at least an improvment?

ReactJS: prepare data before render

I am creating a pagination component and it's have piece of prepare logic before render.
import React, {PropTypes, Component} from "react";
import PaginationItemComponent from "scripts/components/paginationItemComponent";
const MAX_ITEMS = 10;
const prepareShiftItems = (activeShift, pagesCount) => {
let toLeftCaption, toRightCaption, endPage;
const startPage = activeShift * MAX_ITEMS + 1;
const end = (activeShift + 1) * MAX_ITEMS;
if (end >= pagesCount) {
toRightCaption = null;
endPage = pagesCount;
} else {
endPage = end;
toRightCaption = "-->";
}
if (activeShift === 0) {
toLeftCaption = null;
} else {
toLeftCaption = "<--";
}
let activePages = [];
for (let i = startPage; i <= endPage; i++) {
activePages.push(i);
}
return {
toLeftCaption,
toRightCaption,
activePages
};
};
const getActiveShiftByPageNumber = (activePageNumber) => {
const div = (activePageNumber - 1) / MAX_ITEMS;
return Math.floor(div);
};
class PaginationComponent extends Component {
constructor(props) {
super(props);
this.state = {
activePageNumber: props.activePageNumber,
activeShift: getActiveShiftByPageNumber(props.activePageNumber)
};
}
componentWillReceiveProps(nextProps) {
this.setState({
activePageNumber: nextProps.activePageNumber,
activeShift: getActiveShiftByPageNumber(nextProps.activePageNumber)
});
}
setActivePage(activePageNumber) {
if (this.state.activePageNumber === activePageNumber) {
return;
}
const {onChange} = this.props;
if (onChange) {
onChange(activePageNumber);
}
this.setState({
activePageNumber
});
}
moveToRight() {
this.setState({
activeShift: this.state.activeShift + 1
});
}
moveToLeft() {
this.setState({
activeShift: this.state.activeShift - 1
});
}
render() {
if (this.props.pagesCount < 2) {
return null;
}
const {activePages, toLeftCaption, toRightCaption} =
prepareShiftItems(
this.state.activeShift,
this.props.pagesCount
);
return (
<ul className="pagination-component">
{
toLeftCaption &&
<li onClick={this.moveToLeft.bind(this)} className="to-left">
{toLeftCaption}
</li>
}
{
activePages.map((pageNumber) =>
<PaginationItemComponent
key={pageNumber}
pageNumber={pageNumber}
onClick={this.setActivePage.bind(this, pageNumber)}
isActive={pageNumber === this.state.activePageNumber}/>)
}
{
toRightCaption &&
<li onClick={this.moveToRight.bind(this)} className="to-right">
{toRightCaption}
</li>
}
</ul>
);
}
}
const {number, func} = PropTypes;
PaginationComponent.propTypes = {
pagesCount: number.isRequired,
activePageNumber: number.isRequired,
onChange: func
};
export default PaginationComponent;
My question is: do i need to call prepareShiftItems first time in constructor and then in componentWillReceiveProps or i can leave it as it is. I want to know the best practice for such cases. Thanks for answers.

Categories

Resources