I'm using Vuejs and Quasar on my project, I'm trying to do an autocomplete select to select some value, when the user tries to type some text, an API is sent to the server to retrieve. all the values contains that text, heres the code :
<template>
<div class="autocomplete">
<q-select
filled
label="Search"
v-model="searchTerm"
use-input
use-chips
multiple
#filter="filterFn"
#input-value="inputValue"
#filter-abort="abortFilterFn"
:options="searchResult"
style="width: 395px"
:option-label=""
option-value="text"
>
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey"> No results </q-item-section>
</q-item>
</template>
</q-select>
<div>
<back-button #click="go" data-test="btn-back" forwardicon="arrowright" />
</div>
<script>
import { defineComponent, ref, computed } from "vue";
import { apiProductService } from "../../models/";
import apiEndPoints from "../../models/api";
import BackButton from "../ui/";
import { useRouter } from "vue-router";
import { useSearchStore } from "../../store/";
import _ from "lodash";
import { debounce } from "quasar";
export default defineComponent({
name: "search-bar",
components: { BackButton },
setup(props, context) {
const router = useRouter();
let searchTerm = ref(null);
let searchResult = ref([]);
const search = useSearchStore();
function filterFn(val, update, abort) {
if (searchResult.value.length > 0) {
update();
return;
}
abortFilterFn();
document.addEventListener('keypress', function(e) {
true
});
}
function abortFilterFn() {
// console.log('delayed filter aborted')
}
const inputValue = computed(() => debounce(suggestions, 0).bind(this));
function suggestions(val) {
if (val) {
getSearchData(val);
document.addEventListener('keypress', function(e) {
true
});
} else {
searchResult.value = [];
}
}
const getSearchData = async (val) => {
await apiProductService(
apiEndPoints.GetSearchSuggestions.method,
apiEndPoints.GetSearchSuggestions.url,
{
q: val,
top: 5,
suggester: "sg",
}
).then((response) => {
searchResult.value = response?.data?.suggestions || [];
});
};
}
return {
searchTerm,
searchResult,
filterFn,
inputValue
};
},
});
the problem with code is when I try to type for example the term "tes", I can see the data retrieved for the db, when I clic on the desired value, the type text is savec on the multi selected component like:
how can I remove the type text please ?
Use ref and use updateInputValue for clear input.
https://codepen.io/Pratik__007/pen/RwMWbwK
Related
I have created a Vue3 to-do list project with VueCLI(VueX) for practice. I can add items to the array of objects and display them from objects.
Now, I want to implement a delete function that when I click the delete button beside the item, it deletes the element and also removes the object from array.
Here is my code:
NoteInput.vue
<template>
<div>
<input
type="text"
v-model="inputValue"
#keyup.enter="addItem"
/>
</div>
</template>
<script>
import { ref } from 'vue'
import { useStore } from 'vuex'
export default {
setup() {
const inputValue = ref()
const store = useStore()
const addItem = () => {
if (inputValue.value !== '') {
store.commit('addItem', inputValue.value)
}
inputValue.value = ''
}
return {
inputValue,
addItem
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>
NoteItem.vue
<template>
<div>
<div
v-for="(item, index) in list"
:key="index"
>
<span>{{item.title}}</span>
<span>
<button #click="deleteItem">Delete</button>
</span>
</div>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const list = store.state.list
const deleteItem = () => {
// store.commit('deleteItem', this.item.title)
console.log()
}
return {
list,
deleteItem
}
}
}
</script>
store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
list: []
},
getters: {
},
mutations: {
addItem(state, item) {
state.list.push({
title: item,
status: 'normal'
})
},
deleteItem(state, item) {
}
},
actions: {
},
modules: {
}
})
Please modify your NoteItem.vue and store/index.js files as below.
Working codesandbox link https://codesandbox.io/s/vue-3-vuex-4-vue-router-forked-ei4x1r
NoteItem.vue
<template>
<div>
<div
v-for="(item, index) in list"
:key="index"
>
<span>{{item.title}}</span>
<span>
<button #click="deleteItem(index)">Delete</button>
</span>
</div>
</div>
</template>
<script>
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
const list = store.state.list
const deleteItem = () => {
store.commit('deleteItem', index)
}
return {
list,
deleteItem
}
}
}
</script>
store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
list: []
},
getters: {
},
mutations: {
addItem(state, item) {
state.list.push({
title: item,
status: 'normal'
})
},
deleteItem(state, index) {
state = state.list.splice(index, 1);
}
},
actions: {
},
modules: {
}
})
i've been learning vuejs for 2 weeks and i'm stuck a day to this problem when i passing a reactive data to child component using axios
here's the code :
<template>
<div class="wrapper">
<div class="box-container">
<TreeChart
:json="tree"
:class="{ landscape: landscape.length }"
#click-item="clickItem"
/>
</div>
</div>
</template>
<script lang="ts">
import TreeChart from "#/components/TreeChart.vue";
import { defineComponent, ref } from "vue";
import { useRouter } from "vue-router";
import http from "#/common-setup/http";
export default defineComponent({
props: ["treeUid"],
setup(props) {
const landscape = ref([]);
let tree = ref({} as any);
const router = useRouter();
if (
props.treeUid === undefined ||
props.treeUid === null ||
props.treeUid === ""
) {
router.push({ name: "NotFoundResource" });
}
let response = http
.get(`/wv/tree/show/${props.treeUid}`)
.then((response) => {
if (response.data.response.length === 0) {
tree.value = {};
router.push({ name: "emptyTree" });
}
tree.value = response.data.response[0];
})
.catch((err) => {
console.log(err);
})
.then(() => {
console.log("done");
});
function clickItem(item: any) {
console.log(item);
}
return { tree, landscape, clickItem };
},
components: {
TreeChart,
},
});
</script>
<style scoped>
.wrapper {
#apply flex;
flex-direction: row;
#apply relative;
width: 100%;
}
.box-container {
margin: 0 auto;
}
</style>
here is setup api on child component
import { ref, watch, getCurrentInstance } from "vue";
import { TreeTypes } from "#/types/Tree";
export default function treeChart(props?: any) {
let treeData = ref({} as any);
const { ctx: _this }: any = getCurrentInstance();
// console.log(props.json);
watch(
props.json,
(props) => {
let showKey = function (jsonData: any) {
jsonData.extend = jsonData.extend === void 0 ? true : !!jsonData.extend;
// console.log(jsonData);
if (Array.isArray(jsonData.children)) {
jsonData.children.forEach((c: any) => {
showKey(c);
});
}
return jsonData;
};
if (props) {
treeData = showKey(props);
}
},
{ immediate: true }
);
function toggleTree(treeData: any): void {
treeData.extend = !treeData.extend;
_this.$forceUpdate();
}
return { treeData, toggleTree };
}
if i use dummy data not data from axios it will working and it become more weird when in hot reload mode, i tried to console it the data from axios suddenly appear.
is there something wrong how i pass the data with axios to the child component ?
I have this code here,
it basically fetch from firestore in the setup(), then display the information with the Categoria component. It also should update the Categoria components when the <span> is pressed. However, something don't work. My snippet successfully update the database but does not reload the component... any ideas?
<template>
<div class="header">
<span class="mdi mdi-home icona" />
<h1>Modifica menĂ¹</h1>
</div>
<Categoria
v-model="categorie"
v-for="categoria in categorie"
v-bind:key="categoria"
:Nome="categoria.nome"
/>
<div class="contenitore_aggiungi">
<span #click="crea()" class="mdi mdi-plus aggiungi" />
</div>
</template>
<script>
import Categoria from "#/components/edit/Categoria-edit.vue";
import { useRoute } from "vue-router";
import { creaCategoria, ottieniCategorie } from "#/firebase";
export default {
name: "Modifica",
components: { Categoria },
async setup() {
const route = useRoute();
let idRistorante = route.params.id;
let categorie = await ottieniCategorie(idRistorante);
console.log(categorie);
return { idRistorante, categorie };
},
methods: {
crea() {
let nuovaCategoria = "Nuova categoria";
creaCategoria(this.idRistorante, nuovaCategoria);
this.categorie.push({ nome: nuovaCategoria });
console.log(this.categorie);
},
},
};
</script>
Thanks for your answers!
You need to declare categorie as a reactive property. Also you can write methods in setup() itself instead of methods:
import { ref } from 'vue'
export default {
setup() {
const route = useRoute();
let idRistorante = route.params.id;
const categorie = ref({}) // <-- add default value of properties
const getData = async () => {
const data = await ottieniCategorie(idRistorante);
categorie.value = data
}
getData() // or void getData()
const crea = () => {
let nuovaCategoria = "Nuova categoria";
categorie.value.push({ nome: nuovaCategoria });
console.log(categorie.value);
},
return { idRistorante, categorie, crea };
}
}
Make sure the default value of categorie is set in ref(). If it's an array set it to ref([]).
I have an React Component Which Consists Dropdown , the dropdown has 4 values 'requsted,all,taken,available' and initially all the List of item are loaded. Each Item has isRequested,isTaken and isAvailable. Now, I need to filter those arrays accordingly to dropdown but i am getting something else as an output
My React Component is:
import React, { useContext, useEffect, SyntheticEvent, useState } from 'react'
import { observer } from 'mobx-react-lite';
import { Dropdown, Segment, Item, Icon, Label, Button, Select } from 'semantic-ui-react';
import { BooksStatus } from '../../app/common/options/BookStatus';
import { RootStoreContext } from '../../app/stores/rootStore';
import { format } from 'date-fns';
import { NavLink } from 'react-router-dom';
import { id } from 'date-fns/esm/locale';
const LibrarianManager: React.FC = () => {
const rootStore = useContext(RootStoreContext);
const { loadBooks, getAvailableBooks, deleteBook } = rootStore.bookStore;
const [status, setStatus] = useState(BooksStatus[0].value);
useEffect(() => {
loadBooks();
}, [loadBooks]);
const onChange = (value: any) => {
setStatus(value)
console.log(value)
if (value === 'requested') {
if (value === 'requested') {
getAvailableBooks.filter(data => data.isRequested == true)
console.log(getAvailableBooks)
}
}
}
return (
<div>
<Select
value={status}
onChange={(e, data) => onChange(data.value)}
options={BooksStatus}
/>
{getAvailableBooks.map(books => (
<Segment.Group key={books.bookName}>
<Segment>
<Item.Group>
<Item>
<Item.Image size='tiny' circular src='/assets/books.jpg' />
<Item.Content>
<Item.Header as={NavLink} to={`/booksDetail/${books.id}`} >{books.bookName}</Item.Header>
</Item.Content>
</Item>
</Item.Group>
</Segment>
<Segment clearing>
<span></span>
</Segment>
</Segment.Group>
))}
</div>
)
}
export default observer(LibrarianManager);
My Map Functions is :-
#computed get getAvailableBooks() {
return Array.from(this.bookRegistry.values());
}
#action loadBooks = async () => {
this.loadingInitial = true;
try {
const books = await agent.Books.list();
runInAction("loading books", () => {
books.forEach((books) => {
books.issuedOn = new Date(books.issuedOn);
this.bookRegistry.set(books.id, books);
});
this.loadingInitial = false;
});
} catch (error) {
runInAction("load books error", () => {
this.loadingInitial = false;
});
}
};
and my data Model or the item is
export interface IBooks {
id: number;
bookname: string;
issuedOn: Date;
returnDate: Date;
isReturned: boolean;
isRequested: boolean;
isAvailable: boolean;
isTaken: boolean;
name: string;
requestedBy: string;
}
while trying by this method
if (value === 'requested') {
if (value === 'requested') {
getAvailableBooks.filter(data => data.isRequested == true)
console.log(getAvailableBooks)
}
in console i am getting all the list without filtering
Array.filter doesn't mutate the original array but returns a new one which is nice (MDN).
What you want is:
const filteredBooks = getAvailableBooks.filter(data => data.isRequested == true)
console.log(filteredBooks)
I'm building a simple note-taking app and I'm trying to add new note at the end of the list of notes, and then see the added note immediately. Unfortunately I'm only able to do it by refreshing the page. Is there an easier way?
I know that changing state would usually help, but I have two separate components and I don't know how to connect them in any way.
So in the NewNoteForm component I have this submit action:
doSubmit = async () => {
await saveNote(this.state.data);
};
And then in the main component I simply pass the NewNoteForm component.
Here's the whole NewNoteForm component:
import React from "react";
import Joi from "joi-browser";
import Form from "./common/form";
import { getNote, saveNote } from "../services/noteService";
import { getFolders } from "../services/folderService";
class NewNoteForm extends Form {
//extends Form to get validation and handling
state = {
data: {
title: "default title",
content: "jasjdhajhdjshdjahjahdjh",
folderId: "5d6131ad65ee332060bfd9ea"
},
folders: [],
errors: {}
};
schema = {
_id: Joi.string(),
title: Joi.string().label("Title"),
content: Joi.string()
.required()
.label("Note"),
folderId: Joi.string()
.required()
.label("Folder")
};
async populateFolders() {
const { data: folders } = await getFolders();
this.setState({ folders });
}
async populateNote() {
try {
const noteId = this.props.match.params.id;
if (noteId === "new") return;
const { data: note } = await getNote(noteId);
this.setState({ data: this.mapToViewModel(note) });
} catch (ex) {
if (ex.response && ex.response.status === 404)
this.props.history.replace("/not-found");
}
}
async componentDidMount() {
await this.populateFolders();
await this.populateNote();
}
mapToViewModel(note) {
return {
_id: note._id,
title: note.title,
content: note.content,
folderId: note.folder._id
};
}
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}
doSubmit = async () => {
await saveNote(this.state.data);
};
render() {
return (
<div>
<h1>Add new note</h1>
<form onSubmit={this.handleSubmit}>
{this.renderSelect("folderId", "Folder", this.state.folders)}
{this.renderInput("title", "Title")}
{this.renderInput("content", "Content")}
{this.renderButton("Add")}
</form>
</div>
);
}
}
export default NewNoteForm;
And here's the whole main component:
import React, { Component } from "react";
import { getNotes, deleteNote } from "../services/noteService";
import ListGroup from "./common/listGroup";
import { getFolders } from "../services/folderService";
import { toast } from "react-toastify";
import SingleNote from "./singleNote";
import NewNoteForm from "./newNoteForm";
class Notes extends Component {
state = {
notes: [], //I initialize them here so they are not undefined while componentDidMount is rendering them, otherwise I'd get a runtime error
folders: [],
selectedFolder: null
};
async componentDidMount() {
const { data } = await getFolders();
const folders = [{ _id: "", name: "All notes" }, ...data];
const { data: notes } = await getNotes();
this.setState({ notes, folders });
}
handleDelete = async note => {
const originalNotes = this.state.notes;
const notes = originalNotes.filter(n => n._id !== note._id);
this.setState({ notes });
try {
await deleteNote(note._id);
} catch (ex) {
if (ex.response && ex.response.status === 404)
toast.error("This note has already been deleted.");
this.setState({ notes: originalNotes });
}
};
handleFolderSelect = folder => {
this.setState({ selectedFolder: folder }); //here I say that this is a selected folder
};
render() {
const { selectedFolder, notes } = this.state;
const filteredNotes =
selectedFolder && selectedFolder._id //if the selected folder is truthy I get all the notes with this folder id, otherwise I get all the notes
? notes.filter(n => n.folder._id === selectedFolder._id)
: notes;
return (
<div className="row m-0">
<div className="col-3">
<ListGroup
items={this.state.folders}
selectedItem={this.state.selectedFolder} //here I say that this is a selected folder
onItemSelect={this.handleFolderSelect}
/>
</div>
<div className="col">
<SingleNote
filteredNotes={filteredNotes}
onDelete={this.handleDelete}
/>
<NewNoteForm />
</div>
</div>
);
}
}
export default Notes;
How can I connect these two components so that the data shows smoothly after submitting?
You can use a callback-like pattern to communicate between a child component and its parent (which is the 3rd strategy in #FrankerZ's link)
src: https://medium.com/#thejasonfile/callback-functions-in-react-e822ebede766)
Essentially you pass in a function into the child component (in the main/parent component = "Notes": <NewNoteForm onNewNoteCreated={this.onNewNoteCreated} />
where onNewNoteCreated can accept something like the new note (raw data or the response from the service) as a parameter and saves it into the parent's local state which is in turn consumed by any interested child components, i.e. ListGroup).
Sample onNewNoteCreated implementation:
onNewNoteCreated = (newNote) => {
this.setState({
notes: [...this.state.notes, newNote],
});
}
Sample use in NewNoteForm component:
doSubmit/handleSubmit = async (event) => {
event.preventDefault();
event.stopPropagation();
const newNote = await saveNote(this.state.data);
this.props.onNewNoteCreated(newNote);
}
You probably want to stop the refresh of the page on form submit with event.preventDefault() and event.stopPropagation() inside your submit handler (What's the difference between event.stopPropagation and event.preventDefault?).