React - state property is undefined - Why? - javascript

I want to load all companies via AJAX request into a state property when the user clicks on the select box.
This is the code:
import React, { Component } from 'react';
import SelectOption from './SelectOption';
class CreateFreightEntryModal extends Component {
constructor(props) {
super(props);
this.state = {
freights: props.freights,
onClose: props.onClose,
onClick: props.onClick,
companies: [],
};
}
loadCompanies(event) {
$.ajax({
type: "POST",
context:this,
dataType: "json",
async: true,
url: "../data/get/json/companies",
data: ({
_token : window.Laravel.csrfToken,
}),
success: function (data) {
var arr = $.map(data, function(el) { return el; });
this.setState({
companies: arr
})
}
});
}
render() {
return (
<div className="modal fade" id="createFreightEntryModal" tabIndex="-1" role="dialog">
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 className="modal-title">New freight entry</h4>
</div>
<div className="modal-body">
<div>
<div>
<form onSubmit={this.add.bind(this)}>
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<strong>Create a new freight entry:</strong>
</div>
</div>
<div className="row">
<div className="col-xs-4 col-sm-4 col-md-4 col-lg-4">
Company
</div>
<div className="col-xs-8 col-sm-8 col-md-8 col-lg-8">
<div className="form-group" onClick={this.loadCompanies.bind(this)}>
<select className="selectpicker show-tick form-control" data-live-search="true" data-title="Please select" ref="Firma" required>
{
this.state.companies.map((company)=> {
return (
<SelectOption value={company.Nummer} displayValue={company.Bezeichnung} key={company.id} />
);
})
}
</select>
</div>
</div>
</div>
<div className="row">
<div className="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<div className="form-group">
<button type="submit" className="btn btn-success"><span className="glyphicon glyphicon-floppy-disk"></span> Save </button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
);
}
}
export default CreateFreightEntryModal
When I add the componentWillReceiveProps(nextProps) method, I get this error.
This error occurs when the page is loaded and not when I click on the select box!
componentWillReceiveProps(nextProps) {
this.setState({
companies: nextProps.companies,
});
}
TypeError: this.state.companies is undefined
This is the part where the error occurs:
...
this.state.companies.map((company)=> {
...
How can I solve this issue? Thanks for your help in advance.

Using this construct:
componentWillReceiveProps(nextProps) {
this.setState({
companies: nextProps.companies,
});
}
you update state.companies every time the component receives ANY props, even when there are no companies in the props. And when the nextProps don't have companies it is set to undefined.
Let's illustrate it this way:
{
let props = { freights : [ 'a', 'b', 'c' ] }
this.setState({ companies : props.companies })
/* state.companies are now undefined, because props.companies are undefined */
}
Fix:
componentWillReceiveProps(nextProps) {
if( nextProps.companies ){
this.setState({
companies: nextProps.companies,
});
}
}
BTW the problem with success callback scope I have mentioned in a comment may still apply.

Related

Vue Laravel: Reactive Nested Array value is Empty on Backend

I'm relatively new to Vue JS. So I have this case on Form Submit.
This is what I've submitted in Console Log:
{
amount: [
monday: "123",
tuesday: "97438"
],
day_of_week: "perday",
started_at: "2022-09-14"
}
And what it received in Laravel Backend when I dump the data is this:
^ array:3 [
"started_at" => "2022-09-14"
"day_of_week" => "perday"
"amount" => []
]
So My Question Is, what did I do wrong? How is the "amount" part not included on the backend? It already looks right on the console.log. I do appreciate help here. I reckon I did a mistake on how to write the model name in the array part inside HTML, but still, I do not know how to do it right.
THANK YOU IN ADVANCE 🙏🏼🙏🏼🙏🏼
This is how I submit my form:
HTML Vue Page:
<div class="card-body p-9">
<div class="row mb-8">
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Type of Rate</div>
</div
<div class="col-xl-9 fv-row">
<VueMultiselect v-model="day_of_week" label="name" track-by="name" placeholder="Select Type" :options="type_of_day" :close-on-select="true">
<template slot="singleLabel" slot-scope="{ type_of_day }"><strong>{{ type_of_day.name }}</strong></template>
</VueMultiselect>
</div>
</div>
<div class="row mb-8">
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Started Date</div>
</div
<div class="col-xl-9 fv-row">
<div class="position-relative d-flex align-items-center">
<input v-model="ticketing.started_at" class="form-control form-control-solid ps-12" name="date" placeholder="Pick Start date" id="kt_datepicker_1" />
</div>
</div>
</div>
<div v-if="day_of_week">
<div v-if="day_of_week.value == 'allday'" class="row mb-8">
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Entrance Rate</div>
</div>
<div class="col-xl-9 fv-row">
<div class="input-group mb-5">
<span class="input-group-text">Rp.</span>
<input v-model="amount.allday" type="text" class="form-control" aria-label="Amount"/>
</div>
</div>
</div>
<div v-if="day_of_week.value == 'perday'" class="row mb-8">
<div class="col-12 mb-8">
<div class="fs-6 fw-bold mt-2 mb-3">Entrance Rate Per Day of Week</div>
</div>
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Monday</div>
</div>
<div class="col-xl-9 fv-row">
<div class="input-group mb-5">
<span class="input-group-text">Rp.</span>
<input v-model="amount.day.monday" type="text" class="form-control" aria-label="Amount"/>
</div>
</div>
<div class="col-xl-3">
<div class="fs-6 fw-semibold mt-2 mb-3">Tuesday</div>
</div>
<div class="col-xl-9 fv-row">
<div class="input-group mb-5">
<span class="input-group-text">Rp.</span>
<input v-model="amount.day.tuesday" type="text" class="form-control" aria-label="Amount"/>
</div>
</div>
</div>
</div>
</div>
<div class="card-footer d-flex justify-content-end pb-6 px-9">
<span #click="storeNewRate" class="btn btn-primary">Create New Rate</span>
</div>
</template>
This Is the script Part
<script>
import { onMounted, toRaw } from "vue";
import useTicketing from "../../../composable/ticketing";
export default {
data() {
const { storeTicketing, ticketing } = useTicketing()
const storeNewRate = async () => {
let type = this.day_of_week.value
let submittedAmount = []
type == 'allday' ? submittedAmount = this.amount.allday : submittedAmount = this.amount.day
this.ticketing.day_of_week = this.day_of_week.value
this.ticketing.amount = submittedAmount
console.log('submit: ', toRaw(ticketing))
await storeTicketing({...ticketing})
}
return {
type_of_day: [
{
"name": "All Days of Week",
"value": "allday"
},
{
"name": "Rate per Day",
"value": "perday"
},
],
started_at: '',
day_of_week: '',
amount: {
allday: '',
day: []
},
storeNewRate,
ticketing
}
}
}
</script>
And I have Separate file for handling the API:
export default function usePlan() {
const ticketing = reactive({});
const router = useRouter();
let toastr = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 5000
})
const storeTicketing = async (data) => {
let errors = ''
try {
let response = await axios.post('/api/ticketing/create', data);
ticketing.value = response.data;
await toastr.fire("Success", "New Rate Has Been Created!", "success");
} catch (e) {
if(e.response.status === 422) {
for(const key in e.response.data.message) {
errors = e.response.data.message[key][0];
toastr.fire("Failed", errors, "error");
}
errors = ''
}
}
}
return {
storeTicketing,
ticketing
}
}
🙏🏼

Vue: Can't set the computed property

I need some help with computed properties and ajax in vue.
"filterFactories" is a list of factories.
The computed property "filterFactories" creates this list of factories.
Now, I want a new feature:
I have a button for an ajax request which get some new factories.
I want set the computed property "filterFactories" after an ajax request.
Unfortunately nothing happens.
it makes no difference:
1. this.filterFactories = response;
or
2. window.filterFactories = response;
In both cases - nothing happened
Is it possible to update the "filterFactories" after the successfull ajax request?
I have added a larger code snipped
<div id="app">
<div id="filter">
<div class="row">
<div class="col-md-12">
<div class="input-group">
<span class="input-group-addon">Factory</span>
<input type="text" class="form-control" placeholder="Name" aria-describedby="basic-addon1" v-model="searchFactory">
<div class="input-group-btn">
<button class="btn btn-default" type="submit" v-on:click="clearSearchFactory">
<i class="fa fa-times"></i>
</button>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<Multiselect
v-model="selectedCapabilities"
:options="allCapabilities"
label="name"
placeholder= "Select capabilities"
track-by="id"
:multiple="true"
></Multiselect>
</div>
<div class="col-xs-12 col-md-6">
<Multiselect
v-model="selectedCountries"
:options="allCountries"
label="name"
placeholder= "Select countries"
track-by="code"
:multiple="true"
></Multiselect>
</div>
</div>
</div>
<!--Modal-->
<div id="myModal" class="modal fade" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">{{this.clickedCapability.name}}</h4>
</div>
<div class="modal-body">
<!--......-->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" data-dismiss="modal" #click="filterProperties">OK</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="row" style="height: 35px; margin: 10px">
<button type="button" class="btn btn-outline-dark" v-for="cap in selectedCapabilities" #click="modalCapClicked(cap)" data-toggle="modal" data-target="#myModal"> {{ cap.name }} <i class="fa fa-cogs"></i></button>
</div>
<!--Factories-->
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-3 col-lg-2" id="myCard-wrapper" v-for="factory in this.filterFactories">
<!--a list of factories-->
</div>
</div>
</div>
<script>
window.app = new Vue({
el: '#app',
components: {
Multiselect: window.VueMultiselect.default
},
data() {
return {
//Capabilities
allCapabilities: [], // alle Capabilities aus der Konfiguration die über das Json übermittelt wurden
selectedCapabilities: [], // selektierte Capabilities
clickedCapability: '', // im Modalfenster geöffnete Capability
//Countries
selectedCountries: [], // selektierte Countries
allCountries: [], // alle Countries aus der Json
//Factories
searchFactory: '', // Freitext Suchfeld für Fabriken
factories: [] // angezeigte Fabriken
}
},
computed:{
/* Filtert die Fabriken anhand der Kriterien: Suche-Input, Capabilities, Countries */
filterFactories: function(){
var filteredFactories = [];
var allFilter = [];
allFilter.push(this.filterFactoriesBySearchInput());
allFilter.push(this.filterFactoriesByCaps());
allFilter.push(this.filterFactoriesByCountries());
filteredFactories = allFilter.shift().filter(function(v) {
return allFilter.every(function(a) {
return a.indexOf(v) !== -1;
});
});
return filteredFactories;
}
},
methods: {
/* Filtert anhand der Suchfeld-Eingabe */
filterFactoriesBySearchInput(){
/*filter an return a new list of factories*/
},
/* Filtert anhand der Capabilities */
filterFactoriesByCaps(){
/*filter an return a new list of factories*/
},
/* Filtert anhand der Countries */
filterFactoriesByCountries(){
/*filter an return a new list of factories*/
},
/* Setzt die aktuell im Modal-Fenster geöffnete Capability */
modalCapClicked(cap){
this.clickedCapability = cap;
}
filterProperties(){
axios.post('.....................................')
.then(function (response) {
this.factories = response.data.factoriesJson;
})
.catch(function (error) {
console.log(error);
});
},
clearSearchFactory(){
this.searchFactory = [];
}
},
beforeMount(){
axios.get('.........').then(response => {
this.factories = response.data.elementsJson.factories;
this.allCapabilities = response.data.elementsJson.config.capabilities;
});
axios.get('.......').then(response => {
this.allCountries = response.data;
});
}
})
</script>
Vue uses some magic for computed properties: it scans the function code and will automatically create watchers for the properties it finds within the code. This works well for simple situations but has failed many times for me with loops, map, reduce, etc.
I use multiple workarounds as needed, what is simplest to understand is this: create an artificial property which you reference in the computed prop and then update as needed:
new Vue({
data: {
updated: 0,
},
computed: {
myComputed(): {
// ...
// just a reference to the prop
this.updated;
},
},
methods: {
myAjax(): {
// ...
// modifying the prop will trigger update of myComputed
this.updated++;
},
},
});
Of course you should use names more appropriate to the use cases you have, however i have instances where i just called this property "updateDummy" ;)

Modal event before hide

I have a problem. I need to show toastr informing that changes are not saved when someone wants to hide modal. I need to trigger toastr before modal hide, and when the user tries again to dismiss modal allow this. I tried something like this:
declare let jQuery: any;
declare let $: any;
declare let toastr: any;
#Component({
selector: 'app-trigger',
templateUrl: './trigger.component.html',
styleUrls: ['./trigger.component.scss']
})
export class TriggerComponent implements OnInit {
name: string
private canHideModal = true;
constructor() {
}
ngOnInit(){
const self = this;
$('#triggerModal').on('hide.bs.modal', () => {
if (self.canHideModal) {
//hide modal here <---------
} else {
toastr['warning']('You have unsaved changes');
self.canHideModal = true;
return false
}
});
}
fireModal(changes : {action:string, name:string}){
changes.action = 'show';
changes.name = 'test';
this.name = changes.name
$('#triggerModal').modal(changes.action);
}
}
and it works fine for first time, after this hide event seems to be overwriten and function $('#triggerModal').on('hide.bs.modal', () => { doesn't trigger anymore.
HTML:
<div class="modal fade" id="triggerModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" style="display: none;" aria-hidden="true">
<div class="modal-dialog modal-lg px-4" role="document">
<!--Content-->
<div class="modal-content">
<!--Header-->
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<!--Body-->
<div class="modal-body mb-0">
<!--Grid row-->
<div class="row d-flex justify-content-center mb-4">
<!--Grid column-->
<div class="col-md-6">
<!--Name-->
<div class="md-form">
<input type="text" id="triggerStartName" (input)="canHideModal = false" class="form-control" #triggerName [value]="name">
<label for="triggerStartName" [ngClass]="{ 'active': name }">Trigger name</label>
</div>
</div>
<!--Grid column-->
</div>
<!--Grid row-->
</div>
<!--Footer-->
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary waves-effect waves-light" data-dismiss="modal">Close</button>
</div>
</div>
<!--/.Content-->
</div>
</div>
You can do it with the ng-bootstrap Modal component, by assigning a method to its beforeDismiss option, as illustrated in this plunker:
import {Component} from '#angular/core';
import {NgbModal, ModalDismissReasons} from '#ng-bootstrap/ng-bootstrap';
#Component({
selector: 'ngbd-modal-basic',
templateUrl: 'src/modal-basic.html'
})
export class NgbdModalBasic {
closeResult: string;
private canHideModal = false;
constructor(private modalService: NgbModal) {}
open(content) {
this.canHideModal = false;
const options : NgbModalOptions = {
beforeDismiss: () => {
if (this.canHideModal) {
return true;
} else {
alert('You have unsaved changes');
this.canHideModal = true;
return false;
}
}
};
this.modalService.open(content, options).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
...
}

Vue.js Display call one component from another component

I have 2 components:
Vue.component('repo-button', {
props:["check_in_id", "repo_id"],
template: '#repo-button',
methods: {
fetchRepo: function() {
url = window.location.href.split("#")[0] + "/check_ins/" + this.check_in_id + "/repositionings/" + this.repo_id + ".json"
cl(url)
cl(this)
var that;
that = this;
$.ajax({
url: url,
success: function(data) {
cl(data)
that.showRepo();
}
})
},
showRepo: function() {
// what do I put here to display the modal
}
},
data: function() {
var that = this;
return {
}
}
});
Vue.component('repo-modal', {
template: "#repo-modal",
data: function() {
return {
status: 'none'
}
}
});
var repositionings = new Vue({
el: "#repo-vue"
});
...and my view consists of a button and a modal. I'd like the button to call fetchRepo on the repo-button component and display the modal (change its status property from none to block.
<script type="text/x-template" id="repo-button">
<div class='socialCircle-item success'>
<i class='fa fa-comment'
#click="fetchRepo"
:data-check_in='check_in_id'
:data-repo='repo_id'>
</i>
</div>
</script>
<script type="text/x-template" id="repo-modal">
<div v-bind:style="{ display: status }" class="modal" id="vue-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-client_id="<%= #client.id %>">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-danger btn-simple" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</script>
<div id="repo-vue">
<div is="repo-modal"></div>
<div is="repo-button" repo_id="<%= ci.repositioning.id %>" check_in_id="<%= ci.id %>"></div>
</div>
Props down, events up
In Vue.js, the parent-child component relationship can be summarized
as props down, events up. The parent passes data down to the child via
props, and the child sends messages to the parent via events.
In particular, if the state of a component needs to be controlled externally (by a parent or sibling), that state should be passed in as a prop from the parent. Events indicate to the parent that the state should be changed.
Your modal's state is controlled by events in itself and in a sibling component. So the state lives in the parent, and is passed to the modal as a prop. Clicking the modal Close button emits a (custom) hidemodal event; clicking the sibling component's comment icon emits a showmodal event. The parent handles those events by setting its showRepoModal data item accordingly.
Vue.component('repo-button', {
template: '#repo-button',
methods: {
showRepo: function() {
this.$emit('showmodal');
}
}
});
Vue.component('repo-modal', {
template: "#repo-modal",
props: ["show"],
computed: {
status() {
return this.show ? 'block' : 'none'
}
},
methods: {
hideRepo() {
this.$emit('hidemodal');
}
}
});
var repositionings = new Vue({
el: "#repo-vue",
data: {
showRepoModal: false
}
});
.socialCircle-item i {
cursor: pointer;
}
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="repo-button">
<div class='socialCircle-item success'>
<i class='fa fa-comment'
#click="showRepo"
>
</i>
</div>
</template>
<template id="repo-modal">
<div v-bind:style="{ display: status }" class="modal" id="vue-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" >
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title"></h4>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" #click="hideRepo" class="btn btn-danger btn-simple" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</template>
<div id="repo-vue">
<div is="repo-modal" :show="showRepoModal" #hidemodal="showRepoModal = false"></div>
<div is="repo-button" #showmodal="showRepoModal = true"></div>
</div>

Meteor-React delete post with submit modal (post not defined)

I have created additional submit modal in order to receive user confirmation after he decides to delete the post from collection and I cant figure out how to target the post.
Another thing that I would like to ask you is a productivity question, is it wise to insert DeletePost component into each post component or there is a way to have it inserted inside currentPage component and somehow bind the modal call to to the post.
Here is the code for DeletePost component:
class DeletePost extends Component {
handleDelete(event) {
event.preventDefault();
Meteor.call('posts.remove', post);
$('#modalDelete').modal('hide');
}
render() {
return (
<div className="modal fade form-delete" id="modalDelete" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div className="form-outer">
<form id='delete_post' onSubmit={this.handleDelete.bind(this)}>
<div className='form-text form-header'>
<p>My dear, <strong>master</strong></p>
<p>Are you really sure about that?</p>
</div>
<button type="button" className="form-button button-delete" data-dismiss="modal">No</button>
<button type="sumbit" className="form-button button-delete">Yes</button>
</form>
</div>
</div>
);
}
}
And here is code for the Post component which imports from DeletePost:
class PostsList extends Component {
renderData(){
return this.props.posts.map(post => {
const {title, social, link, link_image, time=moment(post.createdAt).fromNow()} = post;
return (
<div key={post._id} className='social-post'>
<img src={link_image}></img>
<p>{social}, {time}</p>
<a className='social-link' target="_blank" href={link}>{title}</a>
<div className='list-buttons'>
<button className='form-button button-gradient'>Edit</button>
<button type="button" className='form-button button-gradient' data-toggle="modal" data-target="#modalDelete">Delete</button>
</div>
<DeletePost />
</div>
);
})
}
render() {
return (
<div className='flex-timeline'>
{this.renderData()}
</div>
);
}
}
You have to pass the post value from its parents to child:
Your PostsList Class need to pass post value to child.
class PostsList extends Component {
renderData(){
return this.props.posts.map(post => {
const {title, social, link, link_image, time=moment(post.createdAt).fromNow()} = post;
return (
<div key={post._id} className='social-post'>
<img src={link_image}></img>
<p>{social}, {time}</p>
<a className='social-link' target="_blank" href={link}>{title}</a>
<div className='list-buttons'>
<button className='form-button button-gradient'>Edit</button>
<button type="button" className='form-button button-gradient' data-toggle="modal" data-target="#modalDelete">Delete</button>
</div>
<DeletePost post={post}/>
</div>
);
})
}
render() {
return (
<div className='flex-timeline'>
{this.renderData()}
</div>
);
}
}
Your DeletePost use this.props.post to access data from parent.
class DeletePost extends Component {
handleDelete(event) {
event.preventDefault();
Meteor.call('posts.remove', this.props.post);
$('#modalDelete').modal('hide');
}
render() {
return (
<div className="modal fade form-delete" id="modalDelete" tabIndex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div className="form-outer">
<form id='delete_post' onSubmit={this.handleDelete.bind(this)}>
<div className='form-text form-header'>
<p>My dear, <strong>master</strong></p>
<p>Are you really sure about that?</p>
</div>
<button type="button" className="form-button button-delete" data-dismiss="modal">No</button>
<button type="sumbit" className="form-button button-delete">Yes</button>
</form>
</div>
</div>
);
}
}

Categories

Resources