vue3 array push not woking - vue.js

const myArray =[];
const nestedMenuitems = ref([
{
items:myArray
},
]);
onMounted(() => {
ApiService.getStores().then((data) => {
stores.value = data;
stores.value.map(function(value, _key) {
myArray.push({lable: value.shop});
});
});
});
i am trying show array data items:myArray this line
under the code working fine but mu custom data api to pass data its not working.what is the problem.
how to solve this?
const list= [
{
label: 'Tracker',
icon: 'pi pi-fw pi-compass'
},
{
label: 'Map',
icon: 'pi pi-fw pi-map-marker'
},
{
label: 'Manage',
icon: 'pi pi-fw pi-pencil'
}
];
const nestedMenuitems = ref([
{
items:list
},
]);
i am trying show array data items:myArray this line
under the code working fine but mu custom data api to pass data its not working.what is the problem.
how to solve this?
<Menubar :model="nestedMenuitems" optionValue="value">
</Menubar>
model="nestedMenuitems to get dropdown data

To trigger reactivity you need to replace the item inside nestedMenuItems.
What's not clear to me is why aren't you using a simpler data structure:
const state = reactive({ items: [] });
const { items } = toRefs(state);
onMounted(() => {
ApiService.getStores().then((data) => {
data.forEach((value) => {
state.items.push({ label: value.shop });
});
});
});
What do you need nestedMenuItems for? Do you have more than one nestedMenuItem?
You might want to show <Menubar />. Most likely, you don't need v-model in it, but v-bind. e.g:
<Menubar :items="items" optionValue="value" />
And another question would be: if you're storing the response in stores, why are you also storing it somewhere else (e.g: in nestedMenuItems, or items)? Why not consume it directly from stores?
For example:
const stores = ref([]);
const items = computed(() => stores.value.map(({ shop: label }) => ({ label })));
onMounted(() => {
ApiService.getStores().then((data) => {
stores.value = data;
});
});
<pre v-text="JSON.stringify({ stores, items }, null, 2)" />
It's bad practice to keep the same reactive data in two separate places, because you're always running the risk of them getting out of sync. If you're using the same source, any change will be reflected in every place where the data is consumed/displayed.

Related

Inheritance / shared action and getters in Pinia

I have a couple of Pinia stores that should share a set of actions and getters and I’m not quite sure how to effectively achieve that.
I’m building an app that lets users manage a number of different media (Books, Movies, TV Shows, etc). The way I’m currently thinking about it is to have a store for each media type e.g. BookStore, MovieStore etc. A lot of the getters and actions (e.g., count and deleteOne) are exactly the same between those different stores.
How do I achieve DRY here? The Pinia documentation has examples that mostly focus around reusing actions and getters inside other stores but I don’t think that quite solves my use case of inheriting a set of getters and setters outright.
Is my attempted inheritance approach here an anti-pattern?
This is achievable using plugins docs
Example Movies:
You have multiple stores using shared naming scheme for each state:
item: single entity item (single movie details)
collection: collection of items (collection of all movies)
each store will have the same CRUD actions with only the URL changing
getCollection: get list of items from API and set response as collection (https://url.com/movies)
getItem: get single item from API and set response as item (https://url.com/movies/id)
handleError: displays alert to the user with error information
Create plugin:
function BaseStorePlugin () {
return {
collection: [],
item: {},
getCollection: function (url) {
api.get(url)
.then((response) => {
this.collection = response.data;
})
.catch((error) => {
this.handleError(error);
});
},
getItem: function (url) {
api.get(url)
.then((response) => {
this.item = response.data;
})
.catch((error) => {
this.handleError(error);
});
},
handleError: function (error) {
window.alert(error);
},
};
}
Give plugin to Pinia:
const pinia = createPinia();
pinia.use(BaseStorePlugin);
Example movieStore.js (using shared action & state)
import { defineStore } from 'pinia';
import { api } from 'src/boot/axios';
export const useMovieStore = defineStore({
id: 'movie',
state: () => ({
movieSpecificStateObject: {},
}),
actions: {
movieSpecificAction (url) {
console.log(this.item);
api.get(url)
.then((response) => {
// handle response
})
.catch((error) => {
this.handleError(error);
});
},
},
});
Example usage in component
<template>
<div
v-for="movie in movieStore.collection"
:key="movie.id"
>
<div>
{{ movie.name }}
</div>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
import { useMovieStore } from 'src/stores/movieStore.js';
const movieStore = useMovieStore();
onMounted(() => {
movieStore.readCollection('http://url.com/movies');
});
</script>
Edit: 1
if you pass the context into the plugin you have access to the store and options being passed into it, from this you could check the store id and only return for specific stores like below
function BaseStorePlugin (context) {
const allowedStores = ['movie', 'album'];
if (allowedStores.includes(context.store.$id)) {
return {
collection: [],
getCollection: function () {
const fakeCollection = Array.from({length: 10}, () => Math.floor(Math.random() * 40));
fakeCollection.forEach((item) => {
this.collection.push({
id: item,
name: `name${item}`
});
});
},
};
};
}
I have created a very basic example using 3 stores and the above check available on codesandbox here

Save multiple fields from record in different collection in react-admin Form

I'm using react-admin to manage a MongoDB database. A simplified example of the collections in the database:
contacts = [
{ id: 8, name: "Joe", country: "UK" },
]
tasks = [
{ id: 0, description: "", dev: { contact_id: 8, name: "Joe" } },
]
The documents in tasks have to store both contact_id (contact doc reference) and name (shown in many different views, so the number of API calls can be reduced). In this case, I'd use an AutocompleteInput within a ReferenceInput to save contact_id.
<ReferenceInput source="dev.contact_id" reference="contacts">
<AutocompleteInput
source="dev.contact_id"
optionText="name"
optionValue="id"
/>
</ReferenceInput>
However, once the contact is selected in the Autocomplete, I can't find a way to save the field name, so the task document looks like in the example. So far I've tried getting the contact record and adding the name field before submitting the form, but it's not working (hooks can only be called inside the body of a function component):
export const TaskForm = ({ children }) => {
const { handleSubmit } = useForm()
const onSubmit = async (data) => {
const contact = await useGetOne('contacts', { id: data?.dev?.contact_id })
console.log(contact)
}
return (
<Form onSubmit={handleSubmit(onSubmit)}>
{children}
</Form>
)
}
Any suggestions?
Perhaps you can use the dataProvider-hook instead of the useGetOne:
export const TaskForm = ({ children }) => {
const { handleSubmit } = useForm()
const dataProvider = useDataProvider();
const onSubmit = async (data) => {
const contact = await dataProvider.getOne('contacts', { id: data?.dev?.contact_id });
console.log(contact)
}
return (
<Form onSubmit={handleSubmit(onSubmit)}>
{children}
</Form>
)
}

Call useUpdateMany callback with data

Reading the docs it seems that whenever I import useUpdateMany I already have to pass the data it's going to send. My question is, is it possible to pass the data on the callback?
I want to call the updateMany in a handleSubmit function, so I will only have the data when the function is called:
export const ChangeStatus = (props) => {
const { record, version } = props;
const { t } = useTranslation('admin');
const classes = useStyles();
const refresh = useRefresh();
const notify = useNotify();
const [componentStatus, setComponentStatus] = useState(null);
const [updateMany, { loading, error }] = useUpdateMany('orders', props.selectedIds, {componentStatus });
const defaultSubscription = {
submitting: true,
pristine: true,
valid: true,
invalid: true,
};
const handleSubmit = ({ status }) => {
setComponentStatus({ status });
updateMany();
refresh();
};
return (
<Form
initialValues={record}
subscription={defaultSubscription}
key={version}
onSubmit={handleSubmit}
render={(formProps) => (
<form onSubmit={formProps.handleSubmit} className={classes.form}>
<SelectInput
label="Status"
variant="outlined"
source="status"
className={classes.selectField}
FormHelperTextProps={{ className: classes.selectHelperText }}
choices={[
{ id: 'created', name: 'Created' },
{ id: 'canceled', name: 'Canceled' },
{ id: 'active', name: 'Active' },
{ id: 'awaiting', name: 'Awaiting allocation' },
{ id: 'processing', name: 'Processing' },
{ id: 'review', name: 'Review' },
{ id: 'completed', name: 'Completed' },
]}
/>
<Button variant="contained" color="secondary" type="submit" disabled={loading}>
{t('Confirm')}
</Button>
</form>
)}
/>
);
};
Right now I'm updating a state and then calling the updateMany, but it would be much easier if I could call the updateMany passing the data:
const handleSubmit = ({ status }) =>
updateMany({status});
};
Is it possible to do it?
Thanks!
You can override the params when calling updateMany function but you have to respect the mutation params format.
In your example you can do updateMany({ payload: { data: { componentStatus } } });
Indeed useUpdateMany call useMutation under the hood and this hook allow merge with callTimeQuery
You can find some references here :
https://marmelab.com/react-admin/Actions.html#usemutation-hook
useMutation accepts a variant call where the parameters are passed to the callback instead of when calling the hook. Use this variant when some parameters are only known at call time.
https://github.com/marmelab/react-admin/blob/bdf941315be7a2a35c8da7925a2a179dbcb607a1/packages/ra-core/src/dataProvider/useMutation.ts#L299
Also there is an issue wich foresees a more logical signature at call time : https://github.com/marmelab/react-admin/issues/6047
(Update : And now this is merged for a future release: https://github.com/marmelab/react-admin/pull/6168 )

VueJS MDB-datatable doesn't render data from the API call

I'm using vueJs MDB-datatable to display my data coming from my API.
I followed the MDB-datable documentation in handling the "OtherJSON structure" but it didn't re-render the data from the API request.
I tried different callback beforeCreate, created, beforeMount, and mounted, the data was changed but still, it didn't render the latest data.
Here's the code:
<template>
<mdb-datatable
:data="tableData"
striped
bordered
/>
</template>
<script>
import 'mdbvue/build/css/mdb.css';
import { mdbDatatable } from 'mdbvue';
export default {
components: {
mdbDatatable
},
data: () => ({
tableData: {
columns: [],
rows: []
}
}),
created() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(json => {
let keys = ["id", "name", "username"];
let entries = this.filterData(json, keys);
//columns
this.tableData.columns = keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
});
console.log(this.tableData.columns);
//rows
entries.map(entry => this.tableData.rows.push(entry));
console.log(this.tableData.rows);
})
.catch(err => console.log(err))
},
methods: {
filterData(dataArr, keys) {
let data = dataArr.map(entry => {
let filteredEntry = {};
keys.forEach(key => {
if(key in entry) {
filteredEntry[key] = entry[key];
}
})
return filteredEntry;
})
return data;
}
}
</script>
The MDB-datatable documentation seems to be straight forward but I don't know which part I'm missing.
I'm new to VueJS. Any help is much appreciated.
It seems that the current version of MDB Vue (5.5.0) takes a reference to the rows and columns arrays and reacts when these arrays mutate rather than reacting to changes to the property bound to the data prop itself.
I see you are already mutating rather than replacing the rows array, so you need to do the same with the columns array.
//columns
this.tableData.columns.push(...keys.map(key => {
return {
label: key.toUpperCase(),
field: key,
sort: 'asc'
};
}));

Vuex filter state

I'm at my first app in Vuejs Vuex.
I can not find the best way to filter a state.
store/index.js
state: {
projects: []
},
mutations: {
SET_PROJECT_LIST: (state, { list }) => {
state.projects = list
}
},
actions: {
LOAD_PROJECT_LIST: function ({ commit }) {
axios.get('projects').then((response) => {
commit('SET_PROJECT_LIST', { list: response.data})
}, (err) => {
console.log(err)
})
}
}
in the component:
computed: {
...mapState({
projects
})
}
At this point I have a list of my projects. Good!
Now I added buttons to filter my projects like:
Active Project, Type Project ...
How do I manipulate my projects object (this.projects)?
With another one this.$store.dispatch
With another getters function
I manipulate the state without changing the status?
I'm a bit confused.
Some examples of filters on lists populated in Vuex?
EDIT:
I was trying that way:
this.$store.getters.activeProjects()
But how I update this.projects?
activeProjects(){
this.projects = this.$store.getters.activeProjects()
}
does not work
I'd recommend to keep your original state intact and filter its data by using "getters".
In fact, the official documentation includes an example of how to get all the "done" todos. It might be useful for you:
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
Getters reference: https://vuex.vuejs.org/en/getters.html
Good luck!