fetch rest api in svelte onmount is happening after the screen is rendered - svelte-3

Using the onMount photo example in the svelte docs, I can use my own rest api (yet another todo ..) to display data. I changed it to put the photo html in a separate component and it works. Here is App.svelte
<script>
import { onMount } from 'svelte'
import Photos from './Photos.svelte'
let photos = []
onMount(async () => {
const res = await fetch('http:///mint20-loopback4:3000/todos');
photos = await res.json();
console.log('todolist : ', photos)
})
</script>
<Photos bind:photos={photos}/>
I then took the MDN svelte tutorial and went through that. Later, I removed the todo initial list from the store and added an onMount fetch in App.svelte. The result is an empty todo list but when I interact with the app, the data is displayed.
Here is the console output
Todos.svelte with length of : 0
into TodoStatus.svelte : Array []
totalTodos : undefined
completedTodos : undefined
Todos length - mounted : 0
todolist : Array(7) [ {…}, {…}, {…}, {…}, {…}, {…}, {…} ]
completed mount : Array(7) [ {…}, {…}, {…}, {…}, {…}, {…}, {…} ]```
And App.svelte
<script lang="ts">
import Todos from './components/Todos.svelte'
import Alert from './components/Alert.svelte'
import { onMount } from 'svelte'
import type { TodoType } from './types/todo.type'
let initialTodos: TodoType[] = []
onMount(async () => {
const res = await fetch('http:///mint20-loopback4:3000/todos');
const json = await res.json();
console.log('todolist : ', json)
json.forEach(element => {
let t: TodoType = { id: 0, title: '0', isComplete: true }
t.id = element.id
t.title = element.title
if (element.isComplete === undefined) {
t.isComplete = false
}
else {
t.isComplete = element.isComplete
}
initialTodos.push(t)
})
console.log('completed mount : ', initialTodos)
})
</script>
<Alert />
<Todos bind:todos={initialTodos} />
So it does as expected in the photos example but not in this where the empty array is passed on instead before the onMount. The todo example has a lot more components than photos which just has the single component but, suffice to say, I am confused. What do I have to do get the initial screen to use the populated data?
Just to add - I included console logging in photos and that too has the same pattern - first child component logs and finally the onMount in App.svelte. And yet here, the data is correctly displayed.
So I think is supposed to happen is that because changes to the array are dynamically reflected in the display, when the array is updated, so is the display. But in todos this is not happening.

The DOM is only updated on variable assignments. In your example, you're using the .push() method to add an object to the array. So you'll need to add this after your forEach loop to ensure the component gets the updates:
initialTodos = initialTodos;
Another option is to do this inline. You can replace:
initialTodos.push(t)
with
initialTodos = [...initialTodos,t]
Here is the official docs around this.

Related

Fetching data on IOS react redux

I'm building my first application using react and redux, and for some reason, the structure of the data received on a Async action is behaving different on the IOS (safari).
This action makes the fetch (cross-fetch) request:
export function fetchTransactions() {
return (dispatch) => {
dispatch(requestTransactions());
return fetch('/getTransactions', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Cache: 'no-cache',
},
credentials: 'same-origin',
})
.then(
response => response.json(),
error => console.log('An error occurred.', error),
)
.then(json => dispatch(receiveTransactions(json.dados)));
};
}
And the receive action is:
export const RECEIVE_TRANSACTIONS = 'RECEIVE_TRANSACTIONS';
export function receiveTransactions(data) {
return {
type: RECEIVE_TRANSACTIONS,
data,
};
}
Reducer
case 'REQUEST_TRANSACTIONS':
return Object.assign({}, {...state}, {isFetching: true});
case 'RECEIVE_TRANSACTIONS':
return Object.assign({}, {...state}, {data: action.data}, {isFetching: false});
Now I'll show you the results on Firefox running on linux:
It works as expected, the transaction data is present on the array called data.
Here is a print screen from a Safari emulator:
As you can see, the are duplicate fields and other fields. This behavior is causing the application to not work correctly. Any ideas? Thank you very much and sorry about the English thing.
#edit-------------------------------------------
Running the application on other emulator, results in a good state but the data isn't displayed on the table as you can see bellow:
As you can see, there are 100 data objects in the array, and nothing on the table, this is not happening on firefox or chrome.

Apollo GraphQL: Child Component Re-Runs Parent Query?

I've got a parent component with an Apollo query attached:
const withData = graphql(MY_QUERY, {
options({ userID }) {
return {
variables: { _id: userID}
};
},
props({ data: { loading, getOneUser } }) {
return { loading, getOneUser };
},
});
export default compose(
withData,
withApollo
)(NavigatorsList);
export { getOneUser_QUERY };
I've got a child component called userPhoto embedded in the render function:
return (
<div>
<userPhoto />
[.....]
</div>
)
Without the child component, the withData GraphQL function runs twice, once for loading == true, and one more time with the data returned.
With the child component included, the withData GraphQL function runs three times. The third time getOneUser is undefined and my component throws an error.
How can I correct this?
Thanks in advance to all for any info.
Fixed. There was a syntax error in the child component that wasn't throwing an error, but was causing the query to run twice + assorted other anomalies.

Sorting an array and saving to Mongo doesn't seem to trigger reactivity on my page

I have a template that I am loading from a route like so:
this.route('formEdit', {
path: '/admin/form/:_id',
data: function() { return Forms.findOne({_id: this.params._id}); },
onBeforeAction: function() { AccountUtils.authenticationRequired(this, ['ADMIN']); }
});
In which I have a template defined like:
<template name="formEdit">
<div id="formContainer">
...
{{#each header_fields}}
<div class="sortable">
{{> headerFieldViewRow }}
</div>
{{/each}}
</div>
</template>
And:
<template name="headerFieldViewRow">
{{#with header_field}}
...
{{/with}}
</template>
I then make the container around all the header fields sortable using jQuery UI Sortable:
Template.formEdit.rendered = function() {
$('.sortable').sortable({
axis: "y",
stop: function(event, ui) {
var form = Blaze.getData($('#formContainer')[0]);
var newFormHeaders = [];
$('#headerFieldsTable div.headerField').each(function(idx, headerFieldDiv) {
var header = Blaze.getData(headerFieldDiv);
header.sequence = idx;
Meteor.call('saveHeaderField', header);
newFormHeaders.push({header_field_id: header._id});
});
form.header_fields = newFormHeaders;
Meteor.call('saveForm', form);
}
});
}
Basically, when sorting stops, loop through all the headers, getting the data for each and updating the sequence number, then re-build the array in Forms and save them back. In the server code I have printouts for the two save calls, and the do properly print out the correct order of both the headers and the form.
The problem I am running into is that, after sorting, the visual display of the form and it's headers "snaps" back to the pre-sorted state, even though the data in the DB is correct. If I simply reload the form, either by hitting enter in the Address bar or by simply re-loading it from the menu, everything is displayed correctly. It's as if the reactive piece isn't working.
I have noted that I am getting an error when I update the client code in my server log that reads:
=> Client modified -- refreshing
I20141010-18:25:47.017(-4)? Failed to receive keepalive! Exiting.
=> Exited with code: 1
I don't think this is related as I was getting that error prior to adding this sorting code.
Update: Adding code for saveForm and saveHeader
saveForm:
// Saves the Form to the DB
saveForm: function(params) {
// Structure the data to send
var formEntry = {
title: params.title,
client_id: params.client_id,
header_fields: params.header_fields,
form_fields: params.form_fields,
created_by: Meteor.userId(),
created_on: new Date()
};
if (params._id == null) {
console.log("Saving new Form entry: %j", formEntry);
formEntry._id = Forms.insert(formEntry);
} else {
formEntry._id = params._id;
console.log("Updating Form entry: %j", formEntry);
Forms.update({_id: formEntry._id}, formEntry);
}
return formEntry;
}
saveHeader:
// Saves the HeaderField to the DB
saveHeaderField: function(params) {
// Structure the data to send
var headerFieldEntry = {
label: params.label,
field_type: params.field_type,
field_options: params.field_options,
form_id: params.form_id,
required: params.required,
allows_pictures: params.allows_pictures,
record_geo: params.record_geo
};
if (params._id == null) {
console.log("Saving new HeaderField entry: %j", headerFieldEntry);
headerFieldEntry._id = HeaderFields.insert(headerFieldEntry);
} else {
headerFieldEntry._id = params._id;
console.log("Updating HeaderField entry: %j", headerFieldEntry);
HeaderFields.update({_id: headerFieldEntry._id}, headerFieldEntry);
}
return headerFieldEntry;
}
I think the issue here is that Meteor.call will run on the server - you either need to use a callback or invalidate your template, if you want to return a value Meteor.call. From the docs:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
There is more info in this answer and this answer and the Meteor.call docs.
Hope that helps!

backbone.js load text file into a collection

This is first time I am using a framework for development and stuck with the very first step.
I am converting a Flex application to Javascript application and using backbone as framework.
I have to load a text file which is in name value format.
<!doctype html>
<html>
<head>
<script src='http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js'></script>
<script src='http://cdnjs.cloudflare.com/ajax/libs/backbone.js/0.9.10/backbone-min.js'></script>
<script>
var ResourceBundleCollection = Backbone.Collection.extend({
url:'ResourceBundle.txt',
});
var resourceBundleCollection = new ResourceBundleCollection();
resourceBundleCollection.fetch();
</script>
</head>
<body>
</body>
</html>
The ResourceBundle.txt includes the content in following format
location_icon=../../abc/test.png
right_nav_arrow_image=assets/images/arrow.png
right_nav_arrow_image_visible=true
It is throwing following error
not well-formed
I could load the text file easily using JQuery and parse it
$.ajax({
type : "GET",
url : "ResourceBundle.txt",
datatype : "script",
success : resourceXMLLoaded
});
and parse it using the following code
var lines = txt.split("\n");
for(var i=0;i<lines.length;i++) {
if(lines[i].length > 5) {
var _arr = lines[i].split("=");
resourceBundleObj[$.trim(_arr[0])] = $.trim(_arr[1]);
}
}
Please advice how to achieve the same results in backbone.js
If you MUST use plain text to support this, you can override Backbone.Collection.parse to achieve what you need.
In addition to that, you may also want to create a ResourceBundleModel to host each item in the ResourceBundleCollection.
You can see a demo here: http://jsfiddle.net/dashk/66nkF/
Code for Model & Collection is here:
// Define a Backbone.Model that host each ResourceBundle
var ResourceBundleModel = Backbone.Model.extend({
defaults: function() {
return {
name: null,
value: null
};
}
});
// Define a collection of ResourceBundleModels.
var ResourceBundleCollection = Backbone.Collection.extend({
// Each collection should know what Model it works with, though
// not mandated, I guess this is best practice.
model: ResourceBundleModel,
// Replace this with your URL - This is just so we can demo
// this in JSFiddle.
url: '/echo/html/',
parse: function(resp) {
// Once AJAX is completed, Backbone will call this function
// as a part of 'reset' to get a list of models based on
// XHR response.
var data = [];
var lines = resp.split("\n");
// I am just reusing your parsing logic here. :)
for (var i=0; i<lines.length; i++) {
if (lines[i].length > 5) {
var _arr = lines[i].split("=");
// Instead of putting this into collection directly,
// we will create new ResourceBundleModel to contain
// the data.
data.push(new ResourceBundleModel({
name: $.trim(_arr[0]),
value: $.trim(_arr[1])
}));
}
}
// Now, you've an array of ResourceBundleModel. This set of
// data will be used to construct ResourceBundleCollection.
return data;
},
// Override .sync so we can demo the feature on JSFiddle
sync: function(method, model, options) {
// When you do a .fetch, method is 'read'
if (method === 'read') {
var me = this;
// Make an XHR request to get data
// Replace this code with your own code
Backbone.ajax({
url: this.url,
method: 'POST',
data: {
// Feed mock data into JSFiddle's mock XHR response
html: $('#mockData').text()
},
success: function(resp) {
options.success(me, resp, options);
},
error: function() {
if (options.error) {
options.error();
}
}
});
}
else {
// Call the default sync method for other sync method
Backbone.Collection.prototype.sync.apply(this, arguments);
}
}
});
Backbone is designed to work with a RESTful API through JSON natively. It however is a library that is flexible enough to fit your need, given enough customization.
By default a collection in Backbone will only accept a JSON formatted collection.
So you need to convert your input to JSON format:
[{"name": "name", "value": "value},
{"name": "name", "value": "value}, ...
]
Of course you can override the default behaviour:
Overriding backbone's parse function

Backbone toJson doesn't return attributes when id is a string

I am running some rails code to generate json to be consumed by backbone. When I treat the id like a string, and consume it in backbone, the toJSON() function doesn't return the attributes. When I call a to_i on the id, toJSON() works properly. (But this breaks my app because "012345" is different from 12345.
My backbone view:
serialize: ->
console.log #model.toJSON()
info: #model.toJSON().info
non-working json response:
{"id":"123456","info":[{"label":"Hire Date","text":"06-NOV-00"},{"label":"User ID","text":"YADDA"},{"label":"Employee Number","text":"123456"}] }
non-working toJSON result:
data_partition: DataPartition
id: "123456"
__proto__: Object
working json:
{"id":123456,"info":[{"label":"Hire Date","text":"06-NOV-00"},{"label":"User ID","text":"YADDA"},{"label":"Employee Number","text":123456}] }
working toJSON():
data_partition: DataPartition
id: 123456
info: Array[3]
__proto__: Object
But this breaks my rails app when I chop off leading 0's.
Running this code I don't find any issue:
var responseJSON_1 = {"id":"123456","info":[{"label":"Hire Date","text":"06-NOV-00"},{"label":"User ID","text":"YADDA"},{"label":"Employee Number","text":"123456"}] };
var responseJSON_2 = {"id":123456,"info":[{"label":"Hire Date","text":"06-NOV-00"},{"label":"User ID","text":"YADDA"},{"label":"Employee Number","text":"123456"}] };
var MyModel = Backbone.Model.extend({});
var myModel_1 = new MyModel( responseJSON_1 );
var myModel_2 = new MyModel( responseJSON_2 );
console.log( myModel_1.toJSON() );
console.log( myModel_2.toJSON() );​
Check the working jsFiddle
Are you sure you are not changing more things in your response than the id format?

Resources