Apollo GraphQL FetchMore - apollo

I'm trying to get Apollo gql to load more posts after clicking a button. So it would load the next 15 results, every time you click - load more.
This is my current code
import Layout from "./Layout";
import Post from "./Post";
import client from "./ApolloClient";
import { useQuery } from "#apollo/react-hooks"
import gql from "graphql-tag";
const POSTS_QUERY = gql`
query {
posts(first: 15) {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
const Posts = props => {
let currPage = 0;
const { posts } = props;
const { loading, error, data, fetchMore } = useQuery(
POSTS_QUERY,
{
variables: {
offset: 0,
limit: 15
},
fetchPolicy: "cache-and-network"
});
function onLoadMore() {
fetchMore({
variables: {
offset: data.posts.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
posts: [...prev.posts, ...fetchMoreResult.posts]
});
}
});
}
if (loading) return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
Loading...
</div>
</div>
);
if (error) return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
Oops, there was an error :( Please try again later.
</div>
</div>
);
return (
<div className="container mx-auto py-6">
<div className="flex flex-wrap">
{data.posts.nodes.length
? data.posts.nodes.map(post => <Post key={post.postId} post={post} />)
: ""}
</div>
<button onClick={() => { onLoadMore() }}>Load More</button>
</div>
);
};
export default Posts;
When you click load more it refreshes the query and console errors
Invalid attempt to spread non-iterable instance
I have been loading for solutions but a lot of the examples are previous or next pages like traditional pagination. Or a cursor based infinite loader which I don't want. I just want more posts added to the list onClick.
Any advise is appreciated, thank you.

Your current POSTS_QUERY it isn't accepting variables, so first you need change this:
const POSTS_QUERY = gql`
query postQuery($first: Int!, $offset: Int!) {
posts(first: $first, offset: $offset) {
nodes {
title
slug
postId
featuredImage {
sourceUrl
}
}
}
}
`;
Now, it will use the variables listed in your useQuery and fetchMore.
And to finish the error is because updateQuery isn't correct, change it to:
function onLoadMore() {
fetchMore({
variables: {
offset: data.posts.nodes.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return { posts: { nodes: [...prev.posts.nodes, ...fetchMoreResult.posts.nodes] } };
});
}
});
}

I would suggest useState hook to manage a variable that stores current offset in the dataset, place a useEffect to watch changes to that offset, the offset value in passed as query variable to load data. Remove fetchmore, useEffect hook will do the job.
When user clicks on load more button, you just need to update offset value, that will trigger the query and update data.
const [offset,setOffset] = React.useState(0)
const [results, setResults] = React.useState([])
const { loading, error, data } = useQuery(
POSTS_QUERY,
{
variables: {
offset: offset,
limit: 15
},
fetchPolicy: "cache-and-network"
}
);
React.useEffect(() => {
const newResults = [...results, ...data]
setResults(newResults)
}, [data])
function onLoadMore() {
setOffset(results.data.length)
}

Related

Vue.js, How to get a list of latitude and longitude of a nested json file troughouth a function?

If i use getcoords() , returns always just the first latitude and longitude for all my array length.
If i use normal syntax {{fire.latitude}},{{fire.longitude}} returns all latitudes and longitudes.
i don't know why, probably a very simple thing
this is the first problem that write, i hope to be was clear.
thanck all for the help .
<template>
<div>
<div v-for="(fire, index) in fires" :key="index">
<!-- {{index}}.{{getCoords()}} -->
{{fire.lat}},{{fire.lon}}
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'FiresList',
data () {
return {
fires: null,
errored: false,
}
},
mounted () {
axios.get('https://storage.googleapis.com/public.storykube.com/start2impact/fires.json')
.then(response => {
this.fires = response.data.map((coords)=> {
return {lat: coords.latitude, lon: coords.longitude, date: coords.date}
})
console.log(this.fires);
})
.catch(error => {
console.log(error)
this.errored = true
})
},
methods: {
getCoords (){
for (var latlon in this.fires){
return [this.fires[latlon].lat, this.fires[latlon].lon]
//OTHERWISE
// for (let i = 0; i <= this.fires.length; i++){
// return [this.fires[i].lat, this.fires[i].lon]
}
}
}
}
</script>
<style scoped>
</style>
I think what you are looking for is this:
<div v-for="(fire, index) in fires" :key="index">
{{index}}.{{getCoords(index)}}
</div>
getCoords(index) {
return [this.fires[index].lat, this.fires[index].lon]
}

Vue Warn: property "search" is not defined on instance

i'm creating a web app with a Django server with api from rest_framework and Vue as frontend (Nuxtjs in particular).
Trying to create a "search filter bar" i've got this error and i don't know why:
ERROR [Vue warn]: Property or method "search" is not defined on the instance but
referenced during render. Make sure that this property is reactive,
either in the data option, or for class-based components, by
initializing the property. See:
https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
This is my file.vue
<template>
<div>
<v-text-field v-model="search" label="search conditions" outlined dense></v-text-field>
</div>
<div>
<v-list v-for="condition in filteredConditions" :key="condition.id" >
<v-list-item>
<condition-card :onDelete="deleteCondition" :condition="condition"></condition-card>
</v-list-item>
</v-list>
</div>
</div>
</template>
<script>
import ConditionCard from "~/components/ConditionCard.vue";
export default {
head() {
return {
title: "Conditions list",
search: ""
};
},
components: {
ConditionCard
},
async asyncData({ $axios, params }) {
try {
let query = await $axios.$get(`/conditions/`);
if (query.count > 0){
return { conditions: query.results }
}
return { conditions: [] };
} catch (e) {
return { conditions: [] };
}
},
data() {
return {
conditions: []
};
},
...
...
computed: {
filteredConditions: function(){
return this.conditions.filter((condition) => {
return condition.name.toLowerCase().match(this.search.toLocaleLowerCase())
});
}
}
};
</script>
<style scoped>
</style>
The api is:
{"count":15,
"next":null,
"previous":null,
"results":[{"id":1,
"name":"Blinded",
"description":"A blinded creature can't see...",
"source_page_number":290},
{"id":2,
"name":"Charmed",
"description":"A charmed creature can't...",
...
...
Try to move the search variable from head() to data()
head() {
return {
title: "Conditions list"
}
},
...
data(){
return{
conditions: [],
search : ''
}
}

Why is React not displaying any data?

import React, { Component } from 'react';
import axios from 'axios';
I would like to display this on the page:
class ProjectDeatil extends Component {
constructor(props) {
super(props);
this.state = { user: { name: '' } };
}
.
componentDidMount() {
const { match: { params } } = this.props;
axios.get(`http://localhost:8000/api/project/${params.pk}`)
.then(({ data: user }) => {
console.log( user);
this.setState({"User:": user });
});
}
I added her const
render() {
const{ user } = this.state;
return (
Then I tried displaying it again and it still didn't work
<div className="col-md-4 text-white animated fadeInUp delay-2s if " >
<h1>{user.title}</h1>
<h1> Hello Dear</h1>
</div>
I also tried using django rest api and that also didn't work.
</div>
);
}
}
export default ProjectDeatil
You have to fix this line:
this.setState({"User:": user });
to
this.setState({"user": user });

Kendo Multi-select in cascading scenario unable to populate initial values

I'm using Telerik for MVC and trying to get the multi-select to populate with the initial values in an Edit scenario.
<script>
function filterProducts() {
return {
manufacturerId: $("#ServiceBulletinItem_ManufacturerId").val()
};
}
function onManufacturerChange(e) {
var v = e.sender.dataItem().Value;
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: v }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
function InitialPopulate(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result)
});
}
$(document).ready(function () {
$('.control-datepicker').Zebra_DatePicker();
var m = $("#ServiceBulletinItem_ManufacturerId").val();
InitialPopulate(m);
});
</script>
<div class="form-group">
#Html.LabelFor(m => m.ManufacturerList, "Manufacturer", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().DropDownListFor(m => m.ServiceBulletinItem.ManufacturerId)
.HtmlAttributes(new { #class = "col-md-6 form-control" })
.Filter("contains")
.DataValueField("Value")
.DataTextField("Text")
.BindTo((IEnumerable<SelectListItem>)Model.ManufacturerSelectList)
.HtmlAttributes(new { style = "width:70%;" }).Events(e =>
{
e.Change("onManufacturerChange");
})
)
</div >
</div >
<div class="form-group">
#Html.LabelFor(m => m.ProductList, "Product", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#(Html.Kendo().MultiSelectFor(m => m.ServiceBulletinItem.ApplicableProducts)
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products...")
)
</div>
</div>
I'm trying to populate the manufacturer drop down and the Product multiSelect. The ApplicableProducts item is an IEnumerable representing the ProductId's of all those previously selected and I know that when I select the manufacturer and it calls the GetCascadeProducts controller method it will return back a collection of ProductId and ProductName for all the manufacturers products of which those productId is the ApplicableProducts property should exist.
On document.ready I can call the InitialPopulate method with the manufacturerID which will populate the multiSelect items but can't seem to populate the initial values.
I couldnt get the binding working correctly so ended up using
#(Html.Kendo().MultiSelect()
.Name("ServiceBulletinItem_ApplicableProducts")
.AutoClose(false)
.DataTextField("ProductName")
.DataValueField("ProductId")
.Placeholder("Select products 2...")
.AutoBind(false)
)
and then on the using the following code on document ready to make an ajax call to populate the manufacturer and product controls
function PopulateProductsInitial(manId) {
$.post("#Url.Action("GetCascadeProducts", "Components")", { manufacturerId: manId }, function (result) {
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.setDataSource(result);
var s = $("#ServiceBulletinItem_Id").val();
$.post("#Url.Action("GetSBProducts", "ServiceBulletins")", { Id: s}, function (result) {
var arr = [];
result.forEach(function (element) {
arr.push(element.ProductId);
});
var grid = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect")
grid.value(arr);
});
});
}
}
$(document).ready(function () {
//Populate Initial Values
PopulateProductsInitial($("#ServiceBulletinItem_ManufacturerId").val());
$('#YourButton').click(SendForm);
});
The problem then became sending the selected items back to the controller when the edit was complete which again seemed convoluted because the control was not bound and therefore I had to make an Ajax call to submit the data.
function SendForm() {
var items = $("#ServiceBulletinItem_ApplicableProducts").data("kendoMultiSelect").value();
//Manipulate into ServiceBulletinViewModel for the save
var data = {
Id: $("#ServiceBulletinItem_Id").val(),
ServiceBulletinItem: {
Id: $("#ServiceBulletinItem_Id").val(),
ManufacturerId: $("#ServiceBulletinItem_ManufacturerId").val(),
IssueDate: $('#ServiceBulletinItem_IssueDate').val(),
Heading: $('#ServiceBulletinItem_Heading').val(),
Details: $('#ServiceBulletinItem_Details').val(),
Url: $('#ServiceBulletinItem_Url').val(),
SelectedProducts: items
}
}
$.ajax({
type: 'POST',
url: '/ServiceBulletins/Edit',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (result) {
//Your success code here..
if (result.redirectUrl != null) {
window.location = result.redirectUrl;
}
},
error: function (jqXHR) {
if (jqXHR.status === 200) {
alert("Value Not found");
}
}
});
}
It all seemed a lot more convoluted than any of the demo's that teleriks and I couldnt find any good examples of binding from remote sources which looked similar.
As the binding is convention based I'm wondering if its possible to simplify the ajax calling for the post functionality with the correct naming of the controls so that I can simply get the selected items on the multiselect control or if the ajax post is the way to go.

ReactJS Transitions - Why doesn't this work?

I'd like to transition one element as it changes to another element.
I've got 3 examples:
one that works, but uses a list of items that are kept around (jsfiddle)
one that doesnt work, and only keeps one item around, depending on the state (jsfiddle)
another one that doesn't work, that keeps both items around and hides/shows them (jsfiddle using hide/show)
What I want is more like the second one, which is a very slight variation of the first attempt that works.
Option 1:
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var TodoList = React.createClass({
getInitialState: function() {
return {items: ['hello', 'world', 'click', 'me']};
},
handleAdd: function() {
var newItems =
this.state.items.concat([prompt('Enter some text')]);
this.setState({items: newItems});
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1)
this.setState({items: newItems});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return (
<div key={item} onClick={this.handleRemove.bind(this, i)}>
{item}
</div>
);
}.bind(this));
return (
<div>
<div><button onClick={this.handleAdd} /></div>
<ReactTransitionGroup transitionName="example">
{items}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<TodoList />, document.body);
Option 2:
JSX that doesn't work, but is closer to what I'd like to do (really, hide one view, and show another)
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
if (this.state.showOne)
{
result = <div ref="a">One</div>
}
else
{
result = <div ref="a">Two</div>
}
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
{result}
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
Option 3:
Uses hide/show to keep the 2 views around, but still doesn't work.
/** #jsx React.DOM */
var ReactTransitionGroup = React.addons.TransitionGroup;
var Test = React.createClass({
getInitialState: function() {
return {showOne:true}
},
onClick: function() {
this.setState({showOne:! this.state.showOne});
},
render: function() {
var result;
var c1 = this.state.showOne ? "hide" : "show";
var c2 = this.state.showOne ? "show" : "hide";
return (
<div>
<div><button onClick={this.onClick}>switch state</button></div>
<ReactTransitionGroup transitionName="example">
<div className={c1}>One</div>
<div className={c2}>Two</div>
</ReactTransitionGroup>
</div>
);
}
});
var app = React.renderComponent(<Test />, document.body);
So long story short - How can I make a transition execute on switching from one main "component" to another? I don't get why option 1 works, but option 2 doesn't!
React is just changing the content of the DOM because that's all that changed. Give the elements unique keys to make them animate.
if (this.state.showOne)
{
result = <div key="one">One</div>
}
else
{
result = <div key="two">Two</div>
}
JSFiddle
I used Michelle Treys answer to solve a similar problem using React-Router (1.0.1). Its not clear from the api that the key is needed. I was following React-routers suggestion to render a routes children in a parent as follows:
render() {
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{this.props.children}
</ReactTransitionGroup>
</div>
);
}
However the componentWillEnter only triggered on page load. Following Michelle's solution, I cloned a the children as per the react-router updates and added a key as follows:
render() {
const { location } = this.props;
return (
<div id='app-wrapper'>
<ReactTransitionGroup component='div' className='transition-wrapper'>
{React.cloneElement(this.props.children, {
key: location.pathname,
})}
</ReactTransitionGroup>
</div>
);
}
Thanks for the fix. Cheers