I'm working with an MVC5 project and running into an issue with React not binding an array. I had this working in an MVC Core project, but had to "regress" back to the old structure. Biggest change seemed to be in the controller, changing from JsonResult (Core MVC) to Json (MVC5) for the return type on the ajax call.
Here's the output from Chrome Developer Tools:
(removed due to lack of reputation points)
And, my code for my .jsx file:
var LineItem = React.createClass({
render: function () {
return (
<div className="gridItem">
<div className="lessLineHeight smallFont">
<div className='section group'>
<div className="col span_1_of_2" id={this.props.ordHeaderId}>
<text>{this.props.code}</text>
</div>
<div className='col span_1_of_2 text-right'>
<i className={this.props.apptIconString} aria-hidden='true'></i>
<i className={this.props.highValueIconString}></i>
<i className={this.props.hazmatIconString}></i>
</div>
</div>
<div className='section group'>
<div className='col span_6_of_10'>
<text title='Trading Partner - Client'>{this.props.tradingPartnerName}</text>
</div>
<div className='col span_4_of_10 text-right'>
<text className='overflowElip' title='Account Manager'>{this.props.accountManager}</text>
</div>
</div>
<div className='section group'>
<div className='col span_1_of_2'>
<text title={"Origin: " + this.props.originAddress + "; " + this.props.origContact}>{this.props.originAddress}</text>
</div>
<div className='col span_1_of_2 text-right'>
<text title={"Destination:" + this.props.destinationAddress + "; " + this.props.destContact}>{this.props.destinationCity}</text>
</div>
</div>
<div className='section group'>
<div className='col span_1_of_3'>${this.props.freightValue}</div>
<div className='col span_1_of_3 text-center'>
<a title='Promote Order to Load'>To Load</a>
</div>
<div className='col span_1_of_3 text-right' id={'datePlanned' + this.props.ordHeaderId}>
<text title='Pickup Date'>{this.props.dateCreated}</text>
</div>
</div>
</div>
</div>
);
}
});
var ItemList = React.createClass({
getInitialState: function () {
return { items: [] };
},
loadData: function () {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function (data) {
console.log(data);
this.setState({ items: data });
console.log(this.state.items);
$("#column1").find(".gridItem:odd").css({ "background-color": "#ddd" }).end().find(".gridItem:even").css({ "background-color": "#fff" });
}.bind(this),
error: function (xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function () {
this.loadData();
/*window.setInterval(this.loadData, this.props.pollInterval);*/
},
render: function () {
if (this.state.items) {
console.log("State has items.");
var itemNodes = this.state.items.map(function (foo) {
return (
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
commodityDescription={foo.commodityDescription}
commodityId={foo.commodityId}
dateCreated={foo.dateCreated}
deliveryAppt={foo.deliveryAppt}
destContact={foo.destContact}
destinationAddress={foo.destinationAddress}
destinationAddressName={foo.destinationAddressName}
destinationCity={foo.destinationCity}
earlyDeliveryTime={foo.earlyDeliveryTime}
earlyPickupTime={foo.earlyPickupTime}
equipmentName={foo.equipmentName}
freightValue={foo.freightValue}
handlingUnits={foo.handlingUnits}
hazmatIconString={foo.hazmatIconString}
highValueIconString={foo.highValueIconString}
isHazmat={foo.isHazmat}
isHighValue={foo.isHighValue}
lateDeliveryTime={foo.lateDeliveryTime}
latePickupTime={foo.latePickupTime}
loadId={foo.loadId}
loadNum={foo.loadNum}
loadTmsStatus={foo.loadTmsStatus}
ordHeaderId={foo.ordHeaderId}
ordNum={foo.ordNum}
orderType={foo.orderType}
origContact={foo.originContact}
originAddress={foo.originAddress}
originAddressName={foo.originAddressName}
originationCity={foo.originationCity}
pickupAppt={foo.pickupAppt}
pieces={foo.pieces}
plannedEnd={foo.plannedEnd}
plannedStart={foo.plannedStart}
requiredTemp={foo.requiredTemp}
specialInstructions={foo.specialInstructions}
targetCost={foo.targetCost}
teamId={foo.teamId}
tempControlled={foo.tempControlled}
tradingPartnerNameCNum={foo.tradingPartnerNameCNum}
tradingPartnerName={foo.tradingPartnerNameClient}
transportMode={foo.transportMode}
user3gIdBookedBy={foo.user3gIdBookedBy}
user3gIdCreatedBy={foo.user3gIdCreatedBy}
weight={foo.weight} />
);
});
return (
<div className="itemList">
{itemNodes}
</div>
);
} else {
return null;
}
}
});
ReactDOM.render(
<ItemList url="/DispatchBoard/getColumn1Data" pollInterval={2000} />,
document.getElementById('column1')
);
As you can see from the image, the render: in the loadData function sees the items coming back from the ajax call, and then sets them to state, but when it comes time to map them, it does nothing.
Any ideas on what I'm not seeing?
EDIT
Here's a screen show showing the 'undefined' value(s) in one of the LineItems after failing to map properly. undefined values
EDIT #2
Here's a screenshot showing that the objects are hydrated and not being parsed. object present, not parsed
After seeing the screenshot you posted in EDIT #2
The issue is you're using different property name when accessing the data from foo while setting the properties on your component
So changing it from
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
to
<LineItem key={foo.ordHeaderId}
accountManager={foo.AccountManager}
...
should do the trick
That is use the exact property name from your foo object instead of using camel cased or some other version of it.
The if condition in <ItemList> render is wrong. It should be like
if(this.state.items.length > 0)
Everything else looks fine. But, you forgot to add the key to the <LineItem> component
<LineItem key={foo.ordHeaderId}
accountManager={foo.accountManager}
... />
Here, you are passing key as a prop to the <LineItem> component but you forgot to set that key from the prop to the parent element.
var LineItem = React.createClass({
render: function () {
return (
<div className="gridItem" key={this.props.key}>
<div className="lessLineHeight smallFont">
....
)
}
})
This should remove the error/warning
From what I have experienced you can't pass key as a prop element. Remove this from you LineItem and see if it works. Let the warning persist. You can figure out a way to remove the warning later if this works.
<LineItem
accountManager={foo.accountManager}
apptIconString={foo.apptIconString}
commodityDescription={foo.commodityDescription}
commodityId={foo.commodityId}
dateCreated={foo.dateCreated}
deliveryAppt={foo.deliveryAppt}
destContact={foo.destContact}
destinationAddress={foo.destinationAddress}
destinationAddressName={foo.destinationAddressName}
destinationCity={foo.destinationCity}
earlyDeliveryTime={foo.earlyDeliveryTime}
earlyPickupTime={foo.earlyPickupTime}
equipmentName={foo.equipmentName}
freightValue={foo.freightValue}
handlingUnits={foo.handlingUnits}
hazmatIconString={foo.hazmatIconString}
highValueIconString={foo.highValueIconString}
isHazmat={foo.isHazmat}
isHighValue={foo.isHighValue}
lateDeliveryTime={foo.lateDeliveryTime}
latePickupTime={foo.latePickupTime}
loadId={foo.loadId}
loadNum={foo.loadNum}
loadTmsStatus={foo.loadTmsStatus}
ordHeaderId={foo.ordHeaderId}
ordNum={foo.ordNum}
orderType={foo.orderType}
origContact={foo.originContact}
originAddress={foo.originAddress}
originAddressName={foo.originAddressName}
originationCity={foo.originationCity}
pickupAppt={foo.pickupAppt}
pieces={foo.pieces}
plannedEnd={foo.plannedEnd}
plannedStart={foo.plannedStart}
requiredTemp={foo.requiredTemp}
specialInstructions={foo.specialInstructions}
targetCost={foo.targetCost}
teamId={foo.teamId}
tempControlled={foo.tempControlled}
tradingPartnerNameCNum={foo.tradingPartnerNameCNum}
tradingPartnerName={foo.tradingPartnerNameClient}
transportMode={foo.transportMode}
user3gIdBookedBy={foo.user3gIdBookedBy}
user3gIdCreatedBy={foo.user3gIdCreatedBy}
weight={foo.weight} />
Random User found the answer and it's contained in his comment.
The "key" to the problem was not capitalizing the properties that were to be mapped. Not sure why it worked the way it was in Core MVC, but, obviously, it doesn't work the same in MVC 4.
Related
My app automatically update $content value without me clicking on buttons. I know it is a simple question, but I can't find out why, I'm learning svelte.
App.svelte
<script>
import { content } from './store.js';
import Item from './Item.svelte';
$content = [{ id:0,obj: "Fell free to browse any category on top." }];
function addContent(value) {
$content = [{ id:0,obj: value}]
}
</script>
<li><button on:click={addContent("Home Page")}>Home</button></li>
<li><button on:click={addContent("Products Page")}>Products</button></li>
<div class="Content">
<p>Fell free to browse any category on top.</p>
{#each $content as item}
<p><svelte:component this={Item} objAttributes={item} /></p>
{/each}
</div>
store.js
import { writable } from 'svelte/store';
export let content = writable({});
Item.svelte
<script>
import { fade } from 'svelte/transition';
export let objAttributes = {};
</script>
<p transition:fade>
{objAttributes.obj}
{#if objAttributes.otherattrib}<em>{objAttributes.otherattrib}</em>{/if}
</p>
This is because your on:click events are defined wrongly.
The on:click takes as argument a function like this
<button on:click={functionGoesHere}>
or, if you want it inlined
<button on:click={() => { }>
What happens in your case is that you directly call a function and the result of this function will then be called when the button is clicked. You can see that in this example:
<script>
function createFn() {
return () => console.log('logging this')
}
</script>
<button on:click={createFn}>Click here</button>
in this example the function () => console.log('logging this') will be attached the button.
So to come back to your code, this is easily fixed by making it a function instead of a function call:
<li><button on:click={() => addContent("Home Page")}>Home</button></li>
I've read all the documentation and stackoverflow I can find, but still am having issues with this.
I'm building a Trello clone with Vue and Rails.
I have many draggable lists.
Each list has draggable cards.
When I drag a card from one list to a second list, how do I persist this to my ajax rails endpoint?
I've tried using the #end method and the :move prop, but have had zero luck.
#app.vue
<template>
<draggable v-model="lists" group='lists' class="row dragArea" #end="listMoved">
<div v-for="(list, index) in lists" class="col-3">
<h6>{{ list.name }}</h6>
<hr />
<draggable v-model="list.cards" group='cards' class="dragArea" :move="cardMoved">
<div v-for="(card, index) in list.cards" class="card card-body mb-3">
{{ card.name }}
</div>
</draggable>
<div class="card card-body">
<input v-model="messages[list.id]" class="form-control" ></input>
<button v-on:click="submitMessages(list.id)" class="btn btn-secondary">Add</button>
</div>
</div>
</draggable>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: { draggable },
props: ["original_lists"],
data: function() {
return {
messages: {},
lists: this.original_lists
}
},
methods: {
cardMoved: function(event) {
console.log(event)
var data = new FormData
data.append("card[list_id]", WHERE_DO_I_FIND_THIS_ID)
data.append("card[position]", event.draggedContext.element.id)
Rails.ajax({
url: `/cards/${event.draggedContext.element.id}/move`,
type: "PATCH",
data: data,
datatype: "json"
})
},
}
}
</script>
Use the change event that contains all the dnd information you need and is called only once the drag operation is ended.
As suggested by #sovalina you need to pass extra infomation linked to the list:
:change="changed(list.id, $event)"
Also your div should be keyed:
<div v-for="(card, index) in list.cards" :key="card.name" class="card card-body mb-3">
I'm building a very simple blog app with React on the front-end and Rails on the backend. I can't quite figure out how to handle this error:
Uncaught Error: Posts.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
I have two react components, Post and Posts. Both are extremely simple. Here is Posts:
var Posts = React.createClass({
getInitialState: function(){
return {
posts: this.props.posts
}
},
render: function(){
var that = this;
var postBoard = this.state.posts.map(function(post){
return (
<Post post={post} key={post.id} />
);
});
return (
{ postBoard }
);
}
});
Here is Post:
var Post = React.createClass({
getInitialState: function(){
return {
post: this.props.post
}
},
render: function(){
return (
<div class="col-md-12">
<h3>{this.state.post.title}</h3>
<p>{this.state.post.content}</p>
</div>
);
}
});
Here is the Rails index view for Posts. I am using the react-rails gem:
<div class="container-fluid">
<div class="row">
<div class="col-md-6 col-md-push-3">
<h1>Posts</h1>
<%= react_component( "Posts", { posts: #posts }) %>
<br>
<%= link_to 'New Post', new_post_path %>
</div>
</div>
</div>
Here is the index action in my Posts controller:
def index
#posts = Post.all
end
I can't figure out what is causing this. It's been a few weeks since I've used React. I am clearly rusty. Help!
Wrap postBoard inside div. The reason of error is return methods return only one element. In your case postBoard is array. If the length of postBoard array is equal to 1, it should work without wraping it inside div element
return (
<div>
{ postBoard }
</div>
);
I've got a div element which I am using as a popup which does not update itself. I'll use the picture below to explain:
When I click on each row in the grid, the right hand side updates to show the details, part of which is a potentially lengthy Note field. The button View Note triggers a popup with the full text inside. Here, I have previously selected Donor 2000000, which displayed the correct note. However, when I select another Donor(2000002 as highlighted) the information in the popup retains the info for the first selected Donor. So, essentially it sets it once and then does not update.
The (partial) code for the main View is:
<div class="col-md-4 col-md-push-8">
<h4>
Donor Details
</h4>
<div id="donor-details">
<p class="muted">
Select donor to display detailed infomation
</p>
</div>
</div>
<div class="col-md-8 col-md-pull-4">
#Html.Action("DonorSummaryGrid") #* configure grid in a partial view *#
</div>
</div>
<script>
$(function () {
pageGrids.donorSummaryGrid.onRowSelect(function (e) {
$.post("/Donor/GetDonorDetails?donorId=" + e.row.DonorId, function (data) {
if (data.Status <= 0) {
alert(data.Message);
return;
}
$("#donor-details").html(data.Content);
});
});
});
</script>
The code for the partial view - which contains the details - is:
#if (!String.IsNullOrWhiteSpace(Model.CurrentNoteText)) {
<div id="note-dialog" title="Note for Donor #Model.DonorId">
<p>#Html.DisplayTextFor(model => model.CurrentNoteText)</p>
</div>
<br />
}
#Scripts.Render("~/Scripts/NoteDialogue.js")
When I debug, the value of Model.CurrentNoteText and #Model.DonorId do reflect the correct data.
NoteDialogue.js is:
$(function() {
$("#note-dialog").dialog({
autoOpen: false,
show: {
effect: "blind",
duration: 500
},
hide: {
effect: "blind",
duration: 200
}
});
$("#opener").click(function() {
$("#note-dialog").dialog("open");
});
});
I hope this is clear :)
Oh, there are no errors shown in the browser, and everthing functions as expected, except for updating the info in the popup.
I have tried
<script>
$("#note-dialog").html("#Model.CurrentNoteText");
</script>
with no success.
Got there in the end :)
Looking at the generated source, I noticed that the code for
<div id="note-dialog" title="Note for Donor #Model.DonorId">
<p>#Html.DisplayTextFor(model => model.CurrentNoteText)</p>
</div>
<br />
was being generated right at the bottom, outside all the other divs containing the grid and details. Looked something like this:
<div tabindex="-1" class="ui-dialog ui-widget ui-widget-content ui-corner-all ui-front ui-draggable ui-resizable" role="dialog" aria-describedby="note-dialog" aria-labelledby="ui-id-1"....>
As I clicked each row with a note in, an extra div was being generated, with the aria-labelledby attribute incrementing, e.g. "ui-id-2", "ui-id-3"...
The first generated div was being selected for the dialogue opening.
The solution was to remove the div on the row click event. Code now is:
$(function() {
pageGrids.donorSummaryGrid.onRowSelect(function (e) {
$("#note-dialog").remove();
$.post("/Donor/GetDonorDetails?donorId=" + e.row.DonorId, function(data) {
if (data.Status <= 0) {
alert(data.Message);
return;
}
$("#donor-details").html(data.Content);
});
});
});
I have a below custom directive in angularjs which uses model thats gets updated from server,
I have added a watch listener to watch the changes of that model,
var linkFn;
linkFn = function(scope, element, attrs) {
scope.$watch('$parent.photogallery', function(newValue, oldValue) {
if(angular.isUndefined(newValue)) {
return;
}
var $container = element;
alert($container.element);
$container.imagesLoaded(function() {
$container.masonry({
itemSelector : '.box'
});
});
});
};
return {
templateUrl:'templates/Photos_Masonry.htm',
replace: false,
transclude:true,
scope: {
photogallery: '=photoGallery',
},
restrict: 'A',
link: linkFn
However, when i debug in my watch directive, i still see that expressions in templates are still unresolved.i.e. photo.label, ng-src all are still unresolved. AFIK, $digest would be called only after $eval. Is this intended behavior?
My jQuery calls are not working due to this? Is there any other event where i get the result element with evaluated expressions?
Here is my template, which has ng-repeat in it,
<div id="container" class="clearfix">
<div class="box col2" ng-repeat="photo in photogallery">
<a ng-href="#/viewphotos?id={{photo.uniqueid}}&&galleryid={{galleryid}}"
title="{{photo.label}}"><img
ng-src="{{photo.thumbnail_url}}" alt="Stanley" class="fade_spot" /></a>
<h3>
<span style="border-bottom: 1px solid black;font-weight:normal;font-size:14px;">{{galleryname}}</span>
</h3>
<h3>
<span style="color:#20ACB8;font-weight:normal;font-size:17px;">{{photo.seasonname}}</span>
</h3>
</div>
</div>
photogallery is initialized in parent controller,
function MyCtrlCampaign($scope, srvgallery, mygallery) {
$scope.updatedata = function() {
$scope.photogallery = srvgallery.getphotos($routeParams);
};
$scope.getphotos = function() {
srvgallery.photos().success(function(data) {
$scope.updatedata();
}).error(function(data) {
});
};
Directive is used in below way,
<div masonry photo-gallery="photogallery" >
</div>
Kindly let me know your views on this.
Looks like this has been resolved in your Github issue (posted for the convenience of others).