Vuex doesn't react with complex object - javascript

I've started to use Vuex in order to remplace the EventBus, because the data in my app has started to get a little bit complex.
In my context, I have a question entity, with multiple answers, when the user insert another answer I want to show the last one; (here I use two different components: one to show the answers and other to answer the question) but when the server response OK with the new answer, and the mutation change the state.answers, the computed property doesn't react and doesn't show the new answer:
Here is my data structure:
"answers": {
"118": {
"id": 118,
"description": "objective",
"created_at": "2019-11-12T19:12:36.015Z",
"dojo_process_id": 1,
"question_id": 1,
"user_id": 10
}
"127": {
"id": 127,
"description": "asdddd",
"created_at": "2019-11-12T19:38:19.233Z",
"dojo_process_id": 1,
"question_id": 1,
"user_id": 10
},
"128": {
"id": 128,
"description": "asddddasddd",
"created_at": "2019-11-12T20:00:17.572Z",
"dojo_process_id": 1,
"question_id": 1,
"user_id": 10
}
},
Here is the code for my store:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
...
answers: {},
...
},
getters: {
findBy: state=> filter => {
let result= Object.values(state[filter.model]).
filter(data => data[filter.field] === filter.criteria);
return result;
}
},
mutations: {
setAnswers(state, answers) {
state.answers = answers;
},
setAnswer(state, answer) {
state.answers[answer.id] = answer;
},
},
actions: {
replaceCompleteProcess(context, data) {
...
context.commit('setAnswers',data.answers);
...
},
cleanCompleteProcess(context) {
...
context.commit('setAnswers',{});
...
},
saveAnswer(context, answer) {
context.commit('setAnswer', answer);
}
}
});
And this is how the script of my component is structured:
export default {
name: "show-question",
computed: {
question: function () {
return this.$store.getters.question(this.questionId)
},
answers: function () {
return this.$store.getters.findBy({
model: 'answers',
field: 'question_id',
criteria: this.questionId,
sort: true
});
},
answer: function () {
if (this.answers && this.answers.length > 0) {
return this.answers[0].description;
} else {
return '';
}
}
},
props: {
questionId: {
type: [Number],
default: 0
}
},
data() {
return {
sending: false,
answerData: this.answer
}
},
methods: {
sendAnswer () {
this.sending = true;
questionConnector.answerQuestion(this,this.question.id,this.dojoProcessId, this.answerData)
},
// this one is called from AXIOS
answerWasOK(answer) {
this.sending = false;
this.$store.dispatch('saveAnswer', answer);
this.answerData = '';
}
}
}
So, if I understand how to use Vuex, when I call this.$store.dispatch('saveAnswer', answer), the state will be updated, and the computed property answers would be updated, and I'll be able to show the new changes in the component, But it doesn't work.... the computed property just doesn't react.
I had read a lot about vuex and how "it not work well" with complex data, so I normalize my data. but it is the same... also I tried to use vuex-orm, but I have a lot of problems with the one-many relation, and I cant do it work.
EDIT: Solution
I did a small test with the ideas from the answers and it works
setAnswer(state, answer) {
let newAnswers = state.answers;
state.answers = {};
newAnswers[answer.id] = answer;
state.answers = newAnswers;
}

When you are working with Objects you have to do it like this
setAnswer(state, answer) {
Vue.set(state.answers, answer.id, answer);
},
This is clearly mentioned in the documentation.
When adding new properties to an Object, you should either:
Use Vue.set(obj, 'newProp', 123), or
Replace that Object with a fresh one. For example, using the object spread syntax we can write it like this:
state.obj = { ...state.obj, newProp: 123 }

You are storing a list of answers inside an object. Nothing wrong with that, since you know how to deal with. It turns out that Vue.js observers don't track new object attributes (which is exactly what you are doing there, creating new attributes, instead of modifying a list/array).
My first suggestion is to change this object to an array. But if you can't, due to your ORM or other reason of your project standard, you should take a look about Reactivity of Vue.js. The quickest solution, in my opinion, is to use a watcher:
https://v2.vuejs.org/v2/api/#watch
Some links that can be helpful to understand Vue.js reactivity:
Reactivity in Depth
https://v2.vuejs.org/v2/guide/reactivity.html
How to actively track an object property change?
https://forum.vuejs.org/t/how-to-actively-track-an-object-property-change/34402/1

Related

How to get array length in vuejs

This is my script tag
<script>
export default {
data() {
return {
blogs: [],
};
},
created() {
this.paginate_total = this.blogs.length / this.paginate;
},
};
</script>
these the response I get in my console
{
"blogs": [
{
"_id": "63243272c988e721db51de9c",
},
{
"_id": "63243cb8a8189f8080411e65",
},
]
}
error i get in my console
Cannot read properties of undefined (reading 'length')
Please what I'm I doing wrong
Place this line in mounted instead of created
this.paginate_total = this.blogs.length / this.paginate;
because this blog is not available in created yet that's why it is undefined.
You can't access the object array length in mounted or created lifecycles so I just used watch property to get the object length
watch: {
blogs(blogs) {
this.paginate_total = blogs.length / this.paginate;
}
}
For row counts (as for table pagination etc.) i would use a computed property like:
computed: {
paginate_total() {
return this.blogs.length;
},
}

Strapi: Get all nested properties for deeply nested relation

I recently started working with strapi and have been figuring out how to go about content relations and so on... Now I have reached a point when I have multiple content relations dependent on each other.
This is my strucute:
Collection Types:
Categories
Articles
with content relation: Article has one category
Single Types:
Homepage
with content relation: Homepage has many articles
Now what I want to do is to to get all nested properties of a category for articles that are assigned to homepage just by simply making a GET request from /homepage
What I currently get is a json structure like this:
{
"id": 1,
"hero": {
....
},
"featuredArticles": {
....
},
"displayedArticles": [
{
"id": 2,
"category": 5,
}
]
}
What is the expected output:
{
"id": 1,
"hero": {
....
},
"featuredArticles": {
....
},
"displayedArticles": [
{
"id": 2,
"category": [
{
"id": 5,
"title": "Foundation"
}
],
}
]
}
My suspicion is that the properties of categories is basically too nested when trying to fetching from /homepage rather than /articles directly.
I found out that handling this could work with modifying the controllers right in the strapi directory but I haven't figured it out quite.
Strapi Controller Docs
Is here anybody who knows solution to this?
Firstly you'll need a custom controller function for this. In /api/homepage/controllers/homepage.js you can export your custom find function.
There you can define which fields you want to populate:
module.exports = {
find: ctx => {
return strapi.query('homepage').find(ctx.query, [
{
path: 'displayedArticles',
populate: {
path: 'category',
},
},
]);
}
};
For reference see the most recent beta docs:
Customization
Second way: populate it as per requirement
module.exports = {
find: async (ctx) => {
const homePage = await strapi.query('homepage').find(ctx.query);
const categories = await strapi.query('categories').find();
if (homePage[0].displayedArticles) {
homePage[0].displayedArticles.forEach(async (content) => {
if(categories){
content.category = categories.find((category) => {
return category.id === content.category;
});
}
});
}
if (homePage[0].displayedComponents) {
homePage[0].displayedComponents.forEach(async (content) => {
if(categories){
content.category = categories.find((category) => {
return category.id === content.category;
});
}
});
}
return homePage;
}
};

Firebase extract name of object

I have a simple question today.
I retrieve data from my firebase database:
const response = await fetch('For pricacy purpose I replaced this link to my firebase database.');
const resData = await response.json();
console.log(resData);
Also I log the results in the console, the following text is what I retrieve:
Object {
"-MPOg49jvG-md0twgj-D": Object {
"id": 1,
},
"-MPTgHoTXzIcY_KpBHkc": Object {
"id": 2,
},
"-MPTgmANDZkMv7f_A9TG": Object {
"id": 4,
},
"-MPTgmc2fuu5XSUawuW7": Object {
"id": 3,
},
}
Now my question: I want to access not the id that is in the objects but rather the "name" of the object itself. If you look at the first element:
"-MPOg49jvG-md0twgj-D": Object {
"id": 1, }
i want to access this "-MPOg49jvG-md0twgj-D" and store it in a constant but I dont know how to do it. Any idea would be appriciated.
If I'm understanding correctly, you already fetched resData as a JavaScript object and want to get the keys? These are some ways that possibly could help you.
const resData = {
"-MPOg49jvG-md0twgj-D": {
id: 1
},
"-MPTgHoTXzIcY_KpBHkc": {
id: 2
},
"-MPTgmANDZkMv7f_A9TG": {
id: 4
},
"-MPTgmc2fuu5XSUawuW7": {
id: 3
}
};
// method 1
console.log(Object.keys(resData));
// method 2
for (const key in resData) {
console.log(key, resData[key]);
}
// method 3
console.log(Object.getOwnPropertyNames(resData));
Hope this can help, please correct me if I got it wrong.

Objects are not valid as a React child - is my JSON data bad?

I am aware this has been asked several times on Stack Overflow, but other solutions don't seem to work for me.
I am trying to access the notifications in this JSON file.
My JSON file: https://dinkyapp.000webhostapp.com/db.json
Is my JSON file poorly structured? I know it's pretty big. In the world outside of React, the JSON file works fine. But in React, it's unworkable, no matter if I parse, convert to arrays etc. Should I make multiple smaller files instead?
In my example, I'm trying to access the notifications and return them in the JSX.
I get the error, 'Objects are not valid as a React child'.
I also get notifs.map is not a function. Presumably this is because the data being pulled through is not in an array format.
My component code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const GetData = () => {
let [notifs, setNotifs] = useState([]);
useEffect(() => {
axios.get('db.json').then((res) => {
var data = res.data.clients.clientID_1.notifications;
setNotifs(data);
});
}, []);
const getIDs =
notifs.map(item => {
return (
<p key={notifs.reqID}>{notifs.text}</p>
)
})
return <div>{getIDs}</div>;
};
export default GetData;
I'm nearly about to switch back to Vanilla JS as I have tried so many ways. But I think it may be my JSON data being poor. If anyone could please advise?
Many thanks
that's because you are trying to map over objects, for simplify your JSON could be
"notifications": [
{
"user_id": 1 // move user id here
"timestamp": 1613777053000,
"reqID": 100012,
"seen": true,
"text": "Aaron F accepted your shift swap"
},
{
"user_id": 2, // move user id here
"timestamp": 1613777053000,
"reqID": 100012,
"seen": true,
"text": "Aaron F accepted your shift swap"
}]
then you now map safely
If look at https://dinkyapp.000webhostapp.com/db.json res.data.clients.clientID_1.notifications it is object like:
const notifs = {
userID_1: [
{
timestamp: 1613777053000,
reqID: 100012,
seen: true,
text: 'Aaron F accepted your shift swap',
},
],
userID_2: [
{
timestamp: 1614676200000,
reqID: 199290,
seen: true,
text: 'Sean W approved your Annual Leave request',
},
{
timestamp: 1610719942000,
reqID: 184828,
seen: true,
text: 'Sean W approved your Annual Leave request',
},
],
};
Not array, so you need work with it as an object, example:
const getIDs = Object.keys(notifs).map(key => {
return (
<p key={key}>
From {key}
{notifs[key].map(n => (
<p key={n.reqID}>{n.text}</p>
))}
</p>
);
});
Try changing it to this:
<p key={item.reqID}>{item.text}</p>

How to put return value into another return value in JS vue

I want the "topic1" to be the value of my breed name and key, but when I try to put this.topic1 to replace the manual typing, it shows nothing.
Or there are any other method to have my button name same as my retrieve API param, and sent it name when I click it?
new Vue({
el: '#app2',
components: { Async },
data() {
return {
topic1: null,
topic2: null,
currentBreed: 0,
breeds: [
{ name: this.topic1 , key: this.topic1 },
{ name: "German Shepherd", key: "germanshepherd" },
{ name: "Husky", key: "husky" },
{ name: "Pug", key: "pug" },
{ name: "(Error)", key: "error" },
]
}
},
async created() {
try {
this.promise = axios.get(
"https://k67r3w45c4.execute-api.ap-southeast-1.amazonaws.com/TwitterTrends"
);
const res = await this.promise;
this.topic1 = res.data[0].Trends;
this.topic2 = res.data[1].Trends;
} catch (e) {
console.error(e);
}
},
async mounted () {
let test = this.topic1;
},
computed: {
breedKey() {
return this.breeds[this.currentBreed].key;
}
}
})
There are several problems here.
The data function is called just once, when the corresponding Vue instance is created. Within that function you can get a reference to its Vue instance via this. At that point some properties, such as those corresponding to props, will already exist. However, others won't.
The object returned from data is used to create new properties on the instance. In this case you're creating 4 properties: topic1, topic2, currentBreed and breeds. Vue creates those properties based on that returned object, so they won't exist until after the data function is run.
So when you write { name: this.topic1 , key: this.topic1 }, within that data function you're attempting to access a property called topic1 that doesn't exist yet. As such it will have a value of undefined. So you're creating an entry equivalent to { name: undefined , key: undefined },.
Further, there is no link back to topic1. That object won't be updated when the value of topic1 changes.
It's also worth noting a few points about timing.
The data function will be called before the created hook, so the axios call isn't made until after the data properties are populated.
An axios call is asynchronous.
Using await may make the code a little easier to read but the 'waiting' is mostly just an illusion. The remaining code inside the function won't run until the awaited promise is resolved but that won't cause anything outside of the function to wait. await is equivalent to using then.
The component will render just after the created hook is called. This is synchronous, it won't wait for the axios request. The mounted hook will then be called, all before the axios call has completed.
All of this means you may need to adjust your template to handle the case where the axios call hasn't completed yet as it will initially render prior to the values of topic1 and topic2 being available.
Specifically addressing the breeds property you have a few options. One is to inject the values in once the value has loaded:
breeds: [
{ name: "" , key: "" }, // Initially empty values
{ name: "German Shepherd", key: "germanshepherd" },
// ...
const res = await this.promise;
this.topic1 = res.data[0].Trends;
this.topic2 = res.data[1].Trends;
this.breeds[0].name = this.breeds[0].key = this.topic1;
Another is to use a computed property for breeds (you'd remove it from the data for this):
computed: {
breeds () {
return [
{ name: this.topic1 , key: this.topic1 },
{ name: "German Shepherd", key: "germanshepherd" },
{ name: "Husky", key: "husky" },
{ name: "Pug", key: "pug" },
{ name: "(Error)", key: "error" },
]
}
}
As we're using a computed property it will be updated when topic1 changes as it's a reactive dependency.
Using a computed property is probably the most natural solution in this case but there are other tricks you can use to get this to work.
For example, you could use property getters for the two properties in that first breed object (that's JavaScript property getters, nothing to do with Vue):
data () {
const vm = this;
return {
topic1: null,
topic2: null,
currentBreed: 0,
breeds: [
{
get name () {
return vm.topic1;
},
get key () {
return vm.topic1;
}
},
{ name: "German Shepherd", key: "germanshepherd" },
{ name: "Husky", key: "husky" },
{ name: "Pug", key: "pug" },
{ name: "(Error)", key: "error" },
]
}
},
I'm not advocating this approach for your use case but it is an interesting way to do it that can sometimes be useful. The key thing to note is how the dependency on topic1 is evaluated only when the properties name and key are accessed, not when the data function is executed. This allows topic1 to be registered as a dependency of whatever is accessing name and key, e.g. during rendering.

Categories

Resources