How get data lists from json using Vue axios infinite scroll - json

I would like to import the data from Vue.js to axios and apply the infinite scroll.
and want to have json data displayed in order. Now only index [0] is being repeated. How can we solve it? Thank you.
https://jsfiddle.net/naeun/eywraw8t/131773/
<div id="app">
<section data-aos="fade-zoom-in" v-for="post in posts">
<div class="post">
<p class="colon" data-aos="fade-zoom-in"><span>“</span></p>
<p class="quote">{{post.quote}}</p>
<p class="colon" data-aos="fade-zoom-in"><span>”</span></p>
<p class="author" data-aos="fade-zoom-in">{{post.author}}</p>
</div>
</section>
</div>
new Vue({
el: "#app",
data: {
bottom: false,
posts: []
},
methods: {
bottomVisible() {
const scrollY = window.scrollY;
const visible = document.documentElement.clientHeight;
const pageHeight = document.documentElement.scrollHeight;
const bottomOfPage = visible + scrollY >= pageHeight;
return bottomOfPage || pageHeight < visible;
},
addPost() {
axios.get(`https://jsonplaceholder.typicode.com/posts`)
.then(response => {
let api = response.data[0];
let apiInfo = {
author: api.id,
quote: api.title,
tag: api.body
};
this.posts.push(apiInfo)
if(this.bottomVisible()) {
this.addPost();
}
})
.catch(e => {
console.log('Error: ', error)
})
}
},
watch: {
bottom(bottom) {
if (bottom) {
this.addPost();
}
}
},
created() {
window.addEventListener('scroll', () => {
this.bottom = this.bottomVisible()
});
this.addPost();
}
})

There are a few problems here. First, whenever you scroll to the bottom, you call the addPost method, right? But the method itself doesn't know which "page" to load. It does the very same request over and over again. Which means it gets the same results each time.
Then you use this let api = response.data[0];, which means that no matter what results you receive, you only get the first item from the list and push it to your local array.
What you need to do is to keep track of the virtual "page" that you are loading, meaning that each addPost is like loading additional items from a virtual pagination and just putting them at the end of the infinite list, instead of reloading the page. Then you need to pass this parameter to the method that loads those new items and prepare the backend to return specific items based on request parameters.
Good luck!

Related

Batching with useQuery react hooks getting back undefined

I am currently working on a project which requires me to make multiple queries/mutations. I tried setting up my apollo client with BatchHttpLink and I can see the data I am requesting in the network tab in the browser. It is coming back at an array of objects instead of JSON.
But the issue is when I try to grab the data in my component data is undefined. I tried using HttpLink instead of BatchHttpLink and I can get the data back from the hook.
My suspicion is the shape of the object that comes back from the response is different, I tried looking into documentation but I can't find much about batching.
Currently using "#apollo/client#^3.0.2"
Here's my client set up.
import { ApolloClient, InMemoryCache, ApolloLink, from } from '#apollo/client'
import { BatchHttpLink } from '#apollo/client/link/batch-http'
import { onError } from '#apollo/client/link/error'
const BASE_URL = 'http://localhost:4000'
const httpLink = new BatchHttpLink({
uri: BASE_URL,
credentials: 'include',
})
const csrfMiddleware = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
'X-CSRF-Token': getCSRFToken(),
},
}))
return forward(operation)
})
const errorMiddleware = onError(({ networkError }) => {
if (networkError && 'statusCode' in networkError && networkError.statusCode === 401) {
window.location.assign('/accounts/login')
}
})
const client = new ApolloClient({
link: from([errorMiddleware, csrfMiddleware, httpLink]),
cache: new InMemoryCache(),
})
This is the react hook I'm trying to console log.
const {data} = useQuery(GET_USER_PERMISSIONS_AND_PREFERENCES)
Figured it out. You need to add another middleware to return the data that the useQuery hook can recognize. The data that comes back in the batch call is an array of objects shaped
{
payload: {
data: { ... }
}
}
So something like this did the trick for me
const batchParseMiddleware = new ApolloLink((operation, forward) => {
return forward(operation).map((data: any) => data.payload)
})
I have been having a similar issue, and have so far only been able to solve it by breaking batching and converting to a normal HttpLink

Weird behaviour with React Hooks and FileReader

I'm new to React Hooks and honestly I'm not sure if this problem is related to Hooks or if I'm just doing something generally wrong.
I want to build a image uploader comonent that uses the HTML5 FileReader in order to show users the uploaded images before actually POSTing them.
Below is what I have so far.
Basically <div id="from-effect"></div> is currently my way of checking whether the images could be rendered.
I first wanted to fill this <div> without side effects (like <div>I have {files.length} files</div>) but this didn't react to changes at all.
The solution below with useEffect is reacting to changes.
However, if you try uploading a few images you will notice that quite often it's showing wrong results.
function FileUploader(props) {
const [files, setFiles] = useState([]);
const loadImageContent = (name, newFiles) => {
return (e) => {
newFiles.push({ name: name, src: e.target.result });
};
}
const handleUpload = async (e) => {
const newFiles = [];
for (const file of e.target.files) {
const reader = new FileReader();
reader.onload = loadImageContent(file.name, newFiles);
await reader.readAsDataURL(file);
}
setFiles(newFiles);
}
useEffect(() => {
console.log('in use Effect, files:', files);
const prevCont = document.getElementById("from-effect");
prevCont.innerHTML = `I have ${files.length} files`;
});
return <div>
<input
type="file" name="fileUploader" id="fileUploader"
accept="image/*" multiple="multiple"
onChange={handleUpload}
/>
<div id="from-effect"></div>
</div>;
}
What am I doing wrong?
Or even better, how can I implement this without side effects?
I am not sure I follow your ultimate goal, or what you mean when you say you want to show users the uploaded images before POSTing them - do you want to POST automatically, or do you want the user to click an "upload/save/POST" button or something?
Here is an example of how to display images:
Edit: made things a little more clear, added "save" button which shows an alert that contains data you could possibly use to POST back to your server. Also, added a method to "JSONify" the file metadata, since the way we are uploading files does not let us natively convert [object File] into JSON.
const { useState } = React;
function FileUploader(props) {
const [files, setFiles] = useState([]);
const getFileMetadata = file => {
/**
* The way we are handling uploads does not allow us to
* turn the uploaded [object File] into JSON.
*
* Therefore, we have to write our own "toJSON()" method.
*/
return {
lastModified: file.lastModified,
name: file.name,
size: file.size,
type: file.type,
webkitRelativePath: file.webkitRelativePath
}
}
const handleUpload = e => {
let newstate = [];
for (let i = 0; i < e.target.files.length; i++) {
let file = e.target.files[i];
let metadata = getFileMetadata(file);
let url = URL.createObjectURL(file);
newstate = [...newstate, { url, metadata }];
}
setFiles(newstate);
};
const handleSave = () => {
alert(`POST Files Here..\n\n ${JSON.stringify(files,null,2)}`);
}
return (
<div>
<input type="file" accept="image/*" multiple onChange={handleUpload} />
<div>
<button onClick={handleSave} disabled={!(files && files.length > 0)}>
Save Image(s)
</button>
</div>
{files.map(f => {
return (
<div>
<img src={f.url} height="100" width="100" />
</div>
);
})}
</div>
);
}
ReactDOM.render(<FileUploader />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Is it possible to populate the input bar in webchat with an onclick method

I'm attempting to display a list of popular questions to the user, when they click them I want them to populate the input bar and/or send the message to the bot via the directline connection.
I've attempted using the ReactDOM.getRootNode() and tracking down the input node and setting the .value attribute, but this does not populate the field. I assume there is some sort of form validation that prevents this.
Also, if I console log the input node then save it as a global variable in the console screen I can change the value that way, but then the message will not actually be able to be sent, hitting enter or the send arrow does nothing. While it may seem that the suggestedActions option would work well for this particular application, I CANNOT use it for this use case.
const [chosenOption, setChosenOption] = useState(null);
const getRootNode = (componentRoot) =>{
let root = ReactDom.findDOMNode(componentRoot)
let inputBar = root.lastChild.lastChild.firstChild.firstChild
console.log('Initial Console log ',inputBar)
setInputBar(inputBar)
}
//in render method
{(inputBar && chosenOption) && (inputBar.value = chosenOption)}
this is the function I tried to use to find the node, the chosen option works as intended, but I cannot change the value in a usable way.
I would like the user to click on a <p> element which changes the chosenOption value and for that choice to populate the input bar and/or send a that message to the bot over directline connection.What I'm trying to accomplish
You can use Web Chat's store to dispatch events to set the send box (WEB_CHAT/SET_SEND_BOX) or send a message (WEB_CHAT/SEND_MESSAGE) when an item gets clicked. Take a look at the code snippet below.
Simple HTML
<body>
<div class="container">
<div class="details">
<p>Hello World!</p>
<p>My name is TJ</p>
<p>I am from Denver</p>
</div>
<div class="wrapper">
<div id="webchat" class="webchat" role="main"></div>
</div>
</div>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script>
// Initialize Web Chat store
const store = window.WebChat.createStore();
// Get all paragraph elements and add on click listener
const paragraphs = document.getElementsByTagName("p");
for (const paragraph of paragraphs) {
paragraph.addEventListener('click', ({ target: { textContent: text }}) => {
// Dispatch set send box event
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
});
}
(async function () {
const res = await fetch('/directline/token', { method: 'POST' });
const { token } = await res.json();
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
document.querySelector('#webchat > *').focus();
})().catch(err => console.error(err));
</script>
</body>
React Version
import React, { useState, useEffect } from 'react';
import ReactWebChat, { createDirectLine, createStore } from 'botframework-webchat';
const WebChat = props => {
const [directLine, setDirectLine] = useState();
useEffect(() => {
const initializeDirectLine = async () => {
const res = await fetch('http://localhost:3978/directline/token', { method: 'POST' });
const { token } = await res.json();
setDirectLine(createDirectLine({ token }));
};
initializeDirectLine();
}, []);
return directLine
? <ReactWebChat directLine={directLine} {...props} />
: "Connecting..."
}
export default () => {
const [store] = useState(createStore());
const items = ["Hello World!", "My name is TJ.", "I am from Denver."]
const click = ({target: { textContent: text }}) => {
store.dispatch({
type: 'WEB_CHAT/SET_SEND_BOX',
payload: {
text
}
});
}
return (
<div>
<div>
{ items.map((item, index) => <p key={index} onClick={click}>{ item }</p>) }
</div>
<WebChat store={store} />
</div>
)
};
Screenshot
For more details, take a look at the Programmatic Post as Activity Web Chat sample.
Hope this helps!

How to create ionic 2 infinite scroll with data from database

I'd query about 500 users from database who are active. I am using laravel and ionic framework.
Here is my query using laravel.
public function getUsers(Request $request) {
$users = DB::table('users')->where('status', $request->status)->get();
return Response::json($users);
}
Here might be my .ts code in ionic 2
import { Component } from '#angular/core';
import { Http, Headers, RequestOptions } from "#angular/http";
#Component({
selector: 'page-users',
templateUrl: 'users.html'
})
export class UsersPage {
users: any = [];
constructor(
private http: Http
) {
var headers = new Headers();
headers.append("Accept", 'application/json');
headers.append('Content-Type', 'application/json' );
let options = new RequestOptions({ headers: headers });
let data = { 'status': 'active'}
this.http.post('http://path/to/laravel/api/getUsers', data, options)
.subscribe(res => {
var jsonData = JSON.parse(res['_body']);
this.users = jsonData;
})
}
doInfinite(infiniteScroll) {
// How can I do infinite scroll here???
setTimeout(() => {
infiniteScroll.complete();
}, 1000);
}
}
I am able to get the 500 active users and display it on my view.
However, It is a kind of laggy because there are too many data query at once.
I want to create infinite scroll to optimize the query. But, I don't know how to implement that in ionic 2.
I want atleast 10 users to be query everytime I scroll. Answers are appreciated.
I'll just pull off one of my codes and replace some variable names. Hopefully it will make sense and be helpfull:
Assuming you have this on your html:
<ion-infinite-scroll (ionInfinite)="fetchMore($event)">
<ion-infinite-scroll-content></ion-infinite-scroll-content>
</ion-infinite-scroll>
You will need something like this on your .ts:
fetchMore(event) {
this.myService.getMoreItems(10/*number of items to fetch*/,this.collection.length/*you might need to send the "skip" value*/).then(
(moreItems: any[]) => {
if (moreItems.length > 0) {
this.collection = this.collection.concat(moreItems);
}
event.complete();
},
(err) => {
event.complete();
}
);
}
The myService.getMoreItems part will be your service/provider that has the function, return type is a Promise<any>, that will make the Http Request. I think you got the idea.

Content inside html elements are rendered with a delay - ReactJS

I am learning ReactJS by creating ReactJS version of my blog. While I was testing google page speed, I am noticed the "prioritize visible content", fine, while articles are loading I have added a placeholder article with title of a loading message and lorem ipsum description and a sample default header image. I have run the page speed again, still the same issue with one difference. The placeholder article is rendered, but the title, image and description are not rendered. It is a static text, cannot imagine why it´s not showing. I have tried to simulate low internet connection and reload the page and yes, the text inside component is rendered with some delay, even its just static text.
The code is available here: https://github.com/erikkubica/reactjs-simple-blog-test See the src/modules/article/ArticleListItemPlaceholder.js and ArticleList.js
I have also noticed that the logo is also missing at this moment. Cannot imagine why, if on non-react website it´s not. Also the styles are loaded, navigation component is rendered...
See in action http://reactjs.netlime.eu/
Screenshot about the problem:
Thank you, I will be happy to get any explanation, good practices,... to learn more.
UPDATE:
Problem resolved.
Problem was that while custom fonts was not loaded browser made the text invisible. I have added fontFamily: "Arial" into inline style of elements which fixed the issue. Thanks to Mr Lister.
Also big thanks to John Ruddell for giving some best practice.
Your issue is you are setting state async but trying to render things in a semi sync manner.
async fetchData() {
//const response = await fetch("/static/articles.json");
let data = null; //await AsyncStorage.getItem('#MySuperStore:articles_storage_' + this.state.category);
if (data === null) {
const response = await fetch("https://www.netlime.eu/wp-json/get-posts/v2/all/");
data = await response.json();
await AsyncStorage.setItem('#MySuperStore:articles_storage_' + this.state.category, JSON.stringify(data));
}
try {
data = JSON.parse(data);
} catch (e) {
}
// your issue is here
______________________________________________
this.setState({articles: data});
this.createArticleList();
______________________________________________
// change to this.
this.setState({articles: data}, () => {
this.createArticleList();
});
}
because setState is async you are creating the articleList state item before the articles are set on state. so the UI never gets updated
Edit
To be honest you shouldn't use a secondary state variable to hold an array of the articles to be rendered. create them on the fly.
export default class ArticleList extends React.Component {
constructor(props) {
super(props);
this.state = {
articles: [],
category: this.props.category ? this.props.category : 0
};
}
async fetchData() {
let data = null; //await AsyncStorage.getItem('#MySuperStore:articles_storage_' + this.state.category);
if (data === null) {
const response = await fetch("https://www.netlime.eu/wp-json/get-posts/v2/all/");
data = await response.json();
await AsyncStorage.setItem('#MySuperStore:articles_storage_' + this.state.category, JSON.stringify(data));
}
try {
data = JSON.parse(data);
} catch (e) {
}
this.setState({articles: data});
}
componentDidMount() {
this.fetchData();
}
render() {
const articles = this.state.articles.map((article) => {
return (
<ArticleListItem
key={article.ID}
article={article}
/>
);
});
return (
<div>
{ articles.length ? articles : <ArticleListItemPlaceholder/>}
</div>
);
}
}