Is it possible to call local functions from imported in Vuex? - javascript

I have simple file called _mixin.js which consists of:
const mutations = {
_set(state, data) {
for (let key in data) {
if (key in state) {
state[key] = data[key];
}
}
},
_reset(state) {
const s = initialState();
Object.keys(s).forEach(key => {
state[key] = s[key];
});
}
};
export default {
mutations
};
What I'm trying to do is share this two methods between all existing modules mutation like this:
import _MIXINS from 'store/modules/_mixins';
function initialState() {
return {
id: null,
email: null,
password: null,
name: null,
};
}
const state = initialState();
const mutations = {
..._MIXINS.mutations,
setId(state, id) {
state.id = id;
}
};
The problem is that browser says it cant find function initialState as its not in same file.

Just do like this:
// sharedLogic.js
export function initialState () {
// your logic
}
// store module
import { initialState } from '<pathTo sharedLogic.js>'
const state = initialState();
// mixin module
import { initialState } from '<pathTo sharedLogic.js>'
const mutations = {
...
_reset(state) {
const s = initialState();
Object.keys(s).forEach(key => {
state[key] = s[key];
});
}
};

Related

Google Tag Manager breaks Next.js UI

I'm using a package Google Tag Manager plugin for analytics:
https://www.npmjs.com/package/#analytics/google-tag-manager
It works fine with localhost, but while try to connect with deployed website, UI is breaking.
import googleTagManager from '#analytics/google-tag-manager';
import Analytics from 'analytics';
import { useContext } from 'react';
import { AuthContext } from '#/providers';
/* Fix - Property does not exist on type Window in TypeScript */
declare global {
interface Window {
dataLayer?: any;
}
}
const analytics = Analytics({
app: 'test',
plugins: [
googleTagManager({
// container Id goes here
containerId: GTM12345,
}),
],
});
/* Track a page view */
export const trackGTMPageEvent = (eventPageName: any) => {
const trackedEventPageObj = {};
for(const [key, value] of Object.entries(eventPageName)) {
if (value) {
Object.assign(trackedEventPageObj, { [key]: value });
}
}
analytics.page(eventPageName, { ...trackedEventPageObj });
};
/* Track a custom events */
export const trackGTMEvent = (eventName: any) => {
const trackedEventObj = {};
for (const [key, value] of Object.entries(eventName)) {
if (value) {
Object.assign(trackedEventObj, { [key]: value });
}
}
if (eventName) {
analytics.track(eventName, { ...trackedEventObj });
window.dataLayer.push({
event: eventName,
...trackedEventObj,
});
}
};
/* Optional : */ /* Identify a visitor */
export const trackGTMPVisitorEvent = (visitorData: any) => {
const trackedEventVisitorObj = {};
for (const [key, value] of Object.entries(visitorData)) {
if (value) {
Object.assign(trackedEventVisitorObj, { [key]: value });
}
}
const { state: getAuth } = useContext(AuthContext);
const { userDetails } = getAuth;
if (visitorData) {
analytics.identify(`${userDetails?.id}`, {
firstName: userDetails?.firstName,
lastName: userDetails?.lastName,
email: userDetails?.email,
dob: userDetails?.dob,
});
}
};

How can I manage state in vuex reactively?

I am trying to solve a problem in my vuex store. I write two different actions in my store. One action is reactive and the other not. But I need the loadSlidesEach() in reactivity, so the data are updated. I cant find the mistake.
My store:
const store = new Vuex.Store({
state: {
loadedSlides: []
},
mutations: {
setSlides(state, slides) {
state.loadedSlides = slides
},
setSlidesPush(state, slide) {
state.loadedSlides.push(slide)
}
},
getters: {
loadedSlides(state) {
return state.loadedSlides
}
},
actions: {
loadSlides({ commit, getters, state }) {
firebase.database().ref('/slides/').on('value', snapshot => {
const slidesArray = []
const obj = snapshot.val()
for (const key in obj) {
slidesArray.push({ ...obj[key], id: key })
}
commit('setSlides', slidesArray)
})
},
loadSlidesEach({ commit, getters, state }, id) {
firebase.database().ref('/slides/' + id).on('value', snapshot => {
const slide = snapshot.val()
commit('setSlidesPush', { ...slide })
})
}
}
})
My component 1: Array from slides() is reactive
export default {
computed: {
slides() {
return this.$store.getters.loadedSlides
}
},
created() {
this.$store.dispatch('loadSlides')
}
}
My component 2: Array from slides() is not reactive
export default {
computed: {
slides() {
return this.$store.getters.loadedSlides
}
},
created() {
const slides = ['1','2','3']
for (let i = 0; i < slides.length; i++) {
this.$store.dispatch('loadSlidesEach', slides[i])
}
}
}
I think the problem is something with the inital state or the mutation with push(). Any advices?
Update:
The two actions are only different in the mutations. So what is the best way to set the state in vuex? The store get confused if I call the action loadSlidesEach() in a loop.
Don't use await and then together. Use one or another:
loadSlidesEach: async({ commit, getters }, id) => {
const data = await firebase.database().ref('/slides/' + id).once('value')
const slide = data.val()
commit('setSlidesPush', { ...slide })
}
do you try to use mapState from vuex ?:
import {mapState} from 'vuex'
export default {
computed: {
...mapState(['loadedSlides '])
}
}
now you can use loadedSlides in component.
I found a working solution for my problem.
Change the mutation:
setSlidesPush(state, addedSlide) {
const slideIndex = state.loadedSlides.findIndex(slide => slide.id === addedSlide.id)
if (slideIndex !== -1) {
Vue.set(state.loadedSlides, slideIndex, addedSlide)
} else {
state.loadedSlides.push(addedSlide)
}
}

Use Vuex in Nuxt

I was able to fetch data and display them using Nuxt's Fetch API, but I want to utilize Vuex instead.
store/index.js:
import Axios from 'axios'
export const getters = {
isAuthenticated (state) {
return state.auth.loggedIn
},
loggedInUser (state) {
return state.auth.user
}
}
export const state = () => ({
videos: []
})
export const mutations = {
storeVideos (state, videos) {
state.videos = videos
}
}
export const actions = {
async getVideos (commit) {
const res = await Axios.get(`https://api.themoviedb.org/3/movie/popular?api_key=${process.env.API_SECRET}&page=${this.currentPage}`)
commit('storeVideos', res.data)
}
}
pages/test.vue:
<template>
<Moviecards
v-for="(movie, index) in $store.state.videos"
:key="index"
:movie="movie"
:data-index="index"
/>
</template>
<script>
...
fetch ({ store }) {
store.commit('storeVideos')
},
data () {
return {
prevpage: null,
nextpage: null,
currentPage: 1,
pageNumbers: [],
totalPages: 0,
popularmovies: []
}
},
watch: {
},
methods: {
next () {
this.currentPage += 1
}
}
}
...
The array returns empty when I check the Vue Dev Tools.
In fetch(), you're committing storeVideos without an argument, which would set store.state.videos to undefined, but I think you meant to dispatch the getVideos action:
export default {
fetch({ store }) {
// BEFORE:
store.commit('storeVideos')
// AFTER:
store.dispatch('getVideos')
}
}
Also your action is incorrectly using its argument. The first argument is the Vuex context, which you could destructure commit from:
export const actions = {
// BEFORE:
async getVideos (commit) {} // FIXME: 1st arg is context
// AFTER:
async getVideos ({ commit }) {}
}

Parameter 'initialState' cannot be referenced in its initializer

In my ReactJS / Typescript app, I'm getting the following error in my store.ts:
Parameter 'initialState' cannot be referenced in its initializer.
interface IinitialState {
fiatPrices: [];
wallets: [];
defaultCurrency: string;
}
const initialState = {
fiatPrices: [],
wallets: [],
defaultCurrency: ''
}
...
export function initializeStore (initialState:IinitialState = initialState) {
return createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
Anyone else run into this issue? Currently having to rely on // #ts-ignore
Entire store.ts file:
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'
interface IinitialState {
fiatPrices: [];
wallets: [];
defaultCurrency: string;
}
const initialState = {
fiatPrices: [],
wallets: [],
defaultCurrency: ''
}
export const actionTypes = {
GET_PRICES: 'GET_PRICES'
}
// REDUCERS
export const reducer = (state = initialState, action: any) => {
switch (action.type) {
case actionTypes.GET_PRICES:
return state
default:
return state
}
}
// MOCK API
export async function getProgress(dispatch: any) {
try {
const priceList = await fetchPrices();
return dispatch({ type: actionTypes.GET_PRICES, payload: priceList })
}
catch (err) {
console.log('Error', err);
}
}
// Wait 1 sec before resolving promise
function fetchPrices() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ progress: 100 });
}, 1000);
});
}
// ACTIONS
export const addLoader = () => (dispatch: any) => {
getProgress(dispatch);
}
// #ts-ignore
export function initializeStore (initialState:IinitialState = initialState) {
return createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
withReduxStore lib file
import React from 'react'
import { initializeStore, IinitialState } from '../store'
const isServer = typeof window === 'undefined'
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
function getOrCreateStore (initialState: IinitialState) {
// Always make a new store if server, otherwise state is shared between requests
if (isServer) {
return initializeStore(initialState)
}
// Create store if unavailable on the client and set it on the window object
// Waiting for (#ts-ignore-file) https://github.com/Microsoft/TypeScript/issues/19573 to be implemented
// #ts-ignore
if (!window[__NEXT_REDUX_STORE__]) {
// #ts-ignore
window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
}
// #ts-ignore
return window[__NEXT_REDUX_STORE__]
}
// #ts-ignore
export default App => {
return class AppWithRedux extends React.Component {
// #ts-ignore
static async getInitialProps (appContext) {
// Get or Create the store with `undefined` as initialState
// This allows you to set a custom default initialState
const reduxStore = getOrCreateStore()
// Provide the store to getInitialProps of pages
appContext.ctx.reduxStore = reduxStore
let appProps = {}
if (typeof App.getInitialProps === 'function') {
appProps = await App.getInitialProps(appContext)
}
return {
...appProps,
initialReduxState: reduxStore.getState()
}
}
// #ts-ignore
constructor (props) {
super(props)
this.reduxStore = getOrCreateStore(props.initialReduxState)
}
render () {
return <App {...this.props} reduxStore={this.reduxStore} />
}
}
}
function initializeStore (initialState:IinitialState = initialState) { ... }
is not valid by any means, not just in TypeScript. It's incorrect to suppress the error with #ts-ignore.
initialState parameter shadows the variable of the same name from enclosing scope, so default parameter value refers the parameter itself. This will result in discarding default parameter value with ES5 target and in an error with ES6 target.
The parameter and default value should have different names:
function initializeStore (initialState:IinitialState = defaultInitialState) { ... }
Notice that the use of defaultInitialState isn't needed in a reducer, due to how initial state works. Initial state from createStore takes precedence if combineReducers is not in use.

Redux Persist: some part of state not being persisted

I'm using redux with redux-persist and redux-thunk.
Only some part of state is not being persisted. what could be the reason? is it a way to force persist the current state? When I call reducers, state gets updated. but when I reload the app, some part of state is still the old one. (empty)
I thought it is taking some parts of state from initialstate, so I added some entries to initialstate, even so, it's returning empty objects. not getting them from initialState.
Thanks in advance.
only discussionStudents gets persisted
Store setup:
import React from "react";
import { View, AsyncStorage } from 'react-native';
import { applyMiddleware, createStore, compose } from 'redux';
import { Provider } from 'react-redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import { reducer } from './reducers';
import Container from './Container';
const middlewares = [thunk];
const logger = createLogger();
middlewares.push(logger);
const store = createStore(reducer, compose(
applyMiddleware(...middlewares)
), autoRehydrate({ log: true }));
persistStore(store, {storage: AsyncStorage});
const Root = () => (
<Provider store={store}>
<Container />
</Provider>
);
export default Root;
parts of the reducer :
import {REHYDRATE} from 'redux-persist/constants';
export const types = {
SYNCHRONISE_DISCUSSIONS: 'SYNCHRONISE_DISCUSSIONS'
};
export const actionCreators = {
synchroniseDiscussions: (args) => {
return dispatch => {
/// Call API
synchroniseDiscussionsAPI()
.then((res) => {
return dispatch(synchroniseDiscussions(res))
})
.catch((e) => {
console.log(e)
})
}
}
}
const synchroniseDiscussions = (args) => {
return {type: types.SYNCHRONISE_DISCUSSIONS, payload: args}
}
const initialState = {
rehydrated: false,
discussionStudents: [],
discussionGroups: [],
discussionsMessages: [],
discussionParticipants: []
}
export const reducer = (state = initialState, action) => {
const {
discussionStudents,
discussionGroups,
discussionsMessages,
discussionParticipants
} = state;
const {type, payload} = action;
switch (type) {
case types.SYNCHRONISE_DISCUSSIONS:
{
const oldStudents = discussionStudents
const newStudents = payload.discussionStudents
var parsedStudents = []
oldStudents.forEach((old, i)=>{
if(newStudents.findIndex(newstd => newstd.userId == old.userId) < 0){
parsedStudents.push({
...old,
status: 'inactive'
})
}
})
newStudents.forEach((newStudent, i)=>{
if(parsedStudents.findIndex(pstd => pstd.userId == newStudent.userId) < 0){
parsedStudents.push({
...newStudent,
status: 'active'
})
}
})
var newdiscussionParticipants = payload.discussionParticipants
var newdiscussionGroups = payload.discussionGroups
return Object.assign({}, state, {
discussionStudents: parsedStudents,
discussionParticipants: newdiscussionParticipants,
discussionGroups: newdiscussionGroups
})
}
case REHYDRATE:
{
return {
...state,
rehydrated: true
}
}
}
return state
}
I've found the issue. Issue was that I was using for loops inside the function. and promise was being resolved before the loop finishes. Issue solved by replacing the built in javascript loop with a custom asynchronous loop :
const asyncLoop = (iterations, func, callback) => {
var index = 0;
var done = false;
var loop = {
next: function() {
if (done) {
return;
}
if (index < iterations) {
index++;
func(loop);
} else {
done = true;
callback();
}
},
iteration: function() {
return index - 1;
},
break: function() {
done = true;
callback();
}
};
loop.next();
return loop;
}

Categories