Filter retults of a Svelte Promise - svelte-3

In making my first app with Svelte I loved it except for the following which came up when filtering the results of a promise from a text input called search.
Filtering products before the promise resolves fails as products is type function.
I had to include a type check before continuing...
typeof products === "object" && ...
{#await promise then products}
<div class="flex justify-center flex-wrap">
{#each typeof products === "object" &&
products.filter((s) =>
s.name.toLocaleLowerCase().includes(search.toLocaleLowerCase())
) as product
}
<Product
{product}
{handleSelectProduct}
selected={selectedProducts.includes(product)}
/>
{/each}
</div>
{/await}
Is there a way to avoid this?
Thanks

There should be no need to validate typeof products === "object". But it's good practice to keep the logic (like filters) out of the template and keep the template "lean".
see example below (can run in https://svelte.dev/repl/)
<script>
let search = "c";
let promise = new Promise((resolve) => {
resolve([{ name: "abc" },{ name: "bcd" },{ name: "cde" },{ name: "def" }]);
});
$: filtered = promise.then((r) =>
r.filter((s) =>
s.name.toLocaleLowerCase().includes(search.toLocaleLowerCase())
)
);
</script>
<input bind:value={search}>
{#await filtered then products}
<ul>
{#each products as product}
<li>{product.name}</li>
{/each}
</ul>
{/await}
{#await promise then products}
<ul>
{#each products.filter((s) =>
s.name.toLocaleLowerCase().includes(search.toLocaleLowerCase())) as product
}
<li>{product.name}</li>
{/each}
</ul>
{/await}
Both blocks have the same result, so the typeof check is not needed (but the first one is a bit easier on the eyes).
If you are having issues that are requiring you to validate the product type, you should check the result of the promise.

Related

Extract a list of items/props within an array to be rendered based on id

I'm trying to create web templates using React.js, and have an array where I list different content fragments (based on which page is rendered). I'm struggling to understand how to use a combination of filter and map in order to extract the relevant parts of the array.
At the moment, I'm trying to use the id (although being able to use the pathname would be ideal) and have the following:
// My Array
const Top = [
{
id: 1,
path: "/myfirstpath",
title: "My First Title",
content1: "Some first content",
content2: "some more first content",
},
{
id: 2,
path: "/mysecondpath",
title: "My Second Title",
content1: "Some second content",
content2: "Some more second content",
}
]
export default Top;
What I'd like to be able to do is pull out everything that's in the id: 1 object, and render it in this framework:
// My Content Framework
import React from "react";
function Content(props) {
return (
<div className= "container-top">
<section class="header">
<div class="wrapper">
<div class="title">
<h1>{props.title}</h1>
</div>
<div className="description">
<p>{props.content1}</p>
<p>{props.content2}</p>
</div>
</div>
</section>
</div>
)
}
export default Content;
Then, for another page, I'd like to do the same for id: 2, and so on.
And here's where I've got to so far:
// My Attempt to Populate My Content
function createContent(selectedObject) {
return (
<Content
key={selectedObject.id}
title={selectedObject.title}
content1={selectedObject.content1}
content2={selectedObject.content2}
/>
);
}
function renderedContent() {
return(
<div>{TopBox.filter("What goes in here?!")}</div>
)
}
export default renderedContent;
I've tried the following where I've written "What goes in here?!":
(selectedObject => selectedObject.id === "1").map(createContent)
(createContent => createContent.id === "1").map(createContent)
Apologies if the terminology I've used isn't correct - I'm quite new to all this!
Thanks for any advice you can lend.
You were actually quite close. You can use Array.filter(f => condition) to filter based on whatever property you need.
The filter() method creates a new array with all elements that pass the test implemented by the provided function.
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]
Where you probably went wrong was in (selectedObject => selectedObject.id === "1").map(createContent). In JavaScript 1 === '1' is false. Strict equality compares two values for equality. If the values have different types, the values are considered unequal.
In the example below, I use content.id === 1 as the filter condition (as per your question), and it displays the Content with id=1.
function Content(props) {
return (
<div className="container-top">
<section class="header">
<div class="wrapper">
<div class="title">
<h1>{props.title}</h1>
</div>
<div className="description">
<p>{props.content1}</p>
<p>{props.content2}</p>
</div>
</div>
</section>
</div>
);
}
function createContent(selectedObject) {
return (
<Content
key={selectedObject.id}
title={selectedObject.title}
content1={selectedObject.content1}
content2={selectedObject.content2}
/>
);
}
function App() {
const Top = [
{
id: 1,
path: '/myfirstpath',
title: 'My First Title',
content1: 'Some first content',
content2: 'some more first content'
},
{
id: 2,
path: '/mysecondpath',
title: 'My Second Title',
content1: 'Some second content',
content2: 'Some more second content'
}
];
const filterBy = (content) => content.id === 1; // <= use whatever prop/value
return <div>{Top.filter(filterBy).map(createContent)}</div>;
}
ReactDOM.render(<App />, document.getElementById("react"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You can swap the id for pathname as well in the filterBy method.

Vue js trucate property on array

I am trying use Vuejs's vue-truncate-collapsed property to add the read more and read less button. A Worker can have multiple services. Something like this. Services are stored as array.
<li v-for="item in worker.service_names">
{{ item}}
</li>
this works perfectly fine. But now what I want to do is display default 5 values(not sure how to add the length of 5) and if a worker has more than 5 services then read more button appear. I am not able to implement this on an array. Please help me figure out the issue. I am new to Vuejs.
<truncate
action-class="action"
clamp="..."
:length="5"
less="read less"
:text="<li>worker.service_names</li>"
type="html"
>
</truncate>
You could iterate over a computed list. If it is a short list the computed value will return a shortened list otherwise the full list.
var vm = new Vue({
el: '#app',
data: {
workers: ['A','B','C','D','E','F','G','H'],
showNum: 4,
short: true
},
computed: {
visibleWorkers(){
if(this.short){
return this.workers.slice(0,this.showNum)
}else{
return this.workers
}
}
},
methods: {
showMore(){
this.short=!this.short;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<div>
<ol>
<li v-for="item in visibleWorkers">
{{item}}
</li>
</ol>
<button #click="showMore">{{short?'show more':'hide'}}</button>
</div>
</div>

ReactJS not binding via map to state values

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.

How to access children components props with react-rails

I have watched this talk and I am right now trying to use react components to show a list of messages (in a chat). So I have two react components:
1) The "Message" component that is basicaly an li with the message props (dateTime,
userName, and body);
var Message = React.createClass({
render: function () {
return (
<li className="right clearfix">
<span className="chat-img pull-right">
<div className="chat-body clearfix">
<div className="header">
<small className=" text-muted"> sent {this.props.dateTime}
</small>
<strong className="primary-font">{this.props.userName</strong>
</div>
<p>{this.props.body}</p>
</div>
</li>
);
}
});
2) The "Messages" component which is a ul that lists all the chat messages
var Messages = React.createClass({
render() {
var createMessage = ({dateTime, userName, body, id}) => <Message key={id} dateTime={dateTime} userName={userName} body={body} />;
return <ul className="chat">{this.props.messages.map(createMessage)}</ul>;
}
});
In my view this is what I have:
<%= react_component('Messages', { messages: #messages }) %>
Nothing too complicated. The problem is that I don't know how to change the "message" component attributes. For example if I want to change the format of the messages created_at attributes ? Or the users names based on a model method ?
Do I have to parse these "props" with json from the controller ?
I'm just trying to figure out how react could work with my rails app, please don't hesitate to leave any suggestion/info. Thanks in advance

How to clear a new record from ember.js frontend after it fails Rails validation (422 error)?

I'm working on a basic reddit clone app with Rails and ember.js (via the ember-rails gem). Basically I have a 'post' model/controller in Rails which works correctly, but when I add a new post from the ember post model's create action, even if it fails the Rails validation, if I then go to the 'posts' index page which lists all the posts, I can see it there (i.e. ember is keeping the data). When I refresh it goes away, but I'm wondering what is the best way to purge that data so that it gets deleted upon rejection from the backend? Another odd thing is that simply going to the posts/new page at all creates a new blank post which is then visible on the Then on the client-side, I have the following files in app/assets/javascripts/routes:
posts_route.js:
RedditJp.PostsRoute = Ember.Route.extend({
model: function() {
return this.get('store').find('post');
}
});
posts_new_route.js:
RedditJp.PostsNewRoute = Ember.Route.extend({
model: function(){
return this.get('store').createRecord('post'); },
actions: {
create: function() {
var newPost = this.get('currentModel');
var self = this;
newPost.save().then(
function() { self.transitionTo('posts'); },
function() { }
);
}
}
});
Here's the form I'm trying to use to submit the data in posts/new.hbs:
<h1> Add a post </h1>
{{#each error in errors.title}}
<p>{{error.message}}</p>
{{/each}}
<form {{action "create" on="submit"}}>
<div>
<label>
Title<br/>
{{input type="text" value=title}}
</label>
</div>
<div>
<label>
Address<br/>
{{input type="text" value=address}}
</label>
</div>
<div>
<label>
Vote count<br/>
{{input type="text" value=voteCount}}
</label>
</div>
<button>Save</button>
</form>
and then in assets/javascripts/templates/posts/ I have index.hbs:
<h1>Posts</h1>
<ul>
{{#each}}
<li>{{title}} at {{address}} vote count: {{voteCount}}</li>
{{else}}
<li>There are no posts.</li>
{{/each}}
</ul>
and here's my router.js:
RedditJp.Router.map(function() {
this.resource('posts', function() {
this.route('new')
});
this.resource('home', function() {
});
});
RedditJp.IndexRoute = Ember.Route.extend({
redirect: function(){
this.transitionTo('home')
}
});
I was thinking I could just add a check in the posts/index.hbs file and only show records that aren't dirty, but there must be a cleaner way of doing it, so I'm wondering what would be considered best practice in this case (I'm thinking there should be some code I could add to the promise in posts_new_route.js to deal with this, but I can't quite figure it out).
Thanks a lot! And let me know if you need any additional info.
You can check if model isNew in template to hide new Record ( also you can use isEmpty property )
var record = store.createRecord('model');
record.get('isNew'); // true
record.save().then(function(model) {
model.get('isNew'); // false
});
In template will look like {{each model}}
{{#if model.get('isNew')}}
record.save().then(function(){
// Success callback
}, function() {
model..deleteRecord();
});

Resources