Angular 6 Async Binding Crazy Behavior - angular-material

I am using a material dialog to display details of entries. I pass data to dialogue in constructor. However despite the fact that variable exists, and is not null, data is not displayed for some reason.
Bellow is my Dialogue.ts
import {Component, Inject, OnDestroy, OnInit} from '#angular/core';
import {Incident} from '../incident';
import {IncidentsService} from '../incidents.service';
import {MAT_DIALOG_DATA} from '#angular/material';
import {Subscription} from 'rxjs';
#Component({
selector: 'app-incident-details',
templateUrl: './incident-details.component.html',
styleUrls: ['./incident-details.component.css']
})
export class IncidentDetailsComponent implements OnInit, OnDestroy {
public incident: Incident;
private subscription: Subscription;
constructor(#Inject(MAT_DIALOG_DATA) public data: any, private incidentsService: IncidentsService) {
this.incident = this.data.incident;
console.log('?????????? ' + JSON.stringify(this.incident));
}
renderPDF(id: number) {
this.subscription = this.incidentsService.getPdfById(id)
.subscribe(response => {
// console.log('Response: ' + response);
const file = new Blob([response], {type: 'application/pdf'});
const fileURL = URL.createObjectURL(file);
// this.dialogRef.close();
window.open(fileURL);
});
}
ngOnInit() {
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}
Bellow is the template dialogue.html
<mat-card>
<h2 mat-dialog-title style="text-align: center; text-decoration: underline;"><b>Έκθεση Ιστολογικής Εξέτασης</b></h2>
<br/>
<mat-dialog-content>
<div>{{incident|json}}</div> **// Printing Incident Data**
<table>
<tr>
<td><b>Αρ. Πρωτοκόλλου: </b>{{incident.protocolNo}} </td>
<td><b>Ημερομηνία Παραλαβής: </b>{{incident.dateIN | date:'dd-MM-yyyy'}} </td>
</tr>
<tr>
<td><b>Ονοματεπώνυμο: </b> {{incident.patient?.lastName.toString()}} {{incident.patient?.firstName}} </td>
<td><b>Ημ. Γέννησης: </b> {{incident.patient?.birthday | date:'dd-MM-yyyy'}} </td>
</tr>
</table>
<p><b>Αποστέλων Ιατρός: </b> {{incident.doctor?.lastName}} {{incident.doctor?.firstName}}</p>
<div *ngIf="incident.klinikesPlirofories">
<h3 class="subSection" mat-dialog-title>Κλινικές Πληροφορίες</h3>
<p>{{incident.klinikesPlirofories}}</p>
</div>
<div *ngIf="incident.yliko">
<h3 class="subSection" mat-dialog-title>Υλικό</h3>
<p>{{incident?.yliko}}</p>
</div>
<div *ngIf="incident.makro">
<h3 class="subSection" mat-dialog-title>Μακροσκοπικά</h3>
<p>{{incident?.makro}}</p>
</div>
<div *ngIf="incident.mikro">
<h3 class="subSection" mat-dialog-title>Μικροσκοπικά</h3>
<p>{{incident?.mikro}}</p>
</div>
<div *ngIf="incident.anoso">
<h3 class="subSection" mat-dialog-title>Ανοσοϊστοχημικός Έλεγχος</h3>
<p>{{incident.anoso}}</p>
</div>
<div *ngIf="incident.anosoEkthesi">
<h3 class="subSection" mat-dialog-title>Ανοσο Έκθεση</h3>
<p>{{incident.anosoEkthesi}}</p>
</div>
<div *ngIf="incident.histo">
<h3 class="subSection" mat-dialog-title>Iστοχημικός Έλεγχος</h3>
<p>{{incident.histo}}</p>
</div>
<div *ngIf="incident.symperasma">
<h3 class="subSection" mat-dialog-title>Συμπέρασμα</h3>
<p>{{incident?.symperasma}}</p>
</div>
<div *ngIf="incident.mikroskopikaSymperasma">
<h3 class="subSection" mat-dialog-title>Μικροσκοπικά Συμπέρασμα</h3>
<p>{{incident?.mikroskopikaSymperasma}}</p>
</div>
<div>
<h3 class="subSection" mat-dialog-title>Πληρωμή</h3>
<p>{{incident.isPayed ? 'Ναι' : 'Όχι'}}</p>
</div>
<div>
<h3 class="subSection" mat-dialog-title>Καρκίνος</h3>
<p>{{incident.cancer ? 'Θετικό' : 'Αρνητικό'}}</p>
</div>
<div *ngIf="incident.signingDoctor">
<h3 class="subSection" mat-dialog-title>Υπογράφων Ιατρός</h3>
<p>{{incident.signingDoctor?.lastName}} {{incident.signingDoctor?.firstName}}</p>
</div>
<div *ngIf="incident.simpliromatikiEkthesi">
<h3 class="subSection" mat-dialog-title>Συμπληρωματική Έκθεση</h3>
<p>{{incident?.simpliromatikiEkthesi}}</p>
</div>
</mat-dialog-content>
</mat-card>
<!--<div>{{incident|json}}</div>-->
<mat-dialog-actions>
<button mat-raised-button mat-dialog-close color="warn">Close</button>
<button mat-raised-button color="primary" (click)="renderPDF(incident.id)">
<i class="material-icons">print</i>
</button>
</mat-dialog-actions>
And bellow there is a screenshot:
As you can see incident get printed and exists with data both in console and in template. However everywhere else in template, by calling incident.member prints nothing.
I am opening dialogue and passing data to it using the function below from another component.
async openDialog(id: number) {
let incident: Incident;
this.subscriptions.push(
await this.incidentsService.getIncidentByID(id).subscribe(response => {
incident = response;
const dialogRef = this.dialog.open(IncidentDetailsComponent, {height: '900px', width: '900px', 'data': {'incident': response}});
}));
// const dialogRef = this.dialog.open(IncidentDetailsComponent, {height: '900px', width: '900px', 'data': {'incident': incident}});
}
Bellow is getIncidentByID() function
getIncidentByID(id: number): Observable<Incident> {
const incidentUrl = 'incidents/details/' + id;
return this.http.get<Incident>(incidentUrl)
.pipe(catchError(ErrorHandler.handleError));
}
Bellow is Incident.ts
import {Patient} from '../patients/patient';
import {Clinic} from '../clinics/clinic';
import {Doctor} from 'app/doctors/doctor';
import {SigningDoctor} from '../signing-doctors/signing-doctor';
import {BodyPart} from '../body-part/BodyPart';
export class Incident {
private _id: number;
private _protocolNo: string;
private _dateIN: any;
private _dateOUT: any;
private _isPayed: boolean;
private _yliko: string;
private _makro: string;
private _anoso: string;
private _mikro: string;
private _symperasma: string;
private _patient: Patient;
private _clinic: Clinic;
private _doctor: Doctor;
private _histo: string;
private _klinikesPlirofories: string;
private _simpliromatikiEkthesi: string;
private _signingDoctor: SigningDoctor;
private _mikroskopikaSymperasma: string;
private _cancer: boolean;
private _anosoEkthesi: string;
private _bodyPart: BodyPart;
get id(): number {
return this._id;
}
set id(value: number) {
this._id = value;
}
get protocolNo(): string {
return this._protocolNo;
}
set protocolNo(value: string) {
this._protocolNo = value;
}
get dateIN(): any {
return this._dateIN;
}
set dateIN(value: any) {
this._dateIN = value;
}
get dateOUT(): any {
return this._dateOUT;
}
set dateOUT(value: any) {
this._dateOUT = value;
}
get isPayed(): boolean {
return this._isPayed;
}
set isPayed(value: boolean) {
this._isPayed = value;
}
get yliko(): string {
return this._yliko;
}
set yliko(value: string) {
this._yliko = value;
}
get makro(): string {
return this._makro;
}
set makro(value: string) {
this._makro = value;
}
get anoso(): string {
return this._anoso;
}
set anoso(value: string) {
this._anoso = value;
}
get mikro(): string {
return this._mikro;
}
set mikro(value: string) {
this._mikro = value;
}
get symperasma(): string {
return this._symperasma;
}
set symperasma(value: string) {
this._symperasma = value;
}
get mikroskopikaSymperasma(): string {
return this._mikroskopikaSymperasma;
}
set mikroskopikaSymperasma(value: string) {
this._mikroskopikaSymperasma = value;
}
get patient(): Patient {
return this._patient;
}
set patient(value: Patient) {
this._patient = value;
}
get clinic(): Clinic {
return this._clinic;
}
set clinic(value: Clinic) {
this._clinic = value;
}
get doctor(): Doctor {
return this._doctor;
}
set doctor(value: Doctor) {
this._doctor = value;
}
get histo(): string {
return this._histo;
}
set histo(value: string) {
this._histo = value;
}
get klinikesPlirofories(): string {
return this._klinikesPlirofories;
}
set klinikesPlirofories(value: string) {
this._klinikesPlirofories = value;
}
get simpliromatikiEkthesi(): string {
return this._simpliromatikiEkthesi;
}
set simpliromatikiEkthesi(value: string) {
this._simpliromatikiEkthesi = value;
}
get signingDoctor(): SigningDoctor {
return this._signingDoctor;
}
set signingDoctor(value: SigningDoctor) {
this._signingDoctor = value;
}
get cancer(): boolean {
return this._cancer;
}
set cancer(value: boolean) {
this._cancer = value;
}
get anosoEkthesi(): string {
return this._anosoEkthesi;
}
set anosoEkthesi(value: string) {
this._anosoEkthesi = value;
}
get bodyPart(): BodyPart {
return this._bodyPart;
}
set bodyPart(value: BodyPart) {
this._bodyPart = value;
}
}
Crazy or what??
Has anybody faced anything similar?? Any Ideas? I am bunging my head two days now with this... Something wrong with the Observable??

The problem was in the way i was getting the data. If you look closely to the screenshot the printed object is within a table [{object}]. So i changed the code from:
async openDialog(id: number) {
let incident: Incident;
this.subscriptions.push(
await this.incidentsService.getIncidentByID(id).subscribe(response => {
incident = response;
const dialogRef = this.dialog.open(IncidentDetailsComponent, {height: '900px', width: '900px', 'data': {'incident': response}});
}));
// const dialogRef = this.dialog.open(IncidentDetailsComponent, {height: '900px', width: '900px', 'data': {'incident': incident}});
}
To:
async openDialog(id: number) {
let incident: Incident;
this.subscriptions.push(
await this.incidentsService.getIncidentByID(id).subscribe(response => {
incident = response[0]; <-- HERE IS THE CHANGE
const dialogRef = this.dialog.open(IncidentDetailsComponent, {height: '900px', width: '900px', 'data': {'incident': response}});
}));
// const dialogRef = this.dialog.open(IncidentDetailsComponent, {height: '900px', width: '900px', 'data': {'incident': incident}});
}
And everything Worked. So stupid of me....

Related

I want to get my modified post, net ADD modified post on list (React - using react-redux)

// store.js
import { createStore, combineReducers } from "redux";
const INITIAL_STATE = [];
const postingReducer = (state, action) => {
if (action.type === "POST_SUCCESS") {
const newPost = {
title: action.payload.title,
content: action.payload.content,
};
return state.concat(newPost);
} else if (action.type === "POST_DELETE") {
return state.filter((item) => {
return item.title !== action.payload;
});
} else if (action.type === "POST_EDIT_SUCCESS") {
const modifiedPost = {
title: action.payload.title,
content: action.payload.content,
};
// console.log(modifiedPost)
// console.log(state.map((item,index)=>item[0]))
// console.log(state[0].title)
// state[0].title = modifiedPost.title
// state[0].content = modifiedPost.title
return state.concat(modifiedPost);
}
return INITIAL_STATE;
};
const store = createStore(
combineReducers({
posting: postingReducer,
})
);
export default store;
// EditForm.js
import { Link , useParams} from "react-router-dom";
import { useState } from "react";
import { useDispatch , useSelector } from "react-redux";
const EditForm = () => {
const dispatch = useDispatch();
const state = useSelector(state=>state.posting)
const params = useParams()
const [titleInput, setTitleInput] = useState(state[params.title].title);
const [contentInput, setContentInput] = useState(state[params.title].content);
const publishBtn = () => {
window.alert('Modified.')
return dispatch({
type: "POST_EDIT_SUCCESS",
payload: { title: titleInput, content: contentInput },
});
}
return (
<>
<form>
<div>
<label htmlFor="Title">Title</label>
<br />
<input
id="Title"
value={titleInput}
onChange={event=>{setTitleInput(event.target.value)}}
type="text"
/>
<br />
</div>
<br />
<div>
<label htmlFor="Content">Content</label>
<br />
<textarea
id="Content"
value={contentInput}
onChange={event=>{setContentInput(event.target.value)}}
type="text"
/>
<br />
</div>
<div>
<Link to="/">
<button onClick={publishBtn}>Modify</button>
</Link>
</div>
</form>
</>
);
};
export default EditForm
I want to get my modified post, net ADD modified post on list.
when i click Modify button in 'EditForm.js' I want to get my modified post, net ADD modified post on list.
in this situation, modified post added on the postlist.
I don't know how to fix "POST_EDIT_SUCCESS" return in 'store.js'
please help me!

Cannot read property id of undefined, error found in the map function (react-rails)

I'm trying to make my submit button add on to the list of items, my seed data in rails gets rendered fine but when I try to add a new item when clicking submit, I get this error Cannot read property id of undefined which is found at the map function. The post request works fine, and only after I refresh the page, the item I add gets rendered on the page. Any help would be appreciated!
class TodoList extends React.Component {
render() {
const {todos} = this.props
var todoItems = todos.map(title => <TodoItem key={title.id} title={title}/>) //here
return (
<ListGroup className="my-2">
<h2 className="text-center">Items</h2>
{todoItems}
</ListGroup>
)
}
}
export default TodoList
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
todos: [],
stuff: ''
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.addNewTodo = this.addNewTodo.bind(this)
}
handleChange = event => {
this.setState({
stuff:event.target.value
})
}
handleSubmit = event => {
event.preventDefault()
let body = JSON.stringify({todo: {item: this.state.stuff} })
fetch('http://localhost:3000/api/v1/todos', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: body,
})
.then(response => {response.json()})
.then(todo => {this.addNewTodo(todo)})
}
addNewTodo(todo){
this.setState({
todos: this.state.todos.concat(todo)
})
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-10 mx-auto mt-4">
<h1 className="text-center">Todo List</h1>
<TodoInput
stuff={this.state.stuff}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
/>
<TodoList
todos={this.state.todos}
/>
</div>
</div>
</div>
)
}
}
when your component is renders first "todos" won't have any value, it will be undefined. try below code
class TodoList extends React.Component {
render() {
const { todos } = this.props;
if (todos) {
return (
<ListGroup className="my-2">
<h2 className="text-center">Items</h2>
{todos.map(title => (
<TodoItem key={title.id} title={title} />
))}
</ListGroup>
);
}
return ""; } }
export default TodoList;

Upload image using react and rails

I am trying to upload image using react into rails active storage.
My component is:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import User from './../../Assets/user.png';
import { addAvatar } from './../../actions/userAction';
class UploadAvatar extends Component {
state = {
image: null,
};
fileSelectHandler = (e) => {
this.setState({
image: e.target.files[0],
});
};
fileUploadHandler = () => {
if (this.state.image) {
console.log(this.state.image, this.props.userId);
const fd = new FormData();
fd.append('avatar', this.state.image, this.state.image.name);
this.props.addAvatar({ avatar: fd, userId: this.props.userId });
}
};
render() {
return (
<div className="avatar ">
<div className="avatar-content shadow-lg">
<div className="avatar-pic">
<img src={User} alt="userpic" />
</div>
<p>ADD PHOTO</p>
<input type="file" onChange={this.fileSelectHandler} />
<div className="avatar-foot">
<button type="button" className="skip">
SKIP
</button>
<button type="button" onClick={this.fileUploadHandler} className="submit">
SUBMIT
</button>
</div>
</div>
</div>
);
}
}
const mapStateToProps = store => ({
userId: store.userReducer.userId,
userEmail: store.userReducer.userEmail,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
addAvatar,
},
dispatch,
);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(UploadAvatar);
My ajax.js:
/* eslint-disable no-console no-param-reassign */
let CLIENT_URL = 'http://localhost:3000/api/v1';
function getDefaultOptions() {
return {
method: 'GET',
// credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
};
}
function buildParam(params, asJSON = true) {
if (asJSON) {
return JSON.stringify(params);
}
const fD = new FormData();
Object.keys(params).forEach((param) => {
fD.append(param, params[param]);
});
return fD;
}
function ajax(uri, options = {}) {
const defaultOptions = getDefaultOptions();
options.method = options.method ? options.method : defaultOptions.method;
if (!options.formType) {
options.headers = options.headers ? options.headers : defaultOptions.headers;
}
options.credentials = options.credentials ? options.credentials : defaultOptions.credentials;
if (options.body && !options.formType) {
options.body = buildParam(options.body);
}
uri = uri.startsWith('/') ? uri : `/${uri}`;
return fetch(`${CLIENT_URL}${uri}`, options)
.then(data => data.json())
.catch(errors => console.log(errors));
}
export default ajax;
But on rails side I am getting empty object in avatar. Don't know why?
Please have a look to screenshot for rails side.
But from postman if I am trying to upload it is working fine.
Is there any alternative way to upload image.

Angular 4 - update list after delete

I am building a site for upcoming concert events. When I create a new event I got it to save to the backend (Ruby on Rails) and immediately display the new event in the event list. However, when I delete an event, the event gets deleted in the database but the front end does not refresh.
event-create.component.ts
import { Component, OnDestroy, OnInit } from '#angular/core';
import { FormControl, FormGroup, Validators, FormBuilder } from "#angular/forms";
import { EventService } from "../event.service";
import { Event } from "../event";
import { Venue } from "../../venue/venue";
import { VenueService } from "../../venue/venue.service";
import { ActivatedRoute, Router} from '#angular/router';
import { BsDatepickerConfig } from 'ngx-bootstrap/datepicker'
import { DataService } from '../../data.service';
#Component({
selector: 'app-event-create',
templateUrl: './event-create.component.html',
styleUrls: ['./event-create.component.scss']
})
export class EventCreateComponent implements OnInit {
id: number;
event: Event;
venues: Venue[];
bsValue: Date = new Date();
date: string;
time: string;
datepickerModel: Date = new Date();
bsConfig: Partial<BsDatepickerConfig>;
eventTime: Date = new Date(this.bsValue.getFullYear(), this.bsValue.getMonth(), this.bsValue.getDay(), 19, 0, 0, 0);
mstep: number = 15;
eventForm: FormGroup;
constructor(private route: ActivatedRoute,
private router: Router,
private eventService: EventService,
private venueService: VenueService,
private data: DataService,
fb: FormBuilder) {
this.eventForm = fb.group({
'title': [null, Validators.required],
'venue': [1, Validators.required],
'date': null,
'time': null,
'description': [null, Validators.required]
});
this.bsConfig = Object.assign({}, { containerClass: 'theme-red' });
}
ngOnInit(): void {
this.getVenues();
}
getVenues(){
this.venueService.query().subscribe(
venues => {
this.venues = venues;
},
err => {
console.log(err);
}
);
}
onSubmit() {
Object.keys(this.eventForm.controls).forEach(field => {
const control = this.eventForm.get(field);
control.markAsTouched({ onlySelf: true });
});
if (this.eventForm.valid) {
let date: Date = new Date(this.eventForm.controls['date'].value);
let time: Date = new Date(this.eventForm.controls['time'].value);
let event: Event = new Event(
null,
this.eventForm.controls['venue'].value,
this.formatTimestamp(date, time),
this.eventForm.controls['title'].value,
this.eventForm.controls['description'].value
);
this.eventService.save(event).subscribe();
this.data.addEvent(event);
this.router.navigate(['/event']);
}
}
formatTimestamp(date: Date, time: Date): number {
let timezoneOffset: number = time.getTimezoneOffset() / 60;
if((time.getHours() + timezoneOffset) > 23){
date.setDate(date.getDate() + 1);
}
date.setUTCHours(time.getUTCHours());
date.setUTCMinutes(time.getMinutes());
date.setUTCSeconds(time.getSeconds());
return date.getTime();
}
redirectUserPage() {
this.router.navigate(['/user']);
}
}
event-index.component.ts
import { Component, OnInit } from '#angular/core';
import { Event } from '../event';
import { EventService } from '../event.service';
import { Router } from '#angular/router';
import { DataService } from '../../data.service';
import { Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-event-index',
templateUrl: './event-index.component.html',
styleUrls: ['./event-index.component.scss']
})
export class EventIndexComponent implements OnInit {
#Output()
someEvent = new EventEmitter();
event: Event;
private events: Event[];
constructor(
private router: Router,
private eventService: EventService,
private data: DataService
) { }
ngOnInit() {
this.getEvents();
this.data.currentEvent.subscribe(event => this.event = event);
}
getEvents(){
this.eventService.query().subscribe(
events => {
this.events = events;
},
err => {
console.log(err);
}
)
}
newEvent(){
this.router.navigate(['/event/create']);
}
editEvent(event: Event){
if(event){
this.router.navigate(['/event/edit', event]);
}
}
deleteEvent(event: Event){
let index = this.events.indexOf(event);
if(index > -1){
this.eventService.delete(event).subscribe(
event => {
this.events.slice(index, 1);
}
);
}
}
}
data.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Event } from './event/event';
#Injectable()
export class DataService{
currentEvent: BehaviorSubject<Event> = new BehaviorSubject<Event>(null);
public addEvent(newEvent: Event): void{
this.currentEvent.next(newEvent);
}
}
event-create.component.html
<div class="row justify-content-center">
<h1>New Event</h1>
</div>
<div class='row justify-content-center'>
<div class='col-6'>
<form [formGroup]='eventForm' (ngSubmit)="onSubmit(eventForm.value)">
<div class='form-group'>
<label>Title</label>
<input type="text" class='form-control' [ngClass]="{'is-invalid': eventForm.controls['title'].invalid && eventForm.controls['title'].touched}" formControlName="title">
<div *ngIf="eventForm.controls['title'].hasError('required')" class="invalid-feedback">Title is required</div>
</div>
<div class='form-group'>
<label>Description</label>
<textarea formControlName="description" class='form-control' [ngClass]="{'is-invalid': eventForm.controls['description'].invalid && eventForm.controls['description'].touched}"></textarea>
<div *ngIf="eventForm.controls['description'].hasError('required')" class="invalid-feedback">Description is required</div>
</div>
<div class='form-group'>
<label>Venue</label>
<select class='form-control' formControlName='venue'>
<option *ngFor="let venue of venues" [value]="venue.id">{{venue.name}}</option>
</select>
</div>
<div class="form-group">
<label>Date</label>
<input type="text" class="form-control" bsDatepicker [bsConfig]="bsConfig" [(ngModel)]="datepickerModel" formControlName='date'>
</div>
<div class='form-group'>
<label>Time</label>
<timepicker [(ngModel)]="eventTime" [minuteStep]="mstep" formControlName='time'></timepicker>
</div>
<button type="submit" class='btn btn-default'>Submit</button>
<pre>{{eventForm.value | json}}</pre>
</form>
</div>
</div>
event.service.ts
import { Injectable } from '#angular/core';
import { Event } from './event';
import { HttpClient } from '#angular/common/http';
import { Response } from '#angular/http';
import { Observable } from 'rxjs/Observable'
#Injectable()
export class EventService {
private apiUrl = 'http://localhost:3000/events';
constructor(private http: HttpClient) {}
query(): Observable<any>{
return this.http.get(this.apiUrl);
}
get(id: number): Observable<Event>{
return null;
}
save(event: Event): Observable<any>{
return this.http.post(this.apiUrl, event);
}
delete(event: Event): Observable<any>{
console.log(event);
return this.http.delete(this.apiUrl + '/' + event.id)
}
update(event: Event): Observable<Event>{
return null;
}
}
You are using slice() where you should be using splice() in deleteEvent()
Try changing:
this.events.slice(index, 1);
To
this.events.splice(index, 1);

React App works perfectly on localhost, but errors out on Heroku

Very frustrating errors today. I've spent the entire day trying to debug my small application that works perfectly on my localhost, but errors out on heroku occasionally.
If I refresh the page several times, I can achieve the login. But it takes 2-3 refreshes.
The two errors I get when logging in a user are -
Uncaught TypeError: Cannot read property 'exercise_name' of undefined
And
Uncaught TypeError: Cannot read property '_currentElement' of null
Now I basically know what the issue is. I must not have my props when I initially try to map over them. One of the props is an array of exercises, with one of the keys as 'exercise_name.' I'm guessing it has to do with the speed I receive the from local host, compared to heroku's ajax calls.
Here is my issue,
I do not know which component this is coming from since, I use exercise_name in 4 components. Heroku has line numbers, but they are of no help since it doesn't point to anything in my application and I can't drop debuggers in heroku like I can on my machine here.
I've tried setting default props in mapStateToProps like so -
allExercises: state.entities.exercise || []
Did not work.
Ive tried wrapping things in conditionals in my components. Hasn't worked.
The following four components use exercise_name. Any direction would be greatly appreciated.
I understand the following is a lot of code. I would be completely content with an answer letting me know how to find which lines of code are producing these errors on heroku, or how to debug on heroku in general.
component 1
import React from 'react';
import { withRouter } from 'react-router-dom';
import SetResultContainer from '../setresult/create_setresult_container';
class ExerciseIndex extends React.Component {
constructor(props) {
super(props);
this.state = {
inputVal: '',
active: 'FIRST',
name: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
this.setState({ inputVal: e.target.value })
}
componentDidMount() {
this.props.requestAllExercises();
}
handleClick(e) {
this.setState({ inputVal: e.currentTarget.attributes.value.value})
}
handleSubmit(e) {
let newActive = this.state.active === 'FIRST' ? 'SECOND' : null
let allExercises = this.props.allExercises;
let selected;
let name;
if (allExercises) {
allExercises.forEach(exercise => {
if (exercise.exercise_name === this.state.inputVal) {
selected = exercise,
name = exercise.exercise_name
}
})
e.preventDefault();
}
if (!name) {
this.setState({inputVal: 'Invalid Input, Please try Again'})
return 'Invalid Input'
}
this.props.requestExercise(selected)
this.setState({inputVal: '', active: newActive, name: name})
this.props.requestAllExercises();
}
render() {
let allExercises = this.props.allExercises || [{ exercise_name: '' }]
let match = allExercises.map((exercise) => {
if (this.state.inputVal === '') return [];
let matched = [];
if (this.state.inputVal.length > 0) {
for (var j = 0; j < this.state.inputVal.length; j++) {
matched = [];
if (exercise.exercise_name.slice(0, j + 1).toUpperCase() === this.state.inputVal.slice(0, j + 1).toUpperCase()) {
matched.push(<li onClick={this.handleClick}
value={exercise.exercise_name}
className="workout-auto-li"
key={exercise.id}>{exercise.exercise_name}</li>);
}
}
} else {
matched.push(<li onClick={this.handleClick}
value={exercise.exercise_name}
className="workout-auto-li"
key={exercise.id}>{exercise.exercise_name}</li>)
}
return matched;
});
return (
<div>
{this.props.allExercises ? (
<div>
{this.state.active === 'FIRST' ? (
<div className="exercise-main-div">
<div className="exercise-second-div">
<label className="exercise-label">
<h3>Add an Exercise for {this.props.liftname}</h3>
<input type="text" value={this.state.inputVal}
onChange={this.handleChange}
className="exercise-input"
/>
</label>
<ul className="exercise-ul">
{match}
</ul>
<button className="new-exercise-button"
onClick={this.handleSubmit}>Add Exercise</button>
</div>
</div>
) : this.state.active === 'SECOND' ? (
<SetResultContainer user={this.props.user}
exercises={this.props.exercises}
exercise={this.state.name}
liftname={this.props.liftname}/>
) : null }
</div>
) : null }
</div>
);
}
}
export default withRouter(ExerciseIndex);
component 2
import React from 'react';
import { withRouter } from 'react-router';
import values from 'lodash/values'
import { Pie } from 'react-chartjs-2';
class Leaderboard extends React.Component {
constructor(props) {
super(props)
this.state = { exercise: null }
this.handleUpdate = this.handleUpdate.bind(this)
}
componentDidMount() {
this.props.requestAllUsers();
this.props.requestAllExercises();
}
handleUpdate(property) {
return e => this.setState({ [property]: e.target.value });
}
render() {
const members = this.props.members.map(member => {
return <li className="members-list" key={member.id + 1}>{member.username}</li>
})
const memberId = {}
this.props.members.map(member => {
memberId[member.username] = member
})
const membersSetResults = {}
const membersLiftMaxes = {}
const completedMemberExercises = []
const completedExercises = {}
this.props.members.map(member => {
if (member.workouts) {
let workouts = values(member.workouts)
for (var i = 0; i < workouts.length; i++) {
let workoutResult = workouts[i].setresults
let results = values(workoutResult)
if (membersSetResults[member.username]) {
membersSetResults[member.username].unshift(results)
} else {
membersSetResults[member.username] = [results];
}
}
}
})
Object.keys(membersSetResults).map(member => {
let setResults = membersSetResults[member]
membersLiftMaxes[member] = {}
for (var i = 0; i < setResults.length; i++) {
let sets = setResults[i]
for (var j = 0; j < sets.length; j++) {
let currentExercise = this.props.allExercises[sets[j].exercise_id]
let exercise = currentExercise.exercise_name
if (completedMemberExercises.indexOf(exercise) < 0 && currentExercise.ex_type === 'lift') {
completedMemberExercises.push(exercise)
}
if (completedExercises[exercise]) {
completedExercises[exercise] += 1
} else if (!completedExercises[exercise]) {
completedExercises[exercise] = 1
}
if (currentExercise.ex_type === 'lift') {
if (membersLiftMaxes[member][exercise]) {
if(membersLiftMaxes[member][exercise] < sets[j].weight_lifted) {
membersLiftMaxes[member][exercise] = sets[j].weight_lifted
}
} else if (!membersLiftMaxes[member][exercise]) {
membersLiftMaxes[member][exercise] = sets[j].weight_lifted
}
}
}
}
})
const PieChart = {
datasets: [{
data: Object.values(completedExercises),
backgroundColor: [
'#2D4262',
'#363237',
'#73605B',
'#D09683',
'#F1F3CE',
'#1E656D',
'#00293C',
'#F0810F',
'#75B1A9',
],
}],
labels: Object.keys(completedExercises)
};
let exerciseDropdown = completedMemberExercises.map((exercise, idx) => {
return <option key={idx} value={exercise}>{exercise}</option>
})
let sorted = [];
const memberAndMax = {}
Object.keys(membersLiftMaxes).map(member => {
if (this.state.exercise) {
let exerciseMax = membersLiftMaxes[member][this.state.exercise]
if(!memberAndMax[this.state.exercise]){
memberAndMax[this.state.exercise] = []
memberAndMax[this.state.exercise].push([member, exerciseMax])
} else if (memberAndMax[this.state.exercise]) {
memberAndMax[this.state.exercise].push([member, exerciseMax])
}
memberAndMax[this.state.exercise].map(max => {
if (sorted.indexOf(max) < 0) {
if (max[1] > 0) {
sorted.push(max)
}
}
})
sorted.sort((a, b) => {
return a[1] - b[1]
})
}
})
let maxLis = sorted.reverse().map((user) => {
if (memberId[user[0]].id === this.props.cu.id) {
return <li className='userPresent' key={memberId[user[0]].id}>
<p className="members-list-p">{user[0]}</p>
<p className="members-list-p-two">{user[1]}</p></li>
} else {
return <li className='members-list' key={memberId[user[0]].id}>
<p className="members-list-p">{user[0]}</p>
<p className="members-list-p-two">{user[1]}</p></li>
}
})
return (
<div className='main-leaderboard'>
<div className='lb-reset-div'>
<button className='lb-reset-button' onClick={() => this.setState({exercise: null})}>Reset</button>
<select className='leaderboard-dropdown' onChange={this.handleUpdate('exercise')}>
<option>Please Select</option>
{exerciseDropdown}
</select>
</div>
{(this.state.exercise) ? (
<div className='lb-ul-div'>
<h3 className='selected-ex-title'>{this.state.exercise}</h3>
<ul className='leaderboard-ul'>
<li className="members-list"><p className="members-list-p">Name</p>
<p className="members-list-p-two">Max (lbs)</p></li>
{maxLis}
</ul>
</div>
): (!this.state.exercise) ? (
<div className='lb-ul-div'>
<h3 className='selected-ex-title'>Leaderboard</h3>
<ul className='leaderboard-ul'>
{members}
</ul>
</div>
): null}
<div className='pie-chart-div-lb'>
<h3 className='pie-chart-header'>What the World's Doing</h3>
<Pie circumfrence={300} data={PieChart}/>
</div>
</div>
)
}
}
export default withRouter(Leaderboard);
component 3
import React from 'react';
import { withRouter } from 'react-router';
import values from 'lodash/values';
import { Line, Pie } from 'react-chartjs-2';
class SearchBestWorkouts extends React.Component {
constructor(props) {
super(props);
this.state = {
inputVal: '',
name: '',
active: '',
result: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.props.requestAllExercises();
this.setState({active: 'FIRST'})
}
handleChange(e) {
e.preventDefault(e)
this.setState({ inputVal: e.target.value })
}
handleClick(e) {
this.setState({ inputVal: e.currentTarget.attributes.value.value})
}
handleSubmit(e) {
let newActive = this.state.active === 'FIRST' ? 'SECOND' : 'FIRST'
let allExercises = values(this.props.exercises);
let selected;
let name;
if (newActive === 'SECOND') {
allExercises.forEach(exercise => {
if (exercise.exercise_name === this.state.inputVal) {
selected = exercise,
name = exercise.exercise_name
}
})
e.preventDefault();
if (!name) {
this.setState({inputVal: 'Invalid Input, Please try Again'})
return 'Invalid Input'
}
this.setState({inputVal: '', active: newActive, name: name})
this.props.requestAllExercises();
} else if (newActive === 'FIRST') {
this.setState({inputVal: '', active: newActive, name: '' })
}
}
render () {
let allWorkouts = this.props.allWorkouts;
let exercises = this.props.exercises;
let setResults = allWorkouts.map(workout => {
return values(workout.setresults)
})
let mergedSets = [].concat.apply([], setResults)
const allResults = {}
const exerciseTypes = {}
const completedExercises = {};
for (var i = 0; i < mergedSets.length; i++) {
let set = mergedSets[i];
let exercise = exercises[set.exercise_id]
let name = exercise.exercise_name
let bodypart = exercise.bodypart
if (exerciseTypes[bodypart]) {
exerciseTypes[bodypart] += 1
} else if (!exerciseTypes[bodypart]) {
exerciseTypes[bodypart] = 1
}
if (exercise.ex_type === 'lift') {
if (!allResults[name]) {
allResults[name] = { labels: [],
datasets: [{
label: 'Weight over Time',
backgroundColor: '#2988BC',
borderColor: '#2F496E',
data: [],
}],
};
}
if (completedExercises[name] < (set.weight_lifted)) {
completedExercises[name] = set.weight_lifted
} else if (!completedExercises[name]) {
completedExercises[name] = set.weight_lifted
}
allResults[name].labels.push(allResults[name].labels.length + 1)
allResults[name].datasets[0].data.unshift(set.weight_lifted)
}
}
const PieChart = {
datasets: [{
data: Object.values(exerciseTypes),
backgroundColor: [
'#2D4262', '#363237', '#73605B', '#D09683'
],
}],
labels: Object.keys(exerciseTypes)
};
const best = Object.keys(completedExercises).map((exercise) => {
if (this.state.inputVal === '') return [];
let bests = [];
if (this.state.inputVal.length > 0) {
for (var j = 0; j < this.state.inputVal.length; j++) {
bests = [];
if (exercise.slice(0, j + 1).toUpperCase() === this.state.inputVal.slice(0, j + 1).toUpperCase()) {
bests.push(<li onClick={this.handleClick}
value={exercise}
className="best-lift-li"
key={exercise.id}>{exercise}</li>);
}
}
} else {
bests.push(<li onClick={this.handleClick}
value={exercise}
className="best-lift-li"
key={exercise.id}>{exercise}</li>)
}
return bests;
});
return (
<div>
{this.state.active === 'FIRST' ? (
<div className="best-lift-div">
<div className='best-lift-div-two'>
<h3 className="best-lift-title">Personal Records</h3>
<div className='best-lift-input-div'>
<input type="text" value={this.state.inputVal}
onChange={this.handleChange}
className="best-lift"
placeholder="Enter an Exercise"
/>
</div>
<ul className='best-lift-ul'>
{best}
</ul>
<button className='best-lift-button' onClick={this.handleSubmit}>Best Lift</button>
</div>
</div>
) : this.state.active === 'SECOND' ? (
<div className="best-lift-div">
<div className='best-lift-div-two'>
<h3 className="best-lift-title">
{this.state.name}: {completedExercises[this.state.name]}</h3>
<div className='chart-background'>
<Line width={250} height={200} data={allResults[this.state.name]}/>
</div>
<button className='best-lift-button' onClick={this.handleSubmit}>Back</button>
</div>
<div className='best-lift-div-three'>
<h3 className="best-lift-title">Workout Analysis</h3>
<div className='pie-chart-background'>
<Pie circumfrence={100} data={PieChart} />
</div>
</div>
</div>
) : null}
</div>
)
}
}
export default withRouter(SearchBestWorkouts)
component 4
import React from 'react';
import values from 'lodash/values'
import InfiniteScroll from 'react-infinite-scroll-component';
import { withRouter } from 'react-router'
class WorkoutShow extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick (e) {
e.preventDefault();
this.props.deleteWorkout(this.props.selectedWorkout).then(
() => {
this.props.requestUser(this.props.match.params.userId)
}
)
this.props.toggleParent();
}
render () {
const setArray = values(this.props.selectedWorkout.setresults)
const exercises = this.props.exercises
const results = setArray.map((result, idx) => {
if (result.workout_id === this.props.selectedWorkout.id) {
return <li key={result.id} className='workout-show-li'>
<p className='workout-title'><p>Set {idx + 1}: </p><p>{exercises[result.exercise_id].exercise_name}</p></p>
<ul>
{result.weight_lifted ? (
<li className='workout-result-li'><p className='workout-result-li'>Weight:</p>{result.weight_lifted}{result.weight_unit}</li>
) : null}
{result.reps ? (
<li className='workout-result-li'><p className='workout-result-li'>Reps:</p>{result.reps}</li>
) : null}
{result.distance ? (
<li className='workout-result-li'><p className='workout-result-li'>Distance:</p>{result.distance}{result.distance_unit}</li>
) : null}
{result.hour || result.min || result.sec ? (
<li className='workout-result-li'><p className='workout-result-li'>Duration:</p>
<div className='dur-format'>
{result.hour ? (
<p className='dur-result-hour'>{result.hour}:</p>
) : null}
{result.min ? (
<p className='dur-result'>{result.min}:</p>
) : null}
{result.sec ? (
<p className='dur-result'>{result.sec}</p>
) : null}
</div>
</li>
) : null }
</ul>
</li>
}
})
return (
<div className="workout-show-main">
<h3 className="workout-show-title">{this.props.selectedWorkout.name}
<button className='remove-workout-button' onClick={this.handleClick}>DELETE</button></h3>
<InfiniteScroll>
<ul className="workout-show-ul">
{results}
</ul>
</InfiniteScroll>
</div>
);
}
}
export default withRouter(WorkoutShow);
Being on a server introduces delays in fetching of data, and things that
work perfectly locally don't work as well on the server.
The major culprit is your code, which is making assumptions about return values. For example
const setArray = values(this.props.selectedWorkout.setresults)
const exercises = this.props.exercises
const results = setArray.map((result, idx) => {
Line 3 of this block blindly assumes that setArray is defined and an array. You need to add checks in at every step, or provide default values (see the end of the first line below)
const setArray = values(this.props.selectedWorkout.setresults) || []
const exercises = this.props.exercises
const results = setArray.map((result, idx) => {
I'll leave it as an exercise for you to add what I call 'defensive code' to check return values and handle missing data without barfing.
You can also add try..catch blocks to trao any errors that your defensive code doesn't handle.
It will take some time, but it's worth upgrading your methodology to include this as standard practice if you want to write good code

Resources