Editing to remove component names but basically trying to use v-modal to get component data inside.
Editing to remove component names but basically trying to use v-modal to get component data insideEditing to remove component names but basically trying to use v-modal to get component data inside
wp.vue . (Modal component inside template as Child component)
<template>
<div class="cart-component">
<Modal
:modal-type="this.modalType"
:customer="this.customer"
>
<div class="shipping-info">
<span class="title">Shipping Address</span>
<span class="personal-details">{{this.customer.street_address + "," + this.customer.city + "," + this.customer.state}}</span>
<span
data-toggle="modal"
v-on:click="() => {this.modalType = 'EditShipping'}"
data-target="#"
class="edit">Edit
</span>
</div>
</template>
<script>
import Modal from './Modal.vue';
export default {
data() {
return {
cart: [],
cartItems: [],
customer: {},
dataLoaded: false,
modalType: null
}
},
mounted() {
this.getCartItems()
},
components: {
'Modal': Modal
},
methods: {
getCartItems: function() {
axios
.get('/api/new-account/cart')
.then(response => {
this.cart = response.data.cart;
this.cartItems = response.data.cart.items;
this.customer = response.data.customer;
this.dataLoaded = true;
});
}
</script>
S.vue (child of Shop.vue)
<template>
<div class="shipping-input-containers">
<div class="name">
<div class="first-name">
<input v-model="customer.name.split(` `)[0]" class="default-input"></input>
<span class="input-small">First Name</span>
</div>
<div class="last-name">
<input v-model="customer.name.split(` `)[1]" class="default-input"></input>
<span class="input-small">Last Name</span>
</div>
</div>
<div class="street-address">
<input v-model="customer.street_address" class="default-input"></input>
<span class="input-small">Street Address</span>
</div>
<div class="city">
<input v-model="customer.city" class="default-input"></input>
<span class="input-small">City</span>
</div>
<div class="state">
<input v-model="customer.state" class="default-input"></input>
<span class="input-small">State</span>
</div>
<div class="zip-code">
<input v-model="customer.zip" class="default-input"></input>
<span class="input-small">Zip Code</span>
</div>
</div>
<script>
export default {
props: {
customer: {type: Object}
},
methods: {
updateShippingAddress: function() {
axios.post('/api/account/update-address', {
street_address: this.customer.street_address || "",
city: this.customer.city || "",
country_code: this.customer.country_code || "",
state: this.customer.state || "",
zip: this.customer.zip || "",
apartment: this.customer.apartment || "",
phone_number: 221232123,
})
.then(response => {
if(response.data.success) {
this.$parent.getCartItems();
let msg = "Address updated!"
this.showFlashMsg(msg, true)
}
})
.catch(err => {
});
}
}
}
</script>
Since you use same model between Shop and Modal, it works like that.
First of all, you need to use new name such as “customer.newCity” in the Modal.
And then when users update via updateShippingAddress, you can POST the user’s city like this :
//...
city: this.customer.newCiity ? this.customer.newCity : (this.customer.city || "")
//...
Related
In VueJS i have an array with plants in my store which looks like this:
state: {
query: '',
tag: '',
plants: [
{
img: "https://cdn.webshopapp.com/shops/29478/files/361778137/650x650x2/hydroplant-monstera-obliqua.jpg",
msg: 'Monstera',
tags: 'easy'},
{
img: "https://mevrouwmonstera.nl/wp-content/uploads/2020/05/Aloe-ferox.jpg",
msg: 'Aloe vera',
tags: 'easy'},
{
img: "https://www.plantje.nl/wp-content/uploads/2022/04/bonsai-ficus-51024.jpg",
msg: 'Bonsai',
tags: 'hard'},
{
img: "https://images.prismic.io/rbnl/0737820a-d068-4588-8898-1c168f49a15d_cactus-plant-in-pot.jpg?auto=compress,format&rect=0,0,1000,1000&w=500&h=500",
msg: 'Cactus',
tags: 'easy'},
],
},
It has msg, on which i filter and tags (easy and hard). I can filter between the names of the plants if I type letters in my input field. Now when I select one of the tags I want the plants with that tag to also be displayed. I want these filters to work in one function so I can filter both these things at the same time. Only the filter of msg is working.
The code in my SearchComponent looks like this:
<template>
<div id="app">
<div>
<form action="#">
<label for="lang">filter between tags:</label>
<select v-model="selected" name="plants" id="lang">
<option class="item plant" v-for="tag in filteredTags" :key="tag" :value="tag">{{tag}}</option>
</select>
<input type="submit" value="Submit" #click="filteredList"/>
</form>
<input type="text" v-model="query" placeholder="Search plants..." />
<div v-for="(plants, index) in $store.state.plants" :key="index">
<div class="item plant" v-for="plant in filteredList" :key="plant.msg">
<img v-bind:src= "plant.img" />
<p>{{ plant.msg }}</p>
<button #click="deletePlants(index)">
Delete plant
</button>
</div>
</div>
</div>
<div class="item error" v-if="query && !filteredList.length">
<p>No results found!</p>
</div>
</div>
</template>
<script>
import { mapMutations, mapGetters } from 'vuex'
export default {
name: 'SearchComponent',
props: {
msg: String
},
data () {
return {
selected: null
}
},
computed: {
...mapGetters([
'filteredList',
'filteredTags',
'filteredTags2'
// ...
]),
query: {
set (value) {
this.setQuery(value);
},
get () {
return this.$store.state.query;
}
},
tag: {
set (value) {
this.setTag(value);
},
get () {
return this.$store.state.tag;
}
},
},
methods: {
...mapMutations([
'setQuery',
'deletePlants',
'setTag'
]),
}
};
</script>
In my store this is the code I havr for the getters and mutations to filter
mutations: {
setQuery(state, value ) {
state.query = value;
},
setTag (state, value) {
state.tag = value
},
addPlants(state, plant) { state.plants.push({ msg: plant }) },
deletePlants (state, index){
state.plants.splice(index, 1);
},
},
getters: {
filteredList (state) {
return state.plants.filter((item) => {
return item.msg.toLowerCase().indexOf(state.query.toLowerCase()) !== -1 && item.tags.toLowerCase().indexOf(state.tag.toLowerCase()) !== -1
})
},
filteredTags (state) {
//todo filter tags and remove duplicates
return Array.from(new Set(state.plants.map( (x) => x.tags)));
}
I want to Use the getter filteredList to be able to filter both these things at one but right now I can still only filter the msg. I also get the following error:
TypeError: handler.apply is not a function
When I try to push the submit button. Can anyone help me with this problem? I also put the code on codesandbox.io: https://codesandbox.io/p/github/michafvdw/plant-searcher/csb-b192tb/plant-searcher?file=%2FREADME.md
I solved the problem by changing the following.
In my searchcomponent I had this:
<form action="#">
<label for="lang">filter between tags:</label>
<select v-model="selected" name="plants" id="lang">
<option class="item plant" v-for="tag in filteredTags" :key="tag" :value="tag">{{tag}}</option>
</select>
<input type="submit" value="Submit" #click="filteredList"/>
</form>
I changed that to the following:
<select v-model="tag" name="plants" id="lang">
<option disabled value="">Please select one</option>
<option class="item plant" v-for="tag in filteredTags" :key="tag" :value="tag">{{tag}}</option>
</select>
Using the submit button to go to filteredList was wrong. But now it's working
I'm sending an event from a child component to my parent component using $emit.
I know I should tell the parent component to listen to that event, however I don't know where to put the
#openModal="changeModalVisibility"
Should I do it as an attribute in the main div? Since this is the highest component I don't find the right place.
Child:
<template>
<div class="cocktail-result" v-on:click="openModal">
<img :src="cocktailImg" alt="cocktail-image" />
<p>{{ cocktailName }}</p>
<div class="explore-button">
<img
src="../assets/img/arrow-forward-icon.svg"
alt="arrow icon"
class="arrow-icon"
/>
</div>
</div>
</template>
<script>
export default {
name: "CocktailResult",
props: {
cocktailName: {
type: String,
},
cocktailImg: {
type: String,
},
modalClass: {
type: String,
},
},
methods: {
openModal() {
this.$emit("openModal");
},
},
};
</script>
Parent:
<template>
<div class="main-container" v-on:openModal="changeModalVisibility">
<div class="search-bar-container">
<img src="./assets/img/logo.png" alt="logo" class="logo" />
<div class="search-bar">
<input
type="text"
v-model="searchQuery"
class="search-bar-input"
placeholder="Enter Your Favorite Cocktail:"
#input="callCocktailApi"
/>
<img
src="./assets/img/close-icon.svg"
alt="close icon"
class="close-icon"
v-on:click="clearInput"
/>
</div>
</div>
<div class="result-container">
<ul v-for="(cocktail, index) in cocktailArray" :key="cocktail.key">
<CocktailResult
:cocktailName="this.cocktailArray[index].strDrink"
:cocktailImg="this.cocktailArray[index].strDrinkThumb"
:cocktailGlass="this.cocktailArray[index].strGlass"
:cocktailInstructions="this.cocktailArray[index].strInstructions"
:modalClass="this.modalClass"
:method="changeModalVisibility"
/>
</ul>
</div>
<CocktailModal :modalClass="this.modalClass" />
</div>
</template>
<script>
import CocktailResult from "./components/CocktailResult.vue";
import CocktailModal from "./components/CocktailModal.vue";
import axios from "axios";
export default {
name: "App",
data() {
return {
cocktailArray: "",
searchQuery: "",
cocktailName: "",
cocktailImg: "",
modalClass: "hidden",
};
},
components: {
CocktailResult,
CocktailModal,
},
methods: {
callCocktailApi() {
axios
.get(
`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${this.searchQuery}
` // API Call
)
.then((res) => {
this.cocktailArray = JSON.parse(
JSON.stringify(res.data.drinks || [])
);
this.cocktailName = this.cocktailArray[0].strDrink;
// Only render results if an array is fetched
console.log(this.cocktailArray);
})
.catch((error) => {
console.log(error);
});
},
clearInput() {
this.cocktailArray = "";
this.searchQuery = "";
},
changeModalVisibility() {
this.modalClass = "";
console.log("I'm working");
},
},
};
</script>
You should put #openModal in your parent component, there were you call CocktailResult.
<CocktailResult
#openModal=“YourCallback”
…
/>
I am trying to create a modal component that takes in user input, and upon saving that information, is displayed within another component. For example, a user is prompted to input their first and last name respectively in a modal component (Modal.vue). Once the user saves that data (a submit method on the modal), the data is displayed on another component (InputItem.vue).
Currently, I have a CreateEvent.vue component that houses the input elements, a modal.vue component that is the modal, an EventItem.vue component, that will display what is entered on once CreateEvent is executed, an EventsList.vue component that displays all the events that a user creates and finally, app.vue which houses the Modal and Events components.
I have been able to successfully get this CRUD functionality working without the modal component, but once I add the modal, I am getting confused.
If you could help lead me in the right direction, I would appreciate that!
Modal.vue
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div
class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header class="modal-header" id="modalTitle">
<slot name="header">
Create an Event
<button
type="button"
class="btn-close"
#click="close"
aria-label="Close modal"
>
x
</button>
</slot>
</header>
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input
type="text"
placeholder="add an emoji?"
v-model="newEmoji"
/>
</div>
<button #click="doneEdit">Create Event</button>
<button #click="cancelEdit">Cancel</button>
</slot>
</section>
<footer class="modal-footer">
<slot name="footer">
I'm the default footer!
<button
type="button"
class="btn-green"
#click="close"
aria-label="Close modal"
>
Close me!
</button>
</slot>
</footer>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'modal',
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
close() {
this.$emit('close')
},
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
CreateEvent.vue
<template>
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input type="text" placeholder="add an emoji?" v-model="newEmoji" />
</div>
</template>
<script>
export default {
props: {
todos: {
type: Array
}
},
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
EventItem.vue
<template>
<div class="todo-item">
<h3 class="todo-item--left">
<!-- <span v-if="!editing" #click="editTodo" class="todo-item--label">
{{ title }}
{{ emoji }}
</span> -->
<input
class="todo-item--edit"
type="text"
v-model="title"
#click="editTitle"
#blur="doneEdit"
/>
<input
class="todo-item--edit"
type="text"
v-model="emoji"
#click="editEmoji"
#blur="doneEdit"
/>
<!-- <button #click="doneEdit">Update</button>
<button #click="cancelEdit">Cancel</button> -->
</h3>
<button class="remove-item" #click="removeTodo(todo.id)">✘</button>
</div>
</template>
<script>
export default {
name: 'todo-item',
props: {
todo: {
type: Object,
required: true
}
},
data() {
return {
id: this.todo.id,
title: this.todo.title,
emoji: this.todo.emoji,
editing: this.todo.editing,
beforeEditCacheTitle: this.todo.title,
beforeEditCacheEmoji: this.todo.emoji
}
},
methods: {
editTitle() {
this.beforeEditCacheTitle = this.title
this.editing = true
},
editEmoji() {
this.beforeEditCacheEmoji = this.emoji
this.editing = true
},
doneEdit() {
if (this.title.trim() == '') {
this.title = this.beforeEditCacheTitle
}
if (this.emoji.trim() == '') {
this.emoji = this.beforeEditCacheEmoji
}
this.editing = false
this.$emit('finishedEdit', {
id: this.id,
title: this.title,
emoji: this.emoji,
editing: this.editing
})
},
cancelEdit() {
this.title = this.beforeEditCacheTitle
this.emoji = this.beforeEditCacheEmoji
this.editing = false
},
removeTodo(id) {
this.$emit('removedTodo', id)
}
}
}
</script>
Events.vue
<template>
<div>
<transition-group
name="fade"
enter-active-class="animated fadeInUp"
leave-active-class="animated fadeOutDown"
>
<EventItem
v-for="todo in todosFiltered"
:key="todo.id"
:todo="todo"
#removedTodo="removeTodo"
#finishedEdit="finishedEdit"
/>
</transition-group>
</div>
</template>
<script>
import EventItem from '#/components/EventItem'
export default {
components: {
EventItem
},
data() {
return {
filter: 'all',
todos: [
{
id: 1,
title: 'Eat sushi',
emoji: '💵',
editing: false
},
{
id: 2,
title: 'Take over world',
emoji: '👨🏽💻',
editing: false
}
]
}
},
computed: {
todosFiltered() {
if (this.filter == 'all') {
return this.todos
}
}
},
methods: {
removeTodo(id) {
const index = this.todos.findIndex(item => item.id == id)
this.todos.splice(index, 1)
},
finishedEdit(data) {
const index = this.todos.findIndex(item => item.id == data.id)
this.todos.splice(index, 1, data)
}
}
}
</script>
app.vue
<template>
<div id="app" class="container">
<button type="button" class="btn" #click="showModal">
Create Event
</button>
<Modal v-show="isModalVisible" #close="closeModal" />
<Events />
</div>
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
isModalVisible: false
}
},
methods: {
showModal() {
this.isModalVisible = true
},
closeModal() {
this.isModalVisible = false
}
}
}
</script>
The modal component should emit the values instead of pushing it into the todos array. When it emits it, the parent component (App.vue) listens for the emitted items.
I would do something like this
Modal.vue
<template>
...
// header
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
...
</div>
<button #click="handleModalSubmit">Create Event</button>
...
//footer
...
</template>
<script>
export default {
...
data() {
...
},
methods: {
...,
handleModalSubmit() {
this.$emit('todos-have-been-submitted', this.todos);
},
addTodo() {
...
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
...
}
}
}
</script>
App.vue
<template>
...
<Modal
#todos-have-been-submitted="handleTodoSubmission" //watch the 'todos-have-been-submitted" emission and trigger handleTodoSubmission method when the emission is detected
/>
<Events
:todos="todos" // pass todos as a prop to the Events component
/>
...
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
...,
todos: []
}
},
methods: {
...,
handleTodoSubmission(todos) {
this.todos = [...todos];
}
}
}
</script>
I have the following two components App.js and PersonalInfo.js:
App.js
import React, { Component } from "react";
import "./styles.css";
import PersonalInfo from "./PersonalInfo";
class App extends Component {
state = [{ fname: "Jonny", lname: "Deep" }, { fname: "test", lname: "test" }];
inputChangeHandler = event => {
this.setState({
...this.state,
[event.target.name]: event.target.value
});
};
render() {
return (
<div className="App">
<PersonalInfo
data={this.state}
inputChangeHandler={this.inputChangeHandler}
/>
</div>
);
}
}
export default App;
PersonalInfo.js
import React from "react";
const PersonalInfo = props => {
const test = props.data.map((x, i) => {
return (
<div className="form-row align-items-center">
<div className="col-sm-3 my-1">
<label className="sr-only">First Name</label>
<input
type="text"
className="form-control"
id="inlineFormInputName"
name="fname"
value={x.fname}
onChange={props.inputChangeHandler}
/>
</div>
<div className="col-sm-3 my-1">
<label className="sr-only">Last Name</label>
<div className="input-group">
<input
type="text"
className="form-control"
id="inlineFormInputGroupUsername"
name="lname"
value={x.lname}
onChange={props.inputChangeHandler}
/>
</div>
</div>
<div className="col-auto my-1">
<button type="submit" className="btn btn-primary">
Add
</button>
<button type="submit" className="btn btn-danger">
Remove
</button>
</div>
</div>
);
});
return (
<div className="container">
<form>{test}</form>
<pre>{JSON.stringify(props, null, 2)}</pre>
</div>
);
};
export default PersonalInfo;
With the above code I'm getting error of props.data.map is not a function when I type in the input field. If I comment out or remove onChange={props.inputChangeHandler} it works. But onChange to update the state.
How do I remove the error and make it work ?
Here is the sandbox link: https://codesandbox.io/s/sharp-leakey-ub859?file=/src/App.js
Your inputChangeHandler is wrong, your updating an array using an object. So when you call it your array became an object like this { key: value }.
To achieve your goal you'll need a unique ID to easily find your data inside your array and then you need to modify this array :
state = [{ id: 1, fname: "Jonny", lname: "Deep" }, { id: 2, fname: "test", lname: "test" }]
inputChangeHandler = (event, id) => {
this.setState((prev) => {
const indexOfName = prev.findIndex(x => x.id === id);
prev[indexOfName][event.target.name] = event.target.value;
return prev;
})
}
And you need also to change the way you call it :
<input
type="text"
className="form-control"
id="inlineFormInputName"
name="fname"
value={x.fname}
onChange={(event) => props.inputChangeHandler(event, x.id)}
/>
I made a small working example using the index instead of id : https://codesandbox.io/embed/nifty-napier-ps86v?fontsize=14&hidenavigation=1&theme=dark
Playing with react, trying to build a simple chat app that will be connected to firebase.
I have one container - ChatApp - and one component UserInput.
ChatApp ->
import React from 'react';
import UserInput from '../components/UserInput';
export default React.createClass({
getInitialState: function() {
return {
chatting: false
};
},
render: function() {
return <div>
<UserInput
startChat={this.chatCanStart}
/>
</div>;
},
chatCanStart: function(recipient) {
console.log(recipient);
this.setState({
chatting: true
})
}
});
UserInput ->
import React from 'react';
export default React.createClass({
getInitialState: function() {
return {
username: '',
recipient: 'asgasg'
};
},
handleUsernameChange: function(event) {
let text = event.target.text;
this.setState({
username: text
});
},
handleRecipientChange: function(event) {
let text = event.target.text;
this.setState({
recipient: text
});
},
handleStartChat: function() {
if (this.state.username !== '' && this.state.recipient !== ''){
console.log(this.state.recipient);
this.props.startChat(this.state.recipient);
}
},
render: function() {
return <div className="panel panel-default">
<div className="panel-heading"> Welcome to the chat app done in React </div>
<div className="panel-body">
<div className="input-group">
<span className="input-group-addon"> Username </span>
<input type="email" className="form-control" value={this.state.username} onChange={this.handleUsernameChange} />
</div>
<br />
<div className="input-group">
<span className="input-group-addon"> Recipient </span>
<input type="email" className="form-control" value={this.state.recipient} onChange={this.handleRecipientChange} />
</div>
<br />
<button type="button" className="btn btn-primary" onClick={this.handleStartChat}> Chat now </button>
</div>
</div>;
}
});
When I change the Username and Recipient fields and then click on the Chat now button, I am expecting the chatting state in the ChatApp container to change to true and also want to log the passed recipient.
But I get 'undefined', why is that? I get 'undefined' even in the console.log in the UserInput component. Even though the inputs value in the form is being changed just fine.
What am I doing wrong?
Thanks.
To get value from input you need to use .value property instead of .text
handleUsernameChange: function(event) {
let text = event.target.value;
this.setState({
username: text
});
},
handleRecipientChange: function(event) {
let text = event.target.value;
this.setState({
recipient: text
});
},
Example