Infinite scroll fetchmore - javascript

const MoreCommentsQuery = gql`
query MoreComments($cursor: String) {
moreComments(cursor: $cursor) {
cursor
comments {
author
text
}
}
}
`;
const CommentsWithData = () => (
<Query query={CommentsQuery}>
{({ data: { comments, cursor }, loading, fetchMore }) => (
<Comments
entries={comments || []}
onLoadMore={() =>
fetchMore({
// note this is a different query than the one used in the
// Query component
query: MoreCommentsQuery,
variables: { cursor: cursor },
updateQuery: (previousResult, { fetchMoreResult }) => {
const previousEntry = previousResult.entry;
const newComments = fetchMoreResult.moreComments.comments;
const newCursor = fetchMoreResult.moreComments.cursor;
return {
// By returning `cursor` here, we update the `fetchMore` function
// to the new cursor.
cursor: newCursor,
entry: {
// Put the new comments in the front of the list
comments: [...newComments, ...previousEntry.comments]
}
};
}
})
}
/>
)}
</Query>
);
This is from the documentation so i cant show the code for the comments query but when executing the CommentsQuery, where does this cursor come from, is it literally just returning the argument we passed in or does the cursor field have to exist on MoreComments Query?

Related

merge previous data while scrolling on relay pagination graphql

I am trying to use relay style pagination. However, I am getting trouble on infinite scrolling. When i scroll or load next sets of data then I just get the current data without it being merged to the previous data. This is how I have done
cache.ts
import { InMemoryCache } from '#apollo/client';
import { relayStylePagination } from '#apollo/client/utilities';
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
conversation: relayStylePagination(),
},
},
},
});
export default cache;
Conversation Query
In my case, params like first, after, before, last are inside params object
export const CONVERSATION = gql`
query conversation($channel: ShortId, $contact: ShortId, $params: ConnectionInput) {
conversation(channel: $channel, contact: $contact, params: $params) {
status
data {
pageInfo {
...PageInfo
}
edges {
cursor
node {
...Communication
}
}
}
}
}
${PAGE_INFO}
${COMMUNICATION}
`;
Conversation.tsx
const [loadConversation, { data, fetchMore, networkStatus, subscribeToMore }] = useLazyQuery(
CONVERSATION,
);
useEffect(() => {
isMounted.current = true;
if (channelId && contactId) {
loadConversation({
variables: {
channel: channelId,
contact: contactId,
params: { first },
},
});
}
return () => {
isMounted.current = false;
};
}, [channelId, contactId, loadConversation]);
<React.Suspense fallback={<Spinner />}>
<MessageList messages={messages ? generateChatMessages(messages) : []} />
{hasNextPage && (
<>
<button
type='button'
ref={setButtonRef}
id='buttonLoadMore'
disabled={isRefetching}
onClick={() => {
if (fetchMore) {
fetchMore({
variables: {
params: {
first,
after: data?.conversation?.data?.pageInfo.endCursor,
},
},
});
}
}}
/>
</>
)}
</React.Suspense>
Can I know what I have missed?
The first, after, before, last should be declared as arguments of conversation rather than as properties of params.
Apollo merges the previous pages when the query arguments contain after/before .
query conversation($channel: ShortId, $contact: ShortId, $after: String, $first: Int, $before: String, $last: Int) {
conversation(channel: $channel, contact: $contact, after: $after, first: $first, before: $before, last: $last) {
...
}
}

how to using a custom function to generate suggestions in fluent ui tag picker

I am trying to use the tagPicker from fluent ui. I am using as starting point the sample from the site:
https://developer.microsoft.com/en-us/fluentui#/controls/web/pickers
The problem is that the object I have has 3 props. the objects in the array are {Code:'string', Title:'string', Category:'string'}. I am using a state with a useeffect to get the data. SO far works fine, the problem is that the suggestion are rendered blank. It filter the items but does not show the prop I want.
Here is my code:
import * as React from 'react';
import {
TagPicker,
IBasePicker,
ITag,
IInputProps,
IBasePickerSuggestionsProps,
} from 'office-ui-fabric-react/lib/Pickers';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
const inputProps: IInputProps = {
onBlur: (ev: React.FocusEvent<HTMLInputElement>) => console.log('onBlur called'),
onFocus: (ev: React.FocusEvent<HTMLInputElement>) => console.log('onFocus called'),
'aria-label': 'Tag picker',
};
const pickerSuggestionsProps: IBasePickerSuggestionsProps = {
suggestionsHeaderText: 'Suggested tags',
noResultsFoundText: 'No color tags found',
};
const url="url_data"
export const TestPicker: React.FunctionComponent = () => {
const getTextFromItem = (item) => item.Code;
const [state, setStateObj] = React.useState({items:[],isLoading:true})
// All pickers extend from BasePicker specifying the item type.
React.useEffect(()=>{
if (!state.isLoading) {
return
} else {
caches.open('cache')
.then(async cache=> {
return cache.match(url);
})
.then(async data=>{
return await data.text()
})
.then(data=>{
const state = JSON.parse(data).data
setStateObj({items:state,isLoading:false})
})
}
},[state.isLoading])
const filterSuggestedTags = (filterText: string, tagList: ITag[]): ITag[] => {
return filterText
? state.items.filter(
tag => tag.Code.toLowerCase().indexOf(filterText.toLowerCase()) === 0 && !listContainsTagList(tag, tagList),
).slice(0,11) : [];
};
const listContainsTagList = (tag, state?) => {
if (!state.items || !state.items.length || state.items.length === 0) {
return false;
}
return state.items.some(compareTag => compareTag.key === tag.key);
};
return (
<div>
Filter items in suggestions: This picker will filter added items from the search suggestions.
<TagPicker
removeButtonAriaLabel="Remove"
onResolveSuggestions={filterSuggestedTags}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
itemLimit={1}
inputProps={inputProps}
/>
</div>
);
};
I just got it, I need to map the items to follow the {key, name} from the sample. Now it works.
setStateObj({items:state.map(item => ({ key: item, name: item.Code })),isLoading:false})

How and when to check if all object values in an array are not null?

I have a React component with multiple dynamic input fields with different data types. I want to save the input values in state (answers) like this:
{ [id]: value }
Example of possible data output:
[
{
'72ebbdc4-8001-4b53-aac0': 'John doe'
},
{
'dd3179c1-90bc-481c-a89e':
'5b6d2f55-8ed0-4f76-98e69'
},
{
'5acff3c7-02f8-4555-9232': 4
},
{
'877817a8-6890-464b-928e': false
},
{
'69e11e5a-613f-46ac-805d': []
},
{
'0bb9c2f3-eda7-4e96-90f6': [
'ad9d4c72-0972764cf9b71c42',
'da788b55-3b68-a9c669c0ec1a'
]
},
{
'e9c2196f-871f-25e6efb2551f': '2020-12-23'
},
];
My React component is as follows. The InputField is a switch based on the questions type. When an input changes updateState is called and this.state.answers is updated. All of the question need to be filled in before the users can navigate to the next screen -> this.state.answeredAllQuestions.
export default class EditComponent extends Component {
state = {
questions: [],
answers: [],
answeredAllQuestions: false
};
async componentDidMount() {
await this.fillQuestions();
}
// I think need a working alternative for this part
componentDidUpdate() {
if (!this.state.answeredAllQuestions) {
this.checkRequiredQuestions();
}
}
fillQuestions = async () => {
const {
response: { questions }
} = await getQuestions();
// Turn questions from api into answers -> [key]:value
const answers = questions.map(el => {
return { [el.uuid]: el.value };
});
this.setState({
questions,
answers
});
};
checkRequiredQuestions = async () => {
const { answers } = this.state;
if (answers) {
const values = answers.map(x => Object.values(x)[0]);
if (
values.every(answer => {
(answer.required && answer !== null) || answer !== '';
})
) {
this.setState({ answeredAllQuestions: true });
} else {
this.setState({ answeredAllQuestions: false });
}
}
};
updateState = (value, id, nestedId) => {
const { answers } = this.state;
if (answers) {
// Check if answer already exists in the state, if so then replace it
this.setState({
answers: this.state.answers.map(el =>
Object.keys(el)[0] === id ? { [id]: value } : el
)
});
} else {
this.setState({
answers: [{ [id]: value }]
});
}
};
render() {
const { questions, answers } = this.state;
return (
<View>
<FlatList
data={questions}
renderItem={({ item: question }) => (
<View key={question.id}>
<Text>{question.label}</Text>
<InputField
type={question.type}
answers={answers}
updateState={this.updateState}
question={question}
/>
</View>
)}
/>
</View>
);
}
}
The big problem I have with this code is that when all input fields are filled in, the this.state.answeredAllQuestions is set too true. But when the user then removes a value from an input field it won't update back to false.
I don't expect someone to fix my code, but I could really use some help at the moment.
if (values.every(answer =>
(answer.required && (answer !== null || answer !== '' )) || answer === ''))
If the answer is required you need to check if isn't an empty string.
Fixed by passing a HandleInput function to all the inputs components, that checks for every data type if true or false and puts this value in the state of the EditComponent.

How can I compose query fragments with variables

I am using Relay and react-router-relay and am trying to compose several Relay containers. The inner container needs a query fragment variable that must be passed from the router down through its parent containers. It's not getting this variable.
Here's how the code looks:
// A "viewer":
const UserQueries = { user: () => Relay.QL`query { user }` };
// A route to compose a message:
<Route
path='/compose/:id'
component={ ComposeContainer }
queries={ UserQueries }
/>
// A container:
const ComposeContainer = Relay.createContainer(
Compose,
{
initialVariables: {
id: null
},
fragments: {
user: () => Relay.QL`
fragment on User {
${ BodyContainer.getFragment('user') }
// other fragments
}
`
}
}
);
// And the composed container:
const BodyContainer = React.createContainer(
Body,
{
initialVariables: {
id: null
},
fragments: {
user: () => Relay.QL`
fragment on User {
draft(id: $id) {
// fields
}
}
`
}
}
);
The draft field inside BodyContainer never gets $id from the route param. The signature to RelayContainer.getFragment() has arguments that seem to let you pass params and variables, but I am not sure how this should be used.
Your user fragment on <ComposeContainer> has to be something like:
user: ({ id }) => Relay.QL`
fragment on User {
${BodyContainer.getFragment('user', { id })}
// other fragments
}
`
Additionally, when you compose in <BodyContainer> from <ComposeContainer>. You'll also need to pass in id as a prop, e.g.
<BodyContainer id={this.props.id} />
See also additional discussion at https://github.com/facebook/relay/issues/309.

Why the results don't disappear when I delete too fast the input in the search field? (Javascript)

I have an input field that maps the state and shows me the results on change:
if (this.state.results === "" || this.state.results === null || this.state.results === undefined){
showResults = null;
} else {
let results = this.state.results.map(result => (
<StyledLink key={result.name} to={`/game/${result.name}`}>
<SearchResult
name={result.name}
/>
</StyledLink>)
)
showResults = <StyledResults>{results}</StyledResults>
}
Everything works fine, but when I press the delete button to fast, even If the input field is empty it still shows results. It doesn't happen if I wait half a second beetween every delete press.
And this is what is what the input field triggers onChange:
findGames = (event) => {
let searchText = event.target.value
if (event.target.value) {
let body = {
"search_text": `${searchText}`, "fields": ["id", "name", "release_dates"]
}
axios.post(getGameIDUrl, body, headers)
.then(res => res.data.filter((result) => { return result.type === "game" }))
.then(res => this.setState({ results: res }))
.then(console.log(this.state.results))
} else {
this.setState({ results: null })
}
}
you have to cancel previous axios.post request before sending another one.
In case when you delete the search term completely, this.setState({ results: null }) is called immediately, but there are previous requests still on their way and when they come back, they set the state with undesired results.
Here's an example how to cancel a request:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
You need to cancel the request or else you will be setting state whenever the request is returned.
Initializing and reseting state to an empty array [] instead of null may also help
const CancelToken = axios.CancelToken;
let cancel;
class SomeComponent extends React.Component {
state = {
values:'',
results:[]
}
findGames = (event) => {
if(cancel){
cancel()
console.log('request canceled')
}
setState({ value:event.target.value, results:[]})
if(event.target.value === ''){
return null
}
const getGameIDUrl = 'www.someapi.com/api'
const body = { "search_text": `${event.target.value}`, "fields": ["id", "name", "release_dates"]}
const headers = {Authorization:'bearer API_TOKEN'}
axios.post(
getGameIDUrl,
body,
{cancelToken: new CancelToken((c)=> cancel = c)},
headers
).then(res =>{
const results = res.data.filter((result) => result.type === "game" )
this.setState({ results })
})
}
render(){
console.log(this.state.results)
return (
<div>
<input onChange={this.findGames} value={this.state.value} />
<StyledResults>
{this.state.results.map(result => (
<StyledLink key={result.name} to={`/game/${result.name}`}>
<SearchResult
name={result.name}
/>
</StyledLink>
))}
</StyledResults>
</div>
)
}
}

Categories

Resources