React Redux store changes sometimes rerender and sometimes dont - javascript

So i am using the fetch API with react / redux to perform API calls to Blockv in my actions and store a returned array of objects in my redux store using a reducer. My array of objects only contains one level of objects (i.e. {id: 9798234982739847, name: 220398402343, etc..}). My table that SHOULD rerender everytime the array is replaced - and I use replace as i want to completely replace the array in the store with the new - only rerenders sometimes and i can see the changes reflected in the store.
I am using thunk middleware. Here is what im working with:
store.js
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger'
import urbanArcadeReducer from '../reducers/reducers'
import { loadTokens } from '../localStorage'
import { checkAuth } from '../actions/check-auth'
const persistedTokens = loadTokens();
const loggerMiddleware = createLogger();
// Store for Urban Arcade Application
const store = createStore(
urbanArcadeReducer,
persistedTokens,
applyMiddleware(
thunk, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)
)
// Check for automatic login on page load
store.dispatch(checkAuth(store.getState().access_token))
export default store
reducers.js
function atoms(state = {
receivedAt: null,
atoms: []
}, action) {
switch (action.type) {
case UPDATE_CURR_ATOMS:
var mappedAtoms = action.atoms.map(atom => {
atom = {
id: atom.id,
gameTitle: atom.private.GameTitle,
highscore: atom.private.HighScore,
highscoreOwner: atom.private.HighScoreOwner,
locationName: atom.private.LocationName,
scoreHistory: atom.private.ScoreHistory,
unpublished: atom.unpublished,
author: atom['vAtom::vAtomType'].author,
description: atom['vAtom::vAtomType'].description,
dropped: atom['vAtom::vAtomType'].dropped,
lat: atom['vAtom::vAtomType'].geo_pos.coordinates[1],
lng: atom['vAtom::vAtomType'].geo_pos.coordinates[0],
owner: atom['vAtom::vAtomType'].owner,
template: atom['vAtom::vAtomType'].template,
template_variation: atom['vAtom::vAtomType'].template_variation,
title: atom['vAtom::vAtomType'].title,
when_created: atom.when_created,
when_modified: atom.when_modified
}
return atom
})
return {
...state,
receivedAt: action.receivedAt,
atoms: mappedAtoms
}
default:
return state
}
}
actions.js
import {
fetchRequest,
fetchFailure,
fetchSuccess,
updateNotifier,
updateCurrAtoms } from './action-creators'
import { bringToLogin } from './bring-to-login'
export const UPDATE_CURR_ATOMS = 'UPDATE_CURR_ATOMS'
export function updateCurrAtoms(atoms) {
return { type: UPDATE_CURR_ATOMS, atoms: atoms.atoms, receivedAt: atoms.receivedAt }
}
/**
* Submits request to get all arcade games using Blockv Discover Endpoint(vAtoms)
*
* #returns list of arcade cabinets (vAtoms)
*/
export function getAtoms(params) {
var access_token = params.access_token
var from_refresh = params.from_refresh
var responseCode = ''
var method = 'POST'
var url = 'https://api.blockv.io/v1/vatom/discover'
var headers = {
'Content-Type': 'application/json',
'App-Id': '<App ID>',
'Authorization': 'Bearer ' + access_token
}
var requestBody = {
"scope": {
"key": "vAtom::vAtomType.template",
"value": "<publisher_fqdn>"
},
"filters": [
{
"filter_elems": [
{
"field": "vAtom::vAtomType.template",
"filter_op": "Match",
"value": "<publisher_fqdn>"
}
]
}
],
"return": {
"type": "*",
}
}
var requestBodyJSON = JSON.stringify(requestBody)
// Thunk middleware knows how to handle functions.
// It passes the dispatch method as an argument to the function,
// thus making it able to dispatch actions itself.
return function(dispatch) {
// First dispatch: the app state is updated to inform
// that the API call is starting.
dispatch(fetchRequest())
console.log('Sending get atoms request to Blockv...');
fetch(url, {
method: method,
body: requestBodyJSON,
headers: headers
}).then(response => {
responseCode = response.status
console.log(responseCode)
return response.json()
}).then(data => {
if (responseCode === 401) {
dispatch(bringToLogin("We've logged you out. Please reauthenticate"))
dispatch(fetchFailure('Failed to get atoms'))
} else if (responseCode === 200) {
var atoms = data.payload.results
dispatch(fetchSuccess('Retrieved atoms!')) // Array of template variations
if (from_refresh) {
dispatch(updateNotifier({
isOpen: true,
message: 'Successfully retrieved games!'
}))
}
dispatch(updateCurrAtoms({
atoms: atoms,
receivedAt: Date.now()
}))
}
}).catch(err => {
console.log(err)
dispatch(fetchFailure('Failed to get atoms'))
});
}
}
MyComponent.js
...
class GameStatsModal extends Component {
getDataAtoms = () => {
return this.props.atoms
}
/**
* Props for each row in table
*
* #memberof GameStatsModal
*/
setTrProps = (state, rowInfo, column, instance) => {
return {
style: {
marginBottom: 15
}
}
}
render () {
return (
<OuterContainer>
<StatsContainer>
<InnerContainer>
<img
src={RefreshIcon}
alt="Refresh List"
id="refreshGames"
className='refreshButton'
onClick={
() => {
store.dispatch(getAtoms({
access_token: store.getState().access_token,
from_refresh: true
}))
}
}
/>
<ReactTable
data={this.getDataAtoms()}
className='-highlight -striped gamesTable'
noDataText="Click Refresh to Load Games"
columns={columnsAtoms}
defaultSorted={[{id: 'created', desc: true}]}
getTrProps={this.setTrProps}
getTdProps={this.setTdProps}
pageSize={this.getDataAtoms().length}
showPagination={false}
resizable={false}
/>
</InnerContainer>
</StatsContainer>
</OuterContainer>
)
}
}
const mapStateToProps = state => ({
atoms: state.atoms.atoms
})
export default withRouter(connect(mapStateToProps)(GameStatsModal))
Again i know the updates are being made to store, so my question is am i somehow mutating the previous state somewhere? If not is it possible that since i have multiple dispatch calls being executed in actions, could they be interfering with each other and/or rerendering? not sure where else to look.
Looking forward to any suggestions, thank you!

i think you are mutating the state directly
try this one
switch (action.type) {
case UPDATE_CURR_ATOMS:
var mappedAtoms = action.atoms.map(atom => {
// directly returning the new object
return {
id: atom.id,
gameTitle: atom.private.GameTitle,
highscore: atom.private.HighScore,
highscoreOwner: atom.private.HighScoreOwner,
locationName: atom.private.LocationName,
scoreHistory: atom.private.ScoreHistory,
unpublished: atom.unpublished,
author: atom['vAtom::vAtomType'].author,
description: atom['vAtom::vAtomType'].description,
dropped: atom['vAtom::vAtomType'].dropped,
lat: atom['vAtom::vAtomType'].geo_pos.coordinates[1],
lng: atom['vAtom::vAtomType'].geo_pos.coordinates[0],
owner: atom['vAtom::vAtomType'].owner,
template: atom['vAtom::vAtomType'].template,
template_variation: atom['vAtom::vAtomType'].template_variation,
title: atom['vAtom::vAtomType'].title,
when_created: atom.when_created,
when_modified: atom.when_modified
}
})
return {
...state,
receivedAt: action.receivedAt,
atoms: mappedAtoms
}

Related

Pinia|Vue3 I can't access the property of the object that returned from the Pinia action

first of all I am using the Mockjs to simulate the backend data:
{
url: "/mockApi/system",
method: "get",
timeout: 500,
statusCode: 200,
response: { //
status: 200,
message: 'ok',
data: {
'onlineStatus|3': [{
'statusId': '#integer(1,3)',
'onlineStatusText': '#ctitle(3)',
'onlineStatusIcon': Random.image('20*20'),
'createTime': '#datetime'
}],
'websiteInfo': [{
'id|+1': 1,
}]
}
}
}
the data structure would be: https://imgur.com/a/7FqvVTK
and I retrieve this mock data in Pinia store:
import axios from "axios"
import { defineStore } from "pinia"
export const useSystem = defineStore('System', {
state: () => {
return {
systemConfig: {
onlineStatus: [],
},
}
},
actions: {
getSystemConfig() {
const axiosInstance = axios.interceptors.request.use(function (config) {
// Do something before request is sent
config.baseURL = '/mockApi'
return config
}, function (error) {
// Do something with request error
return Promise.reject(error);
})
axios.get('/system/').then(res => {
this.systemConfig.onlineStatus = res.data.data.onlineStatus
})
// console.log(res.data.data.onlineStatus)
axios.interceptors.request.eject(axiosInstance)
}
}
})
I use this store in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]
},
},
components: {UserOnlineStatus }
}
template in Profile.vue I import the child component userOnlineStatus.vue
<UserOnlineStatus :userCurrentOnlineStatus="userData.onlineStatus">
{{ showUserOnlineStatusText }}
</UserOnlineStatus>
here is what I have got https://imgur.com/fq33uL8
but I only want to get the onlineStatusText property of the returned object, so I change the computed code in the parent component Profile.vue:
export default {
setup() {
const systemConfigStore = useSystem()
systemConfigStore.getSystemConfig()
const { systemConfig } = storeToRefs(systemConfigStore)
return {
systemConfig,
}
},
computed: {
getUserOnlineStatusIndex() {
return this.userData.onlineStatus//this would be 1-3 int.
},
getUserOnlineStatus() {
return this.systemConfig.onlineStatus
},
showUserOnlineStatusText() {
return this.getUserOnlineStatus[this.getUserOnlineStatusIndex - 1]['onlineStatusText']//👀I chage it here!
},
},
components: {UserOnlineStatus }
}
but I will get the error in the console and it doesn't work:
https://imgur.com/Gb68Slk
what should I do if I just want to display the specific propery of the retrived data?
I am out of my wits...
I have tried move the store function to the child components, but get the same result.
and I google this issue for two days, nothing found.
Maybe it's because of I was trying to read the value that the Profile.vue hasn't retrieved yet?
in this case, how could I make sure that I have got all the value ready before the page rendered in vue3? Or can I watch this specific property changed, then go on rendering the page?
every UX that has data is coming from remote source (async data) should has spinner or skeleton.
you can use the optional chaining for safe access (if no time to await):
return this.getUserOnlineStatus?.[this.getUserOnlineStatusIndex - 1]?.['onlineStatusText']

Vuex getters has undefined data when I try to load data from API and use it in multiple components

I have a page component where I am making api call and storing the data in Vuex store through actions. This data has to be used at multiple places but everywhere I'm initially getting undefined data which loads after a few seconds asynchronously from the API. How should I use vuex getters asynchronously ?
Here's the code for my vuex store module :
import axios from 'axios';
const state = {
all_pokemon: {},
pokemon_details: {}
};
const getters = {
getAllPokemon: function(state) {
return state.all_pokemon;
},
getPokemonDetails: function(state) {
return state.pokemon_details;
}
};
const mutations = {
setAllPokemon: function(state, payload) {
return state.all_pokemon = payload;
},
setPokemon: function(state, payload) {
console.log('Pokemon details set with payload ', payload);
return state.pokemon_details = payload;
}
};
const actions = {
setPokemonAction: function({ commit }, passed_pokemon) {
axios.get('https://pokeapi.co/api/v2/pokemon/' + passed_pokemon)
.then((response) => {
console.log('Response data is : ', response.data);
});
commit('setAllPokemon', response.data);
},
setPokemonDetailAction: function({ commit }, passed_pokemon) {
console.log('Action method called..', passed_pokemon);
axios.get('https://pokeapi.co/api/v2/pokemon/' + passed_pokemon)
.then((response) => {
commit('setPokemon', response.data);
});
}
};
export default {
state,
getters,
mutations,
actions,
};
And code for the component where I want to get this data and pass it to other components :
<script>
import { mapGetters, mapActions } from 'vuex'
import axios from 'axios'
// Imported other components here
export default {
name: 'pokemon_detail_page',
data() {
return {
current_pokemon: this.$route.params.pokemon,
isLoading: false,
childDataLoaded: false,
search_pokemon: '',
sprites: [],
childData: 'False',
isAdded: false,
pokemon_added: 'none_display',
show: false
}
},
methods: {
...mapActions([
'setPokemonDetailAction',
'removePokemon'
]),
},
computed: {
...mapGetters([
'getPokemonDetails',
'getTeam'
])
},
components: {
Game_index,
PulseLoader,
PokemonComponent,
},
filters: {
},
watch: {
getTeam: function (val) {
},
getPokemonDetails: function(val) {
}
},
created() {
setTimeout(() => {
this.show = true;
}, 2000);
this.$store.dispatch('setPokemonDetailAction', this.current_pokemon)
.then(() => {
// const { abilities, name, order, species, } = {...this.getPokemonDetails};
})
},
mounted() {
},
}
</script>
And here's is the code for the template where I'm passing this data to multiple components :
<div v-if="show" class="pokemon_stats_container" :key="childData">
<ability-component
:abilities="getPokemonDetails.abilities"
>
</ability-component>
<sprites-component
:sprites="sprites"
>
</sprites-component>
<location-component
:location_area="getPokemonDetails.location_area_encounters"
:id="getPokemonDetails.id"
>
</location-component>
<stats-component
:stats="getPokemonDetails.stats"
>
</stats-component>
<game_index
:game_indices="getPokemonDetails.game_indices"
/>
<moves-component
:moves="getPokemonDetails.moves"
:pokemon_name="getPokemonDetails.name"
>
</moves-component>
</div>
As of now, I've adopted a roundabout way of doing this through setTimeout and setting a variable after 2 seconds so that data is available for other components to use. But, there has to be a more elegant way of handling vuex asynchronous data. Someone please help me in this.
Your first commit is not in the promise
commit('setAllPokemon', response.data);
make this :
axios.get('https://pokeapi.co/api/v2/pokemon/' + passed_pokemon)
.then((response) => {
console.log('Response data is : ', response.data);
commit('setAllPokemon', response.data);
});
try to use in your vue component
$forceUpdate()
when your request is end for reload the data

How to prevent Vue from loading the data multiple times?

I'm currently practicing Vue with Vuex and the REST API architecture (with Django). I wanna retrieve the data from by database and store the data in my global state in order to access it throughout my Vue components. It works fine so far but there is one weird thing I do not understand.
When I start my application I have the homepage where currently nothing is displayed. I click on the menu item "Contacts" and the contacts component loads (router-view) (API GET call is executed) and displays the table of all the created contacts and shows it properly (source of truth is now my global state). I can edit, delete and view a contact.The contacts are now stored in the global state as well.
The problem: Everytime I load the contact component the mounted() lifecycle gets called (which makes sense) and loads the contacts from the API inside the state so the whole lists gets dupblicated over and over. I just want Vue to make a GET request only once and then access the state's data (where to contacts are stored now).
Another scenario is when I update a contact and click back to the contacts menu item the list contains the old contact and the updated one but when I refresh the page it is fine.
Thanks!
MY CODE
state.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import { apiService } from "../utils/api.service.js";
export const store = new Vuex.Store({
state: {
contacts: []
},
mutations: {
initContacts_MU: (state, data) => {
state.contacts.push(...data);
},
updateContact_MU: (state, data) => {
let getContact = state.contacts.filter(contact => contact.id === data.id);
let getContactIndex = state.contacts.indexOf(getContact);
state.contacts.splice(getContactIndex, 1, data);
},
deleteContact_MU: (state, data) => {
let getContact = state.contacts.filter(contact => contact.id === data.id);
let getContactIndex = state.contacts.indexOf(getContact);
state.contacts.splice(getContactIndex, 1);
},
createContact_MU: (state, data) => {
state.contacts.unshift(data);
}
},
actions: {
initContacts_AC({ commit }) {
let endpoint = "api/contacts/";
apiService(endpoint).then(data => {
commit("initContacts_MU", data);
});
},
updateContact_AC({ commit }, contact) {
let endpoint = "/api/contacts/" + contact.slug + "/";
apiService(endpoint, "PUT", contact).then(contact => {
commit("updateContact_MU", contact);
});
},
deleteContact_AC({ commit }, contact) {
let endpoint = "/api/contacts/" + contact.slug + "/";
apiService(endpoint, "DELETE", contact).then(contact => {
commit("deleteContact_MU", contact);
});
},
createContact_AC({ commit }, contact) {
let endpoint = "/api/contacts/";
apiService(endpoint, "POST", contact).then(contact => {
commit("createContact_MU", contact);
});
}
},
getters: {
contacts: state => {
return state.contacts;
}
}
});
ContactList.vue
<script>
import Contact from "../../components/Contacts/Contact.vue";
import { mapGetters, mapActions } from "vuex";
export default {
name: "ContactList",
components: {
Contact
},
computed: {
...mapGetters(["contacts"])
},
methods: {
...mapActions(["initContacts_AC"])
},
mounted() {
this.initContacts_AC();
}
};
</script>
Just check if there're contacts which are already retrieved from backend.
computed: {
...mapGetters(["contacts"])
},
methods: {
...mapActions(["initContacts_AC"])
},
mounted() {
if (this.contacts && this.contacts.length > 0) return; // already fetched.
this.initContacts_AC();
}
EDIT:
updateContact_MU: (state, data) => {
const contactIndex = state.contacts.findIndex(contact => contact.id === data.id);
if (contactIndex < 0) return;
state.contacts.splice(contactIndex, 1, data);
}

Vue.js: mutation for deleting a comment

I have been working on the feature of comment deleting and came across a question regarding a mutation for an action.
Here is my client:
delete_post_comment({post_id, comment_id} = {}) {
// DELETE /api/posts/:post_id/comments/:id
return this._delete_request({
path: document.apiBasicUrl + '/posts/' + post_id + '/comments/' + comment_id,
});
}
Here is my store:
import Client from '../client/client';
import ClientAlert from '../client/client_alert';
import S_Helper from '../helpers/store_helper';
const state = {
comment: {
id: 0,
body: '',
deleted: false,
},
comments: [],
};
const actions = {
deletePostComment({ params }) {
// DELETE /api/posts/:post_id/comments/:id
document.client
.delete_post_comment({ params })
.then(ca => {
S_Helper.cmt_data(ca, 'delete_comment', this);
})
.catch(error => {
ClientAlert.std_fail_with_err(error);
});
},
};
delete_comment(context, id) {
context.comment = comment.map(comment => {
if (!!comment.id && comment.id === id) {
comment.deleted = true;
comment.body = '';
}
});
},
};
export default {
state,
actions,
mutations,
getters,
};
I am not quite sure if I wrote my mutation correctly. So far, when I am calling the action via on-click inside the component, nothing is happening.
Guessing you are using vuex the flow should be:
according to this flow, on the component template
#click="buttonAction(someParams)"
vm instance, methods object:
buttonAction(someParams) {
this.$store.dispatch('triggerActionMethod', { 'something_else': someParams })
}
vuex actions - Use actions for the logic, ajax call ecc.
triggerActionMethod: ({commit}, params) => {
commit('SOME_TRANSATION_NAME', params)
}
vuex mutations - Use mutation to make the changes into your state
'SOME_TRANSATION_NAME' (state, data) { state.SOME_ARG = data }

Vuex how to pass state/getter in data prop

I retrieved my data from database using axios under my vuex
const state = {
giveaways: null
}
const actions = {
getGiveAways : ({commit}) =>{
axios({
url : '/prod/api/thresholds_settings',
method: 'post',
data : {
},
config: 'JOSN'
})
.then(response=>{
if(response.status == 200){
//console.log(response.data.total_giveaways);
commit('SET_GIVEAWAYS', response.data.total_giveaways)
}
})
.catch(error=>{
if(error.response){
console.log('something happened')
}
});
}
}
const mutations = {
SET_GIVEAWAYS : (state, obj)=>{
state.giveaways = obj
}
}
const getters = {
doneGiveAways(state){
return state.giveaways
}
}
In my Dashboard.vue I have
import {mapState,mapGetters} from 'vuex'
export default{
data: () => ({
cards: [],
giveaways: ''
}),
computed:{
...mapState({
Giveaway: state => state.Threshold.giveaways
}),
doneGiveAways(){
return this.$store.getters.doneGiveAways
}
},
ready(){
//giveaways: this.Giveaways
//console.log(this.Giveaways);
},
created(){
const self = this
this.cards[0].result_val = 2
this.cards[2].result_val = 2;
},
mounted(){
this.$store.dispatch('getGiveAways');
console.log(this.giveaways);
}
}
My problem is I need to pass the value from the mapState Giveaway to my returning data giveaways: '' so when page fires I can get the response value using this.giveaways. If I just call {{ Giveaway }} in my html it shows the value. But I need to make something like this.giveaways = this.$store.state.Thresholds.giveaways
I would use Stephan-v's recommendation and delete the local copy of giveaways. But I don't know what your specific reason is for declaring an extra copy of giveaways, so here is a solution that will work:
In your Dashboard.vue add:
export default {
...
watch: {
Giveaway(value) {
this.giveaways = value
}
},
...
}
Just delete the giveaways property from your data Object and rename the computed doneGiveAways to giveaways and you are done.
There is no need for a local component giveaway data property in this scenario.

Categories

Resources