I using v-select at: https://sagalbot.github.io/vue-select/
It get data from api:
/search/tag?keyword=
and I
return $tags->toJson(); (laravel)
has data return but can not append to options in v-select
View template
<template>
<v-select multiple
:debounce="250"
:on-search="getOptions"
:options="options"
placeholder="Search Tag...">
<template slot="option" scope="option" value="option.id">
{{option.name}}
</template>
</v-select>
</template>
Vue Script
import vSelect from "vue-select"
export default {
components: {vSelect},
data() {
return {
options: null
}
},
methods: {
getOptions(search, loading) {
loading(true)
this.$http.post(base_url+'/search/tag', {
keyword: search
}).then(resp => {
// this.options = JSON.stringify(resp.data);
console.log(resp.data);
loading(false)
})
}
}
}
Resp in console
The objects you are returning from your API do not have a label property. You can customize the label used by v-select by adding it as a property.
<v-select multiple
:debounce="250"
:on-search="getOptions"
:options="options"
placeholder="Search Tag..."
label="name">
</v-select>
Here is a working example.
Since you are returning an object array, define a 'label' designation to your component definition.
From the docs:
/**
* An array of strings or objects to be used as dropdown choices.
* If you are using an array of objects, vue-select will look for
* a `label` key (ex. [{label: 'This is Foo', value: 'foo'}]). A
* custom label key can be set with the `label` prop.
* #type {Array}
*/
options: {
type: Array,
default() { return [] },
},
Related
I attempted to pass a prop from Blade file to Vuejs component name AppMessages
<app-messages :messages="{{ $messages }}"></app-messages>
Then messages are being rendered based on selfOwned boolean.
<app-message v-for="message in messages" :message="message" :key="message.id" v-if="message.selfOwned === false"></app-message>
<app-message-own v-for="message in messages" :message="message" :key="message.id" v-if="message.selfOwned === true"></app-message-own>
in the child component, I pass a message when created by a Bus event
let tempBuitMessage = this.tempMessage()
Bus.$emit('message.added', tempBuitMessage);
define this.messages in props
export default {
props: {
messages: {
required: true,
type: Array
}
},
in the parent component AppMessages I set up the listner
mounted() {
Bus.$on('message.added', data => {
this.messages.unshift(data)
console.log(this.messages)
});
},
Issue:
I expected the new passed message to but up in the chat but I can see it in the console (no error) but not rendered.
You cannot manipulate a component's props from within the component. Data flows down to children through props, and up to parents through events (not talking about a bus).
You should be able to get reactivity using a computed property, or just assigning messages on mount to a data property.
<template>
...
<app-message v-for="message in model" :message="message" :key="message.id" v-if="message.selfOwned === false"></app-message>
<app-message-own v-for="message in model" :message="message" :key="message.id" v-if="message.selfOwned === true"></app-message-own>
...
</template>
<script>
export default {
name: 'app-messages',
props: {
messages: {
required: true,
type: Array
}
},
data() {
return {
model: null,
};
},
created() {
this.model = this.messages;
},
mounted() {
Bus.$on('message.added', data => {
this.model.unshift(data)
console.log(this.messages)
});
},
}
</script>
I'm using Alpine to display a list of items that will change. But I can't figure out how to tell Alpine to refresh the list of items once a new one comes back from the server:
<div x-data=" items() ">
<template x-for=" item in items " :key=" item ">
<div x-text=" item.name "></div>
</template>
</div>
The first "batch" of items is fine, because they're hard-coded in the items() function:
function items(){
return {
items: [
{ name: 'aaron' },
{ name: 'becky' },
{ name: 'claude' },
{ name: 'david' }
]
};
}
Some code outside of Alpine fetches and receives a completely new list of items, that I want to display instead of the original set. I can't figure out how, or if it's even currently possible. Thanks for any pointer.
There are 3 ways to solve this.
Move the fetch into the Alpine.js context so that it can update this.items
function items(){
return {
items: [
{ name: 'aaron' },
{ name: 'becky' },
{ name: 'claude' },
{ name: 'david' }
],
updateItems() {
// something, likely using fetch('/your-data-url').then((res) => )
this.items = newItems;
}
};
}
(Not recommended) From your JavaScript code, access rootElement.__x.$data and set __x.$data.items = someValue
<script>
// some other script on the page
// using querySelector assumes there's only 1 Alpine component
document.querySelector('[x-data]').__x.$data.items = [];
</script>
Trigger an event from your JavaScript and listen to it from your Alpine.js component.
Update to the Alpine.js component, note x-on:items-load.window="items = $event.detail.items":
<div x-data=" items() " x-on:items-load.window="items = $event.detail.items">
<template x-for=" item in items " :key=" item ">
<div x-text=" item.name "></div>
</template>
</div>
Code to trigger a custom event, you'll need to fill in the payload.
<script>
let event = new CustomEvent("items-load", {
detail: {
items: []
}
});
window.dispatchEvent(event);
</script>
Expanding on Hugo's great answer I've implemented a simple patch method that lets you update your app's state from the outside while keeping it reactive:
<div x-data="app()" x-on:patch.window="patch">
<h1 x-text="headline"></h1>
</div>
function app(){
window.model = {
headline: "some initial value",
patch(payloadOrEvent){
if(payloadOrEvent instanceof CustomEvent){
for(const key in payloadOrEvent.detail){
this[key] = payloadOrEvent.detail[key];
}
}else{
window.dispatchEvent(new CustomEvent("patch", {
detail: payloadOrEvent
}));
}
}
};
return window.model;
}
In your other, non-related script you can then call
window.model.patch({headline : 'a new value!'});
or, if you don't want assign alpine's data model to the window, you can simply fire the event, as in Hugo's answer above:
window.dispatchEvent(new CustomEvent("patch", {
detail: {headline : 'headline directly set by event!'}
}));
I'm working with 3 VUE nested components (main, parent and child) and I'm getting trouble passing data.
The main component useget a simple API data based on input request: the result is used to get other info in other component.
For example first API return the regione "DE", the first component is populated then try to get the "recipes" from region "DE" but something goes wrong: The debug comments in console are in bad order and the variable used results empty in the second request (step3):
app.js:2878 Step_1: DE
app.js:3114 Step_3: 0
app.js:2890 Step_2: DE
This is the parent (included in main component) code:
parent template:
<template>
<div>
<recipes :region="region"/>
</div>
</template>
parent code:
data: function () {
return {
region: null,
}
},
beforeRouteEnter(to, from, next) {
getData(to.params.e_title, (err, data) => {
console.log("Step_1: "+data.region); // return Step_1: DE
// here I ned to update the region value to "DE"
next(vm => vm.setRegionData(err, data));
});
},
methods: {
setRegionData(err, data) {
if (err) {
this.error = err.toString();
} else {
console.log("Step_2: " + data.region); // return DE
this.region = data.region;
}
}
},
child template:
<template>
<div v-if="recipes" class="content">
<div class="row">
<recipe-comp v-for="(recipe, index) in recipes" :key="index" :title="recipe.title" :vote="recipe.votes">
</recipe-comp>
</div>
</div>
</template>
child code:
props: ['region'],
....
beforeMount () {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
},
The issue should be into parent beforeRouteEnter hook I think.
Important debug notes:
1) It looks like the child code works properly because if I replace the default value in parent data to 'IT' instead of null the child component returns the correct recipes from second API request. This confirms the default data is updated too late and not when it got results from first API request.
data: function () {
return {
region: 'IT',
}
},
2) If I use {{region}} in child template it shows the correct (and updated) data: 'DE'!
I need fresh eyes to fix it. Can you help me?
Instead of using the beforeMount hook inside of the child component, you should be able to accomplish this using the watch property. I believe this is happening because the beforeMount hook is fired before the parent is able to set that property.
More on the Vue lifecycle can be found here
More on the beforeMount lifecycle hook can be found here
In short, you can try changing this:
props: ['region'],
....
beforeMount () {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
},
To something like this:
props: ['region'],
....
watch: {
region() {
console.log("Step_3 "+this.region); // Return null!!
this.fetchData()
}
},
Cheers!!
I have a Fields component that I am trying to use sometimes by itself and sometimes from inside a FieldArray component. I have added a snippet below with a simplified model.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { reduxForm, Fields, FieldArray, reducer as formReducer } from 'redux-form';
const reducers = {
form: formReducer
};
const reducer = combineReducers(reducers);
const store = createStore(reducer);
const renderSharedComponent = (fields) => {
console.log(fields);
return (<div>Shared Component</div>);
};
const renderHashes = ({ fields }) => (
<div>
{
fields.map((field) => (
<Fields
key={ field }
names={ [`${field}.value`, `${field}.valueIsRegex`] }
component={ renderSharedComponent }
/>
))
}
</div>
);
const ReactComponent = () => (
<div>
<FieldArray
name="hashes"
component={ renderHashes }
/>
<Fields
names={ ['value', 'valueIsRegex'] }
component={ renderSharedComponent }
/>
</div>
);
const ReduxForm = reduxForm({
form: 'default',
initialValues: {
hashes: [{}]
}
})(ReactComponent);
ReactDOM.render((
<div>
<Provider store={ store }>
<ReduxForm />
</Provider>
</div>
), document.getElementById('content'));
When I use the Fields component by itself, the fields argument from inside renderSharedComponent has the following form:
{
value: { input: {...}, meta: {...} },
valueIsRegex: { input: {...}, meta: {...} },
names: [ 'value' , 'valueIsRegex' ]
}
When I use the Fields component inside a FieldArray component, the fields argument from inside renderSharedComponent has the following form:
{
hashes: [
{
value: { input: {...}, meta: {...} },
valueIsRegex: { input: {...}, meta: {...} }
}
],
names: [ 'hashes[0].value' , 'hashes[0].valueIsRegex' ]
}
If I will be using the Fields component inside a FieldArray component with a different name (let's say paths) the names property will change accordingly (eg. names: [ 'paths[0].value' , 'paths[0].valueIsRegex' ]).
I am trying to get the value and valueIsRegex objects in a generic way that will support any of the cases I presented above.
Right now I have created a function where I use a RegEx to determine the fields. But I was wondering if anyone knows a better way to do this (maybe there is a Redux Form util that maybe I missed when reading the documentation).
having the same problem. It may be that the idiomatic way is to use the formValueSelector function. But that way its a bit more boilerplate, as you have to pass the selector (as far as I understood it) all the way down through the form.
Personally I did the regexp-based function as well. Here it is:
/**
* Converts path expression string to array
* #param {string} pathExpr path string like "bindings[0][2].labels[0].title"
* #param {Array<string|int>} array path like ['bindings', 0, 2, 'labels', 0, 'title']
*/
function breakPath(pathExpr) {
return pathExpr
.split(/\]\.|\]\[|\[|\]|\./)
.filter(x => x.length > 0)
.map(x => isNaN(parseInt(x)) ? x : parseInt(x));
}
/**
* Executes path expression on the object
* #param {string} pathExpr – path string like "bindings[0][2].labels[0].title"
* #param {Object|Array} obj - object or array value
* #return {mixed} a value lying in expression path
* #example
* ```
* execPath('books[0].title', {books: [{title: 'foo'}]})
* // yields
* 'foo'
* ```
*/
function execPath(pathExpr, obj) {
const path = breakPath(pathExpr);
if (path.length < 1) {
return obj;
}
return path.reduce(function(obj, pathPart) {
return obj[pathPart];
}, obj);
}
/**
* A generic GroupLabelField that relies on valueBasePath
*/
const GroupLabelField = (props) => {
const groupData = execPath(props.valueBasePath, props);
return (
<div className={`label__content label__content_icon_${groupData.objectType.input.value}`}>
<span className="label__remove"
onClick={(e) => { props.handleRemove(); e.stopPropagation(); }}
>
<i className="material-icons material-icons_12 material-icons_top"></i>
</span>
<span className="label__text">{groupData.title.input.value}</span>
</div>
);
};
redux-form has a hidden utility function which is useful here, but I don't know if you can rely on it's availability in future versions:
import structure from "redux-form/lib/structure/plain";
function RenderRow({ names, ...props }) {
const fields = {};
names.forEach(n => (fields[n] = structure.getIn(props, n)));
}
See also this github issue
On the official pages and in the GitHub issues for redux-form there are more than one example of how to work with initialValues however I cannot find a single one that focuses on explaining how initialValues can be set in response to an asynchronous source.
The main case that I have in mind is something like a simple CRUD application where a user is going to edit some entity that already exists. When the view is first opened and the redux-form component is mounted but before the component is rendered the initialValues must be set. Lets say that in this example that the data is loaded on demand when the component is first mounted and rendered for the first time. The examples show setting initialValues based on hard coded values or the redux store state but none that I can find focus on how to set the initialValues based on something async like a call to XHR or fetch.
I'm sure I'm just missing something fundamental so please point me in the right direction.
References:
Initializing Form State
Handling form defaults
What is the correct way to populate a dynamic form with initial data?
EDIT: Updated Solution from ReduxForm docs
This is now documented in the latest version of ReduxForm, and is much simpler than my previous answer.
The key is to connect your form component after decorating it with ReduxForm. Then you will be able to access the initialValues prop just like any other prop on your component.
// Decorate with reduxForm(). It will read the initialValues prop provided by connect()
InitializeFromStateForm = reduxForm({
form: 'initializeFromState'
})(InitializeFromStateForm)
// now set initialValues using data from your store state
InitializeFromStateForm = connect(
state => ({
initialValues: state.account.data
})
)(InitializeFromStateForm)
I accomplished this by using the redux-form reducer plugin method.
The following demos fetching async data and pre-populating a user form with response.
const RECEIVE_USER = 'RECEIVE_USER';
// once you've received data from api dispatch action
const receiveUser = (user) => {
return {
type: RECEIVE_USER,
payload: { user }
}
}
// here is your async request to retrieve user data
const fetchUser = (id) => dispatch => {
return fetch('http://getuser.api')
.then(response => response.json())
.then(json => receiveUser(json));
}
Then in your root reducer where you include your redux-form reducer you would include your reducer plugin that overrides the forms values with the returned fetched data.
const formPluginReducer = {
form: formReducer.plugin({
// this would be the name of the form you're trying to populate
user: (state, action) => {
switch (action.type) {
case RECEIVE_USER:
return {
...state,
values: {
...state.values,
...action.payload.user
}
}
default:
return state;
}
}
})
};
const rootReducer = combineReducers({
...formPluginReducer,
...yourOtherReducers
});
Finally you include you combine your new formReducer with the other reducers in your app.
Note The following assumes that the fetched user object's keys match the names of the fields in the user form. If this is not the case you will need to perform an additional step on the data to map fields.
By default, you may only initialize a form component once via initialValues. There are two methods to reinitialize the form component with new "pristine" values:
Pass a enableReinitialize prop or reduxForm() config parameter set to true to allow the form the reinitialize with new "pristine" values every time the initialValues prop changes. To keep dirty form values when it reinitializes, you can set keepDirtyOnReinitialize to true. By default, reinitializing the form replaces all dirty values with "pristine" values.
Dispatch the INITIALIZE action (using the action creator provided by redux-form).
Referenced from : http://redux-form.com/6.1.1/examples/initializeFromState/
Could you fire the dispatch on componentWillMount(), and set the state to loading.
While it is loading, render a spinner for example and only when the request returns with the values, update the state, and then re-render the form with the values??
Here is minimal working example on how to set initialValues based on async source.
It uses initialize action creator.
All values from initialValues shouldn't be undefined, or you will get an infinite loop.
// import { Field, reduxForm, change, initialize } from 'redux-form';
async someAsyncMethod() {
// fetch data from server
await this.props.getProducts(),
// this allows to get current values of props after promises and benefits code readability
const { products } = this.props;
const initialValues = { productsField: products };
// set values as pristine to be able to detect changes
this.props.dispatch(initialize(
'myForm',
initialValues,
));
}
While this method may not be the best solution, it works well enough for my needs:
AJAX request to API on entry
Initializes form with data when request has been fulfilled or displays a server error
Resetting form will still reset to initial seed data
Allows the form to be reused for other purposes (for example, a simple if statement could bypass setting initial values): Add Post and Edit Post or Add Comment and Edit Comment...etc.
Data is removed from Redux form on exit (no reason to store new data in Redux since it's being re-rendered by a Blog component)
Form.jsx:
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import { browserHistory, Link } from 'react-router';
import { editPost, fetchPost } from '../../actions/BlogActions.jsx';
import NotFound from '../../components/presentational/notfound/NotFound.jsx';
import RenderAlert from '../../components/presentational/app/RenderAlert.jsx';
import Spinner from '../../components/presentational/loaders/Spinner.jsx';
// form validation checks
const validate = (values) => {
const errors = {}
if (!values.title) {
errors.title = 'Required';
}
if (!values.image) {
errors.image = 'Required';
}
if (!values.description) {
errors.description = 'Required';
} else if (values.description.length > 10000) {
errors.description = 'Error! Must be 10,000 characters or less!';
}
return errors;
}
// renders input fields
const renderInputField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} className="form-details complete-expand" placeholder={label} type={type}/>
{touched && error && <div className="error-handlers "><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
</div>
</div>
)
// renders a text area field
const renderAreaField = ({ textarea, input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<textarea {...input} className="form-details complete-expand" placeholder={label} type={type}/>
{touched && error && <div className="error-handlers"><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>}
</div>
</div>
)
class BlogPostForm extends Component {
constructor() {
super();
this.state = {
isLoaded: false,
requestTimeout: false,
};
}
componentDidMount() {
if (this.props.location.query.postId) {
// sets a 5 second server timeout
this.timeout = setInterval(this.timer.bind(this), 5000);
// AJAX request to API
fetchPost(this.props.location.query.postId).then((res) => {
// if data returned, seed Redux form
if (res.foundPost) this.initializeForm(res.foundPost);
// if data present, set isLoaded to true, otherwise set a server error
this.setState({
isLoaded: (res.foundPost) ? true : false,
serverError: (res.err) ? res.err : ''
});
});
}
}
componentWillUnmount() {
this.clearTimeout();
}
timer() {
this.setState({ requestTimeout: true });
this.clearTimeout();
}
clearTimeout() {
clearInterval(this.timeout);
}
// initialize Redux form from API supplied data
initializeForm(foundPost) {
const initData = {
id: foundPost._id,
title: foundPost.title,
image: foundPost.image,
imgtitle: foundPost.imgtitle,
description: foundPost.description
}
this.props.initialize(initData);
}
// onSubmit => take Redux form props and send back to server
handleFormSubmit(formProps) {
editPost(formProps).then((res) => {
if (res.err) {
this.setState({
serverError: res.err
});
} else {
browserHistory.push(/blog);
}
});
}
renderServerError() {
const { serverError } = this.state;
// if form submission returns a server error, display the error
if (serverError) return <RenderAlert errorMessage={serverError} />
}
render() {
const { handleSubmit, pristine, reset, submitting, fields: { title, image, imgtitle, description } } = this.props;
const { isLoaded, requestTimeout, serverError } = this.state;
// if data hasn't returned from AJAX request, then render a spinner
if (this.props.location.query.postId && !isLoaded) {
// if AJAX request returns an error or request has timed out, show NotFound component
if (serverError || requestTimeout) return <NotFound />
return <Spinner />
}
// if above conditions are met, clear the timeout, otherwise it'll cause the component to re-render on timer's setState function
this.clearTimeout();
return (
<div className="col-sm-12">
<div className="form-container">
<h1>Edit Form</h1>
<hr />
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<Field name="title" type="text" component={renderInputField} label="Post Title" />
<Field name="image" type="text" component={renderInputField} label="Image URL" />
<Field name="imgtitle" component={renderInputField} label="Image Description" />
<Field name="description" component={renderAreaField} label="Description" />
<div>
<button type="submit" className="btn btn-primary partial-expand rounded" disabled={submitting}>Submit</button>
<button type="button" className="btn btn-danger partial-expand rounded f-r" disabled={ pristine || submitting } onClick={ reset }>Clear Values</button>
</div>
</form>
{ this.renderServerError() }
</div>
</div>
)
}
}
BlogPostForm = reduxForm({
form: 'BlogPostForm',
validate,
fields: ['name', 'image', 'imgtitle', 'description']
})(BlogPostForm);
export default BlogPostForm = connect(BlogPostForm);
BlogActions.jsx:
import * as app from 'axios';
const ROOT_URL = 'http://localhost:3001';
// submits Redux form data to server
export const editPost = ({ id, title, image, imgtitle, description, navTitle }) => {
return app.put(`${ROOT_URL}/post/edit/${id}?userId=${config.user}`, { id, title, image, imgtitle, description, navTitle }, config)
.then(response => {
return { success: response.data.message }
})
.catch(({ response }) => {
if(response.data.deniedAccess) {
return { err: response.data.deniedAccess }
} else {
return { err: response.data.err }
}
});
}
// fetches a single post from the server for front-end editing
export const fetchPost = (id) => {
return app.get(`${ROOT_URL}/posts/${id}`)
.then(response => {
return { foundPost: response.data.post}
})
.catch(({ response }) => {
return { err: response.data.err };
});
}
RenderAlert.jsx:
import React, { Component } from 'react';
const RenderAlert = (props) => {
const displayMessage = () => {
const { errorMessage } = props;
if (errorMessage) {
return (
<div className="callout-alert">
<p>
<i className="fa fa-exclamation-triangle" aria-hidden="true"/>
<strong>Error! </strong> { errorMessage }
</p>
</div>
);
}
}
return (
<div>
{ displayMessage() }
</div>
);
}
export default RenderAlert;
Reducers.jsx
import { routerReducer as routing } from 'react-router-redux';
import { reducer as formReducer } from 'redux-form';
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
form: formReducer,
routing
});
export default rootReducer;
use this :
UpdateUserForm = reduxForm({
enableReinitialize: true,
destroyOnUnmount: false,
form: 'update_user_form' // a unique identifier for this form
})(UpdateUserForm);
UpdateUserForm = connect(
(state) => ({
initialValues: state.userManagment.userSingle
})
)(UpdateUserForm);
export default UpdateUserForm;