Passing an array using Stencil JS, and it is not rendering on screen, but displays in the console - stenciljs

export class ArraySample {
#Prop({ mutable: true }) arraydata: string;
#State() _arraydata: { name: string; value: string }[] = [];
#State() userInput: string
componentWillLoad() {
this.parseOptions();
}
#Watch("arraydata")
parseOptions() {
if (this.arraydata) {
this._arraydata = JSON.parse(this.arraydata);
}
}
Here is the place where I am facing the issue. I am mapping the array, but not able to get the result.
render() {
return (
<Host>
<div>{this._arraydata.length}</div>
{this._arraydata.map((x) => {
<div>
<h1>{x.name}</h1>
</div>;
console.log(x.name);
})}
</Host>
);
}
}

I tried to use your code and pass an array to render the component. I made slight changes and it works. Hopefully you'll be able to find what is missed in your code, cuz it looks straight forward.
import {
Component,
Prop,
h,
State,
Watch,
Host
} from '#stencil/core';
#Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true,
})
export class MyComponent {
#Prop({
mutable: true
}) arraydata: string;
#State() _arraydata: Array < {
name: string;value: string
} > = [];
#State() userInput: string
componentWillLoad() {
this.parseOptions();
}
#Watch("arraydata")
parseOptions(): Array < string > {
if (this.arraydata) {
this._arraydata = JSON.parse(this.arraydata);
}
return []
}
render() {
return ( < Host > {
this._arraydata.length
} | {
this._arraydata.map(item => item)
} < /Host>)}
}
<my-component arrayData="[1,2,3,4,5]"></my-component>

Related

how can i make filter of tasks by name here?

<template lang="pug">
.kanban
.statuses
.todo(#drop="onDrop($event,'todo')"
#dragenter.prevent
#dragover.prevent)
span To do
tasks-order-by-status(:tasks = 'taskTodo')
.inprogress(#drop="onDrop($event,'inprogress')"
#dragenter.prevent
#dragover.prevent)
span In Progress
tasks-order-by-status(:tasks = 'taskInprog')
.done(#drop="onDrop($event,'done')"
#dragenter.prevent
#dragover.prevent)
span Done
tasks-order-by-status(:tasks = 'taskDone')
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue'
import { TaskStatusEnum } from './../enums/TaskStatusEnum'
import TasksOrderByStatus from '#/components/TasksOrderByStatus.vue'
import { useStore } from 'vuex'
import { emitter } from '../main'
import { TaskInterface } from '#/types/task.interface'
export default defineComponent({
setup () {
const store = useStore()
const tasks = computed(() => store.state.tasks)
return {
tasks
}
},
components: {
TasksOrderByStatus
},
data () {
return {
TaskStatusEnum,
showDetailsModal: false,
task: '',
taskTodo: [] as TaskInterface[],
taskInprog: [] as TaskInterface[],
taskDone: [] as TaskInterface[]
}
},
methods: {
setArrayByStatuses () {
for (let i = 0; i < this.tasks.length; i++) {
if (this.tasks[i].status === TaskStatusEnum.INPROGRESS) {
const item = this.tasks[i]
this.taskInprog.push(item)
}
if (this.tasks[i].status === TaskStatusEnum.TODO) {
const item = this.tasks[i]
this.taskTodo.push(item)
}
if (this.tasks[i].status === TaskStatusEnum.DONE) {
const item = this.tasks[i]
this.taskDone.push(item)
}
}
}
},
mounted () {
this.setArrayByStatuses()
}
})
</script>
how can i make filter of tasks by name here?
I tried to do this
computed: {
filteredList () {
return this.tasks.filter(task => {
return task.name.toLowerCase().includes(this.search.toLowerCase())
})
}
},
but i have this error Property 'tasks' does not exist on type 'CreateComponentPublicInstance<{ [x: string & on${string}]: ((...args: any[]) => any) | undefined; } | { [x: string & on${string}]: undefined; }, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, ... 10 more ..., {}>'.
Property 'tasks' does not exist on type '{ $: ComponentInternalInstance; $data: {}; $props: { [x: string & on${string}]: ((...args: any[]) => any) | undefined; } | { [x: string & on${string}]: undefined; }; ... 10 more ...; $watch(source: string | Function, cb: Function, options?: WatchOptions<...> | undefined): WatchStopHandle; } & ... 4 more ... & Co...'.
The setup() method does not work like you expect, it will not initiate data attributes (https://v3.vuejs.org/guide/composition-api-setup.html#accessing-component-properties)
Use mounted() instead and assign
this.tasks = tasks
also add tasks to your data object.

vue-pdf doesn't refresh on src change

I'm using the latest vue-pdf package to display pdf files in my app. I built this component, PdfViewer:
<template>
<div class="fill-height pdf-container">
<template v-if="src && numberOfPages">
<pdf
v-for="page in numberOfPages"
:key="`${fileName}-${page}`"
:src="src"
:page="page"
/>
</template>
</div>
</template>
import { mapGetters } from 'vuex'
import pdf from 'vue-pdf'
export default {
props: {
fileName: {
type: String,
required: true
}
},
components: {
pdf
},
data() {
return {
src: null,
numberOfPages: 0
}
},
computed: {
...mapGetters({
getAttachments: 'questions/getAttachments'
})
},
methods: {
init() {
if (this.fileName) {
let url = this.getAttachments[this.fileName]
let loadingTask = pdf.createLoadingTask(url)
this.src = loadingTask
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
},
},
watch: {
fileName() {
this.init()
}
},
beforeMount() {
this.init()
}
}
Basically I'm receiving a fileName as a prop, then look for its URL in the object I receive in getAttachments getter. The file names are in different list component.
It works fine on the first run and the first file is loaded and displayed successfully. But once clicked on another file name - nothing being displayed. I do receive the file name prop and it does find the URL, but the file doesn't display. Even when I click on the file that has already been displayed - now it doesn't.
I thought maybe it has something to do with src and numberOfPages property, so I tried to reset them before loading the file:
init() {
if (this.fileName) {
this.src = null
this.numberOfPages = 0
let url = this.getAttachments[this.fileName]
let loadingTask = pdf.createLoadingTask(url)
this.src = loadingTask
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
}
Alas, same result. And in the console I see the following warning from pdf.worker.js: Warning: TT: invalid function id: 9
Have no idea what it means.
Any help, please?
EDIT
I tried to do that with async/await and forceUpdate:
async init() {
if (this.fileName) {
this.src = null
this.numberOfPages = 0
let url = this.getAttachments[this.fileName]
let loadingTask = await pdf.createLoadingTask(url)
await loadingTask.promise.then(pdf => {
this.src = url
this.numberOfPages = pdf.numPages
})
this.$forceUpdate()
}
}
That also didn't help. But I found out that once I change the passed fileName, the code does go to the init() method, but for some reason it skips the loadingTask.promise.then part, doesn't go in. I have to idea why.
Well, apparently there's some issue with vue-pdf library. Eventually I solved it by setting timeout when assigning fileName prop and re-rendering the component:
<PdfViewer v-if="selectedFileName" :fileName="selectedFileName" />
onFileNameSelected(fileName) {
this.selectedFileName = null
setTimeout(() => {
this.selectedFileName = fileName
}, 0)
}
And then in the PdfViewer component it's just:
created() {
this.src = pdf.createLoadingTask(this.getAttachments[this.fileName])
},
mounted() {
this.src.promise.then(pdf => {
this.numberOfPages = pdf.numPages
})
}
That did the trick for me, though feels kinda hacky.

How to use ionic 4 search bar with *ngFor

I have build a page that use a search bar to filter through an *ngFor array. When I type in the search bar it behaves normally, but when I delete or back space text it does not update. It works normally if I pull an array from a static list from a data service but not with the data I am pulling from an ApolloQueryResult. Any help would be greatly appreciated.
html
<ion-content padding>
<div *ngIf="loading">Loading...</div>
<div *ngIf="error">Error loading data</div>
<ion-toolbar>
<ion-searchbar [(ngModel)]="searchTerm" (ionChange)="setFilteredItems()" showCancelButton="focus"></ion-searchbar>
</ion-toolbar>
<ion-card *ngFor="let data of info">
<ion-card-content>
{{data.TypeOfNotification}}
</ion-card-content>
</ion-card>
</ion-content>
ts
import { Component, OnInit } from '#angular/core';
import { Apollo } from 'apollo-angular';
import { ApolloQueryResult } from 'apollo-client';
import { QueryTodoService } from '../../services/query-todo.service';
import { Storage } from '#ionic/storage';
#Component({
selector: 'app-tab-to-do',
templateUrl: './tab-to-do.page.html',
styleUrls: ['./tab-to-do.page.scss'],
})
export class TabToDoPage implements OnInit {
info: any;
error: any;
loading: boolean;
searchTerm: string;
constructor(
private apollo: Apollo,
private queryTodoService: QueryTodoService,
private storage: Storage
) { }
setFilteredItems() {
this.info = this.filterItems(this.searchTerm);
}
filterItems(searchTerm){
return this.info.filter((item) => {
return item.TypeOfNotification.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1;
});
}
// or
setFilteredItemsAlt(event) {
const searchTerm = event.srcElement.value;
if (!searchTerm) {
return;
}
this.info = this.info.filter(item => {
if (item.TypeOfNotification && searchTerm) {
if (item.TypeOfNotification.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1) {
return true;
}
return false;
}
});
}
ngOnInit() {
this.storage.get('AccessToken').then((_token) => {
this.apollo.watchQuery({
query:this.queryTodoService.ToDoQuery,
fetchPolicy: 'cache-first',
})
.valueChanges.subscribe((result: ApolloQueryResult<any> ) => {
this.loading = result.loading;
this.info = result.data.notifications.Notifications;
console.log('first info', this.info );
this.error = result.errors;
});
});
}
}
It's because you are overwriting this.info every time you fire setFilteredItems():
setFilteredItems() {
//Overwrite this.info with new filtered data set.
this.info = this.filterItems(this.searchTerm);
}
The old values were filtered out and no longer exist - which is why *ngFor="let data of info" is not displaying them.
What you can do is set a new variable equal to this.info in your ts file - e.g. "dataDisplay":
dataDisplay: Array<object> = this.info;
Set this variable during an Ionic lifecycle change like ionViewWillEnter or whenever this.info gets set.
Then swap out the variable in setFilteredItems():
setFilteredItems() {
this.dataDisplay = this.filterItems(this.searchTerm);
}
Now change your *ngFor to the new variable:
*ngFor="let data of dataDisplay"
This should do the trick for you, because now filterItems(searchTerm) is always filtering the full, original this.info data set.

Vue: Functional component doesn't receive props

The component smart-list does it's job and is rendering the correct component.
It just doesn't pass on the props. I'd expect them to be in a context.data but it is undefined.
SmartList.vue
import EmptyList from "./EmptyList";
import FullList from "./FullList";
export default {
functional: true,
props: {
items: {
type: Array
}
},
render(h, { props, data, children }) {
if (props.items.length > 0) {
return h(FullList, data, children);
} else {
return h(EmptyList, data, children);
}
}
};
I have prepared a codesandbox example
What do I miss?
I have found the solution. In the smart-list component I've changed one line:
import EmptyList from "./EmptyList";
import FullList from "./FullList";
export default {
functional: true,
props: {
items: {
type: Array
}
},
render(h, { props, data, children }) {
if (props.items.length > 0) {
- return h(FullList, data, children);
+ return h(FullList, { attrs: props }, children);
} else {
return h(EmptyList, data, children);
}
}
};
Now it works.
Can someone point me why passing the full data object doesn't work? 🤔

Relay Moder - Pagination

I am already working on Pagination.
I used PaginationContainer for that. It work’s but no way what I am looking for.
I got button next which call props.relay.loadMore(2) function. So when I click on this button it will call query and add me 2 more items to list. It works like load more. But I would like instead of add these two new items to list, replace the old item with new.
I try to use this getFragmentVariables for modifying variables for reading from the store but it’s not working.
Have somebody Idea or implemented something similar before?
class QueuesBookingsList extends Component {
props: Props;
handleLoadMore = () => {
const { hasMore, isLoading, loadMore } = this.props.relay;
console.log('hasMore', hasMore());
if (!hasMore() || isLoading()) {
return;
}
this.setState({ isLoading });
loadMore(1, () => {
this.setState({ isLoading: false });
});
};
getItems = () => {
const edges = idx(this.props, _ => _.data.queuesBookings.edges) || [];
return edges.map(edge => edge && edge.node);
};
getItemUrl = ({ bid }: { bid: number }) => getDetailUrlWithId(BOOKING, bid);
render() {
return (
<div>
<button onClick={this.handleLoadMore}>TEST</button>
<GenericList
displayValue={'bid'}
items={this.getItems()}
itemUrl={this.getItemUrl}
emptyText="No matching booking found"
/>
</div>
);
}
}
export default createPaginationContainer(
QueuesBookingsList,
{
data: graphql`
fragment QueuesBookingsList_data on RootQuery {
queuesBookings(first: $count, after: $after, queueId: $queueId)
#connection(
key: "QueuesBookingsList_queuesBookings"
filters: ["queueId"]
) {
edges {
cursor
node {
id
bid
url
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
`,
},
{
direction: 'forward',
query: graphql`
query QueuesBookingsListQuery(
$count: Int!
$after: String
$queueId: ID
) {
...QueuesBookingsList_data
}
`,
getConnectionFromProps(props) {
return props.data && props.data.queuesBookings;
},
getFragmentVariables(prevVars, totalCount) {
console.log({ prevVars });
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, variables, fragmentVariables) {
return {
count: variables.count,
after: variables.cursor,
queueId: fragmentVariables.queueId,
};
},
},
);
As I figure out, there are two solutions, use refechConnection method for Pagination Container or use Refech Container.