Nextjs useState delays when type CKeditor5 input - react-hooks

Created ckeditor 5 component separately and included other component
import React from "react";
import EditorCustom from "ckeditor5-custom-build/build/ckeditor";
import { CKEditor } from "#ckeditor/ckeditor5-react";
import { fileStorage } from "../../../firebase/firebase.config";
import { ref, uploadBytes, getDownloadURL } from "firebase/storage";
class MyUploadAdapter {
constructor(loader) {
this.loader = loader;
}
// Starts the upload process.
upload() {
return this.loader.file.then(
(file) =>
new Promise((resolve, reject) => {
let storage = fileStorage;
uploadBytes(ref(storage, file.name), file)
.then((snapshot) => {
return getDownloadURL(snapshot.ref);
})
.then((downloadURL) => {
resolve({
default: downloadURL,
});
})
.catch((error) => {
reject(error.message);
});
})
);
}
}
const editorConfiguration = {
ui: {
viewportOffset: {
top: 56,
},
},
toolbar: {
items: [
"undo",
"redo",
"|",
"heading",
"fontFamily",
"fontSize",
"fontBackgroundColor",
"fontColor",
"highlight",
"bulletedList",
"numberedList",
"todoList",
"|",
"imageUpload",
"imageInsert",
"insertTable",
"mediaEmbed",
"link",
"pageBreak",
"-",
"outdent",
"indent",
"|",
"blockQuote",
"bold",
"italic",
"underline",
"strikethrough",
"horizontalLine",
],
shouldNotGroupWhenFull: true,
},
language: "en",
image: {
toolbar: [
"imageTextAlternative",
"toggleImageCaption",
"imageStyle:inline",
"imageStyle:block",
"imageStyle:side",
"linkImage",
],
},
table: {
contentToolbar: [
"tableColumn",
"tableRow",
"mergeTableCells",
"tableCellProperties",
"tableProperties",
],
},
};
const Editor = ({ value, onChange }) => {
return (
<>
<CKEditor
editor={EditorCustom}
config={editorConfiguration}
onReady={(editor) => {
editor.plugins.get("FileRepository").createUploadAdapter = (
loader
) => {
return new MyUploadAdapter(loader);
};
}}
data={value}
onChange={(event, editor) => {
const data = editor.getData();
onChange(data);
}}
/>
</>
);
};
export default Editor;
but when i include component in create.js other default inputs delay and ckeditor input delay too, if not use useState ckeditor working fine
import { useEffect, useState } from "react";
import dynamic from "next/dynamic";
import { database } from "../../../firebase/firebase.config";
import { collection, addDoc } from "firebase/firestore";
const CreateBlog = () => {
const Editor = dynamic(() => import("../../dashboard/utils/myEditor"), {
ssr: false,
});
const [title, setTitle] = useState("");
const [subTitle, setSubTitle] = useState("");
const [message, setMessage] = useState("");
const [body, setBody] = useState("");
const databaseRef = collection(database, "blogs");
const addData = () => {
addDoc(databaseRef, {
title: title,
sub_title: subTitle,
message: message,
})
.then(() => {
alert("Data Sent");
// getData();
setTitle("");
setSubTitle("");
setMessage("");
})
.catch((err) => {
console.error(err);
});
};
return (
<>
<div className="w-full px-4">
<div className="w-full p-8 mt-10 bg-white rounded-lg shadow-md lg:w-3/3 md:w-2/2 md:ml-auto md:mt-0">
<h2 className="mb-1 text-lg font-medium text-gray-900 title-font">
Create Post
</h2>
<div className="mb-4 ">
<label htmlFor="titile" className="text-sm leading-7 text-gray-600">
Title
</label>
<input
type="title"
id="title"
name="title"
value={title}
onChange={(event) => setTitle(event.target.value)}
className="w-full px-3 py-1 text-base leading-8 text-gray-700 transition-colors duration-200 ease-in-out bg-white border border-gray-300 rounded outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
/>
</div>
<div className="mb-4">
<label htmlFor="titile" className="text-sm leading-7 text-gray-600">
Sub Title
</label>
<input
type="title"
id="title"
name="title"
value={subTitle}
onChange={(event) => setSubTitle(event.target.value)}
className="w-full px-3 py-1 text-base leading-8 text-gray-700 transition-colors duration-200 ease-in-out bg-white border border-gray-300 rounded outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
/>
</div>
<div className="relative mb-4">
<label
htmlFor="message"
className="text-sm leading-7 text-gray-600"
>
Message
</label>
<textarea
id="message"
name="message"
value={message}
onChange={(event) => setMessage(event.target.value)}
className="w-full h-32 px-3 py-1 text-base leading-6 text-gray-700 transition-colors duration-200 ease-in-out bg-white border border-gray-300 rounded outline-none resize-none focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
></textarea>
</div>
<div className="mb-4 ">
<label htmlFor="body" className="text-sm leading-7 text-gray-600">
Body
</label>
<Editor value={body} onChange={(value) => setBody(value)} />
</div>
<button
onClick={addData}
className="px-6 py-2 text-lg text-white bg-blue-500 border-0 rounded focus:outline-none hover:bg-blue-600"
>
Button
</button>
</div>
</div>
</>
);
};
export default CreateBlog;
<Editor value={body} onChange={(value) => setBody(value)} />
used useState but its delay and slow breaking input type
how to use ckeditor with other inputs and value set useState without dealy

Related

React props not passing dropdown values into parent component

I've got a NextJS 13 application which is using the Tailwind CSS framework to style the UI. The app is using the Boardgame Atlas API to fetch a board game for the user based on a few dropdown list items (category, player count, and spend).
The Home component is the main component of the app and is responsible for fetching the board game from the API and rendering the UI. It uses three other components, CategoriesSelect, PlayerCount, and SpendSelect to render the dropdown lists.
The CategoriesSelect component fetches the available categories from the API and renders a dropdown list to allow the user to select the category. The PlayerCount component renders a dropdown list to allow the user to select the minimum number of players needed for the game. The SpendSelect component renders a dropdown list to allow the user to select their budget.
Once the user selects the values from the dropdown lists, the fetchOutput function is called on the click of the "Find my game" button. This function makes a GET request to the API using the selected values, and the API returns the matching games. If there are any games found, the first game in the array is logged to the console, otherwise an error message is logged.
The code can be found on GitHub here, and a live version is available on Vercel here. I've also pasted the code below for convenience:
page.js (HOME)
"use client"
import { useState, useEffect } from 'react';
import CategoriesSelect from "#/components/CategoriesSelect";
import PlayerCount from "#/components/PlayerCount";
import SpendSelect from "#/components/SpendSelect";
import axios from 'axios';
export default function Home() {
const[formData, setFormData] = useState({
category: "",
playerCount: 1,
spend: 25,
})
const fetchOutput = async () => {
console.log(formData)
try {
const { data } = await axios.get(
`https://api.boardgameatlas.com/api/search?categories=${formData.category}&min_players=${formData.playerCount}&lt_price=${formData.spend}&client_id=EsdgqvppMg`
);
if (data.games && data.games.length > 0) {
console.log(data.games[0]);
} else {
console.error('No games found');
}
} catch (error) {
console.error(error);
}
};
// useEffect(() => {
// console.log(formData)
// }, [formData])
return (
<main >
<div className="text-center mt-24 mb-16 text-4xl font-mono">looking for your next boardgame?</div>
<div className="text-center font-mono px-10 mb-4">
<span>I want to play a(n) </span>
<CategoriesSelect
formData={formData.category}
setFormData={setFormData} />
<span> game with at least </span>
<PlayerCount
formData={formData.playerCount}
setFormData={setFormData}
/>
<span> other player(s). My budget is up to: </span>
<SpendSelect
formData={formData.spend}
setFormData={setFormData}
/>
<span> USD</span>
</div>
<div className="flex">
<button type="button"
onClick={fetchOutput}
className="mx-auto px-6 py-2.5 bg-blue-600 text-white font-medium leading-tight rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out text-xl">🎲 Find my game 🎰</button>
</div>
</main>
)
}
CategoriesSelect.jsx
import { useState, useEffect } from 'react';
import axios from 'axios';
const CategoriesSelect = ({formData, setFormData}) => {
const [categories, setCategories] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://api.boardgameatlas.com/api/game/categories?client_id=EsdgqvppMg'
);
setCategories(result.data.categories);
};
fetchData();
}, []);
return (
<div className='inline mb-3 xl:w-96'>
<select className='form-select appearance-none
px-3
py-1.5
text-base
font-normal
text-gray-700
bg-white bg-clip-padding bg-no-repeat
border border-solid border-gray-300
rounded
transition
text-center
ease-in-out
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none" aria-label="Default select example'
value={formData}
onChange={e => setFormData({...formData, category: e.target.value})}
>
{categories.map(category => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</div>
);
};
export default CategoriesSelect;
PlayerCount.jsx
"use client"
export default function PlayerCount({formData, setFormData}) {
const options = [1, 2, 3, 4, 5, 6, 7].map(number => (
<option key={number} value={number}>
{number}
</option>
));
return (
<div className='inline mb-3 xl:w-96'>
<select
value={formData}
onChange={e => setFormData({...formData, playerCount: e.target.value})}
className='form-select appearance-none
px-7
py-1.5
text-base
font-normal
text-gray-700
bg-white bg-clip-padding bg-no-repeat
border border-solid border-gray-300
rounded
transition
ease-in-out
m-0
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
aria-label='Default select example'
>
{options}
</select>
</div> )
}
SpendSelect.jsx
"use client"
export default function SpendSelect({formData, setFormData}) {
const options = [10, 25, 50, 75, 100, 200, 300 ].map(number => (
<option key={number} value={number}>
{number}
</option>
));
return (
<div className='inline mb-3 xl:w-96'>
<select
value={formData}
onChange={e => setFormData({...formData, spend: e.target.value})}
className='form-select appearance-none
px-7
py-1.5
text-base
font-normal
text-gray-700
bg-white bg-clip-padding bg-no-repeat
border border-solid border-gray-300
rounded
transition
ease-in-out
m-0
focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none'
aria-label='Default select example'
>
{options}
</select>
</div> )
}
My challenge:
I'm having trouble updating my formData's state in my Home component from child components. The data.games.length is returning 0 even after updating the props passed from the child components. Can anyone help me with the proper way to update the formData state from its child components?
Fixed! Answer:
before:
onChange={e => setFormData({...formData, playerCount: e.target.value})}
after:
onChange={e => setFormData(prevFormData => ({...prevFormData, playerCount: e.target.value}))}
That should be applied to all components using onChange. It seems that I was spreading a prop instead of an object array.

rendering hyperlink from the rich text content from graph CMS

Hi there I am not able to load link from graphCMS pls help
trying to render hyperlink content from the graphCMS post but it is not appearing on the post details page on blog.
here is my code
import React from "react";
import moment from "moment";
const PostDetail = ({ post }) => {
const getContentFragment = (index, text, obj, type) => {
let modifiedText = text;
if (obj) {
if (obj.bold) {
modifiedText = <b key={index}>{text}</b>;
}
if (obj.italic) {
modifiedText = <em key={index}>{text}</em>;
}
if (obj.underline) {
modifiedText = <u key={index}>{text}</u>;
}
}
switch (type) {
case "heading-three":
return (
<h3 key={index} className="text-xl font-semibold mb-4">
{modifiedText.map((item, i) => (
<React.Fragment key={i}>{item}</React.Fragment>
))}
</h3>
);
case "paragraph":
return (
<p key={index} className="mb-8">
{modifiedText.map((item, i) => (
<React.Fragment key={i}>{item}</React.Fragment>
))}
</p>
);
case "heading-four":
return (
<h4 key={index} className="text-md font-semibold mb-4">
{modifiedText.map((item, i) => (
<React.Fragment key={i}>{item}</React.Fragment>
))}
</h4>
);
case "image":
return (
<img
key={index}
alt={obj.title}
height={obj.height}
width={obj.width}
src={obj.src}
/>
);
case "link":
return (
<a {...index} href={obj.url}>
{modifiedText.map((item, i) => (
<React.Fragment key={i}>{item}</React.Fragment>
))}
</a>
);
default:
return modifiedText;
}
};
return (
<div className="bg-white shadow-lg rounded-lg lg:p-8 pb-12 mb-8">
<div className="relative overflow-hidden shadow-md mb-6">
<img
src={post.featuredImage.url}
alt={post.title}
className="objecy-top h-full w-full rounded-t-lg"
/>
</div>
<div className="px-4 lg:px-0">
<div className="flex items-center mb-8 w-full">
<div className="flex items-center mb-4 lg:mb-0 w-full lg:w-auto mr-8">
<img
className="align-middle rounded-full"
src={post.author.photo.url}
alt={post.author.name}
height="30px"
width="30px"
/>
<p className="inline align-middle text-gray-700 ml-2 text-lg font-medium">
{post.author.name}
</p>
</div>
<div className="font-medium text-gray-700">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 inline mr-2 text-pink-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span className="align-middle">
{moment(post.createdAt).format("MMM DD, YYYY")}
</span>
</div>
</div>
<h1 className="mb-8 text-3xl font-semibold">{post.title}</h1>
{post.content.raw.children.map((typeObj, index) => {
const children = typeObj.children.map((item, itemindex) =>
getContentFragment(itemindex, item.text, item)
);
return getContentFragment(index, children, typeObj, typeObj.type);
})}
</div>
</div>
);
};
export default PostDetail;
everything other than link is appearing on the page but not hyperlink content from the graphcms
thanks in advance !
If you console.log raw data you can see that the 'link' types are contained in 'paragraph' types, so the code will never enter the case 'link'.
GraphCMS provide a lib to easyly render Rich Text. May be a good option for what you want.
What you have done here is map through the modifiedText array, but hyperlinks are not stored in that same way as the text.
Your code should look something like this:
case "link":
return (
<a key={index} href={obj.href} className='text-md text-blue-700'>
<React.Fragment>{obj.title}</React.Fragment>
</a>
);
Added some styling there to give the link a blue color.

show editable field when empty

I have this problem: I have a component with which I create editable fields.
If the field is already populated, everything works fine. I see the database value, I can click on it and change it.
If, on the other hand, in the database that value is empty, it is "hidden" in the view. In this way it is not possible to click on it to insert a value in the editable field.
I tried to get around the obstacle by inserting a: placeholder = "placeholder" but I don't even see that.
How can I do?
This is my visualization file:
<div class="py-0 sm:grid sm:grid-cols-10 sm:gap-4 my-2">
<dt class="text-md leading-6 font-medium text-gray-900 sm:col-span-2 self-center">
{{ $trans('labels.description') }}
</dt>
</div>
<div class="py-1 sm:grid sm:grid-cols-10 sm:gap-4">
<dd class="text-sm leading-5 text-gray-600 sm:mt-0 sm:col-span-10 self-center">
<v-input-text-editable
v-model="task.description"
#input="updateTask()"
:placeholder = "placeholder"
/>
</dd>
</div>
props: {
users: {
type: Array,
default: []
},
description: {
type: String,
default: null
}
},
data() {
return {
task: new Form({
description: this.description
})
}
},
this is my component:
<template>
<div class="block w-full">
<div v-if="!editable" class="cursor-pointer" #click="enableEditMode()">
{{ valueAfterEdit }}
</div>
<div v-else class="mt-1 flex rounded-md shadow-sm" v-click-outside="handleClickOutside">
<div class="relative flex-grow focus-within:z-10">
<input #keyup.enter="commitChanges()" class="form-input block w-full rounded-none rounded-l-md transition ease-in-out duration-150 sm:text-sm sm:leading-5" v-model="valueAfterEdit" ref="input"/>
</div>
<span class="btn-group">
<button #click="discardChanges()" type="button" class="btn btn-white rounded-l-none border-l-0">
<svg class="h-4 w-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z"/>
</svg>
</button>
<button #click="commitChanges()" type="button" class="btn btn-white">
<svg class="h-4 w-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M0 11l2-2 5 5L18 3l2 2L7 18z"/>
</svg>
</button>
</span>
</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: null
},
allowEmpty: {
type: Boolean,
default: false
}
},
data() {
return {
editable: false,
valueBeforeEdit: this.value,
valueAfterEdit: this.value
}
},
watch: {
value(val) {
this.valueBeforeEdit = val;
}
},
methods: {
handleClickOutside() {
this.disableEditMode();
this.commitChanges();
},
enableEditMode() {
this.editable = true;
this.$emit('edit-enabled');
this.$nextTick(() => {
this.$refs.input.focus();
});
},
disableEditMode() {
this.editable = false;
this.$emit('edit-disabled');
},
commitChanges() {
if (!this.allowEmpty && this.valueAfterEdit !== '.') {
this.$emit('input', this.valueAfterEdit);
this.disableEditMode();
}
},
discardChanges() {
this.valueAfterEdit = this.valueBeforeEdit;
this.disableEditMode();
}
}
}
</script>

VueJS - Add lazy loading to component v-for

I want to increase the loading times on my website. I currently have a list of all my projects and all the projects load on the first pageload. I want to implement lazy loading to those components but I can't seem to find a way to do it.
I have a component called project-card:
<project-card v-bind:project="project" v-bind:key="project.id" v-if="projects" v-for="project in filteredProjects"></project-card>
The template file:
<template>
<div class="project-card rounded dark:bg-gray-800 bg-white overflow-hidden shadow-lg rounded-lg flex flex-col relative">
<img class="w-full h-64 object-cover" :src="'/uploads/' + project.image" alt="">
<div class="px-6 py-4 h-full flex flex-col">
<div class="project-info block min-h-8">
<div class="w-full">
<p class="tracking-widest text-xs title-font font-medium dark:text-white text-gray-500 mb-1">{{ project.language }}</p>
</div>
</div>
<div class="project-language flex-1">
<div class="w-5/6 float-left">
<p class="font-bold dark:text-white gilroy text-xl">{{ project.name }}</p>
</div>
</div>
<div class="project-description flex-2 space-y-4 py-3 h-full">
<p class="dark:text-white ">{{ project.description | str_limit(128) }}</p>
</div>
<div class="read-more mt-auto">
<div class="flex items-center flex-wrap ">
<button type="button" #click="openModal" class="font-bold text-sm text-indigo-600 hover:text-indigo-500 transition duration-150 ease-in-out hover:text-indigo-900 read-more-button flex items-center focus:outline-none">
Lees meer <span class="read-more-arrow ml-2">→</span>
</button>
<span class="text-gray-600 mr-3 inline-flex items-center lg:ml-auto md:ml-0 ml-auto leading-none text-sm pr-3 py-1 border-r-2 border-gray-300">
</span>
<span class="text-gray-600 inline-flex items-center leading-none text-sm">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4 mr-1">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
{{ project.stargazers_count }}
</span>
</div>
<project-card-modal :project="project" ref="projectModal"></project-card-modal>
</div>
</div>
</div>
</template>
<script>
import projectCardModalComponent from "./projectCardModalComponent";
export default {
components: {
projectCardModalComponent
},
methods: {
openModal() {
this.$refs.projectModal.show = true;
}
},
props: ['project']
}
</script>
Currently It is looking something like this, so it gives an idea of how it is supposed to turn out. But I want to implement lazy loading to this component. How can I achieve that?
APP.JS
import Vue from "vue";
require('./bootstrap');
window.Vue = require('vue');
Vue.component('project-card', require('./components/projectCardComponent.vue').default);
Vue.component('project-card-modal', require('./components/projectCardModalComponent.vue').default);
Vue.component('contact-form', require('./components/contactFormComponent.vue').default);
Vue.component('contact-form-flash', require('./components/contactFormFlashComponent.vue').default);
Vue.component('project-language-filter-button', require('./components/projectLanguageButtonFilterComponent.vue').default);
Vue.component('project-language-filter-dropdown', require('./components/projectLanguageDropdownFilterComponent.vue').default);
Vue.component('education', require('./components/educationComponent.vue').default);
Vue.component('work-experience', require('./components/workExperienceComponent.vue').default);
Vue.component('sidebar', require('./components/dashboard/dashboardSidebarComponent.vue').default);
Vue.component('projects', require('./components/dashboard/dashboardProjectsComponent.vue').default);
import MugenScroll from 'vue-mugen-scroll'
const app = new Vue({
el: '#app',
data: {
showModal: false,
projects: [],
filteredProjects: [],
languages: [],
languageFilterKey: 'all',
workExperiences: [],
educations: [],
search: '',
pageSizeBegin: 0,
pageSizeEnd: null,
projectsLoading: false,
},
mounted: function() {
this.getProjects();
this.getWorkExperiences();
this.getAllEducations();
this.getProjectLanguages();
},
created() {
this.getFilteredProjects();
},
methods: {
getProjects: function() {
axios.get('/api/get/projects')
.then(response => {
this.filteredProjects = this.projects = response.data
}).catch(err => {
console.log(err)
});
},
getFilteredProjects: function() {
for(let i = 0; i < 6; i++) {
let count = this.filteredProjects.length + i
}
},
getProjectLanguages: function() {
axios.get('/api/get/project/languages')
.then(response => {
this.languages = response.data
}).catch(err => {
console.log(err)
});
},
selectedLanguage: function() {
if(this.languageFilterKey !== null) {
this.languages.forEach((item) => {
item.active = item.language === this.languageFilterKey;
});
} else {
this.languageFilterKey = null
}
},
filterProjectsByLanguage () {
if(this.languageFilterKey === 'all') {
this.filteredProjects = this.projects;
} else {
this.filteredProjects = this.projects.filter((project) => {
return this.languageFilterKey === project.language
});
}
},
getWorkExperiences: function() {
axios.get('/api/get/workexperiences')
.then(response => {
this.workExperiences = response.data
}).catch(err => {
console.log(err)
});
},
getAllEducations: function() {
axios.get('/api/get/educations')
.then(response => {
this.educations = response.data
}).catch(err => {
console.log(err)
});
},
amountOnChange(event) {
if(!event.target.value) {
this.pageSizeEnd = null;
} else {
this.pageSizeEnd = event.target.value;
}
}
},
computed: {
filteredList() {
if(!this.pageSizeEnd) {
return this.projects.filter((project) => {
return project.name.toLowerCase().includes(this.search.toLowerCase()) || project.language.toLowerCase().includes(this.search.toLowerCase())
})
}
return this.projects.filter((project) => {
return project.name.toLowerCase().includes(this.search.toLowerCase()) || project.language.toLowerCase().includes(this.search.toLowerCase())
}).slice(this.pageSizeBegin, this.pageSizeEnd)
},
}
})
Vue.filter('str_limit', function (value, size) {
if (!value) return '';
value = value.toString();
if (value.length <= size) {
return value;
}
return value.substr(0, size) + '...';
});
I think what you want is actually infinite scrolling.
A lot of libs are doing that, my favorite being vue-mugen-scroll.
Take a look at their demo, I think it's close to your use case.
var vm = new Vue({
el: '#vue-instance',
data: {
posts: [],
loading: false
},
created() {
this.getPosts()
},
methods: {
getPosts() {
for (var i = 0; i < 16; i++) {
var count = this.posts.length + i
this.posts.push({
title: 'title ' + count
})
}
}
}
});
<script src="https://unpkg.com/vue-mugen-scroll#0.2.5/dist/vue-mugen-scroll.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue-instance" class="container">
<div class="row">
<div class="col-sm-6" v-for="(post, index) in posts">
<div class="card m-4" style="width: 18rem;">
<img class="card-img-top" src="https://via.placeholder.com/350x150">
<div class="card-body">
<h5 class="card-title"><strong>{{ post.title }}</strong></h5>
</div>
</div>
</div>
</div>
<mugen-scroll :handler="getPosts" :should-handle="!loading">
loading...
</mugen-scroll>
</div>

[Vue warn]: Error in mounted hook: "Error: viewType "" is not available. Please make sure you've loaded all neccessary plugins

Im trying to use FullCalendar as a vue component in Laravel. I've loaded the plugins correctly as per the documentation but for whatever reason, they are not loading https://fullcalendar.io/docs/vue
Component:
template>
<div class="container">
<div class="row justify-contnet-center">
<div class="col-lg-8">
<form #submit.prevent>
<div class="form-group">
<label for="event_name">event Name</label>
<input
type="text"
id="event_name"
class="form-control"
v-model="newevent.event_name"
>
</div>
<div class="row">
<div class="cold-lg-8">
<div class="form-group">
<label for="date">Date</label>
<input
type="date"
id="date"
class="form-control"
v-model="newevent.date"
>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<div class="form-group">
<label for="time">Time</label>
<input
type="time"
id="time"
class="form-control"
v-model="newevent.time"
>
</div>
</div>
</div>
</div>
<div class="col-lg-6 mb-4" v-if="addingMode">
<button class="btn btn-custom" #click="addNewevent">Book event</button>
</div>
<template>
<div class="col-lg-6 mb-4">
<button class="btn btn-success" #click="updateevent">Update</button>
<button class="btn btn-danger" #click="deleteevent">Delete</button>
<button class="btn btn-warning" #click="addingMode = !addingMode">Cancel</button>
</div>
</template>
</form>
</div>
<div class="col-lg-8 full-calendar" id="calendar">
<FullCalendar #eventClick="showevent" defaultView:="dayGridMonth" :plugins="calendarPlugins" :events="events"/>
</div>
</div>
</div>
</template>
<script>
import { Calendar } from '#fullcalendar/core'
import FullCalendar from '#fullcalendar/vue'
import dayGridPlugin from '#fullcalendar/daygrid'
import interactionPlugin from '#fullcalendar/interaction'
import axios from 'axios'
export default {
components:{
FullCalendar // make custom tag avaliable
},
data() {
return {
calendarPlugins: [ dayGridPlugin, interactionPlugin],
events: "",
newevent: {
event_name: "",
date: "",
time: ""
},
addingMode: true,
indexToUpdate: ""
};
},
created() {
this.getevents();
},
methods: {
addNewevent() {
axios.post('/api/event', {
...this.newevent
})
.then(data=> {
this.getevents(); //update list of getevents
this.resetForm();
})
.catch(err =>
// alert("Unable to add event")
console.log(err.response.data)
);
},
showevent(arg) {
console.logt(arg);
this.addingMode = false;
const {id, event, date, time} = this.events.find (
event => event.id === +arg.event.id
);
this.indexToUpdate = id;
this.newevent = {
event_name: event, // comeback to and see if inserts into db as event_name
date: date,
time: time
};
},
updateevent() {
axios.put('/app/event/' + this.indexToUpdate, {
...this.newevent
})
.then(resp => {
this.resetForm();
this.getevents();
this.addingMode = !this.addingMode;
})
.catch(err =>
// alert('Unable to update event!')
console.log(err.response.data)
);
},
deleteevent() {
axios.delete('/api/event/' + this.indexToUpdate)
.then(resp => {
this.resetForm();
this.getevents();
this.addingMode = !this.addingMode;
})
.catch(err =>
// alert('Unable to delete event')
console.log(err.response.data)
);
},
getevents(){
axios.get('/api/event')
.then(resp => (this.events = resp.data.data))
.catch(err => console.log(err.response.data));
},
resetForm() {
Object.keys(this.newevent).forEach(key=> {
return (this.newevent[key] = "");
});
}
},
watch: {
indexToUpdate() {
return this.indexToUpdate
}
}
};
Then also I have initialized the component with es6 as per this part of the doc https://fullcalendar.io/docs/initialize-es6
import { Calendar } from '#fullcalendar/core';
import dayGridPlugin from '#fullcalendar/daygrid';
import interactionPlugin from '#fullcalendar/interaction';
document.addEventListener('DOMContentLoaded', function() {
let calendarEl = document.getElementById('calendar');
let calendar = new Calendar(calendarEl, {
plugins: [ dayGridPlugin , interactionPlugin ]
});
calendar.render();
});
This is my app.js
Vue.component('calendar-component', require('./components/CalendarComponent.vue').default);
<FullCalendar #eventClick="showevent" defaultView:="dayGridMonth" :plugins="calendarPlugins" :events="events"/>
should become
<FullCalendar :options="calendarOptions" />
I saw the FullCalendar code in github and it doesn't support props defaultView and plugins. Instead it uses prop with name "options"
<script>
import FullCalendar from '#fullcalendar/vue'
import dayGridPlugin from '#fullcalendar/daygrid'
import interactionPlugin from '#fullcalendar/interaction'
export default {
components: {
FullCalendar // make the <FullCalendar> tag available
},
data() {
return {
calendarOptions: {
plugins: [ dayGridPlugin, interactionPlugin ],
initialView: 'dayGridMonth'
}
}
}
}
</script>
<template>
<FullCalendar :options="calendarOptions" />
</template>
Please check the plugin documentation

Resources