i want to watch when a mutation called and updated a status. i make a component to show database table count when api called.
this is my store i wrote
const state = {
opportunity: ""
}
const getters = {
countOpportunity: state => state.opportunity
}
const actions = {
// count opportunity
async totalOpportunity({ commit }) {
const response = await axios.get(count_opportunity)
commit("setOpportunity", response.data)
},
}
const mutations = {
setOpportunity: (state, value) => (state.opportunity = value)
}
i want to show this getter value when this mutation called in another component name Opportunity.vue file.
i showed database count values in file name Dashboard.vue
i wrote it like this.
computed: {
...mapGetters(["countOpportunity"])
},
watch: {},
mounted() {
//do something after mounting vue instance
this.$store.watch(() => {
this.$store.getters.countOpportunity;
});
},
created() {
this.totalOpportunity();
},
methods: {
...mapActions(["totalOpportunity"])
}
and showed my view like this.
<div class="inner">
<h3>{{ countOpportunity }}</h3>
<p>Opportunities</p>
</div>
when api called and count increase shows my mutations. but my view value not updated (countOpportunity). any one can help me to fix this.
The issue here (most likely) is that the value of response.data is an object or an array. You've initially defined opportunity as '' which is not an observable object or array. You have 2 choices:
Redefine it as an empty object or array, depending on the response:
opportunity: [] // or {}
Otherwise, use Vue.set() to apply reactivity when changing it:
(Vue.set(state, 'opportunity', value))
Related
I'm working on a composable that's meant to encapsulate some logic with Vue3 and Vuex. The composable is working with a "feed" that is liable to change in the future, and comes from Vuex.
I'd like the composable to return the status of that feed when the feed changes as a computed value.
However, I'm unclear on how to fetch/wrap the value from Vuex so this computed property will change when the value in Vuex changes. For instance, at the top of the composable, I'm passing in the ID of the feed, fetching it from Vuex, and then using it in the composable like this:
const feed = store.getters['feeds/getFeedById'](feedId)
I'm then using the feed in a computed, inside of the composable, like this:
const feedIsReady = computed(() => feed.info.ready ? 'READY' : 'NOT READY')
However, when I change the feed object in Vuex via a mutation elsewhere in the application, the feed inside the composable does not change.
I've tried wrapping the feed in a reactive call and it's individual properties with toRefs but those approaches only provide reactivity within the composable itself, and don't capture changes from Vuex.
How would one wrap the feed from Vuex to provide reactivity? I need the changes in Vuex to propagate to my composable somehow.
Did you try to use vuex getter in your composable with computed property:
const feed = computed(() => store.getters['feeds/getFeedById'](feedId));
I see you are using store.getters['feeds/getFeedById'](feedId), which, AFAICT, means that the getter store.getters['feeds/getFeedById'] returns a function, and that the feedId is a parameter passed to the returned function.
If this is the case, this probably won't work, because that function likely doesn't return a reactive value.
I can't see the vuex code so don't know for sure, but assuming this is the case I would do something like this
const store = Vuex.createStore({
state() {
return {
feeds: {
1334:{info:{ready:false}},
}
}
},
getters: {
feeds(state) {
return state.feeds
}
},
mutations: {
change(state) {
state.feeds[1334].info.ready = !state.feeds[1334].info.ready;
}
}
});
function watchFeedState(feedid) {
const feeds = Vue.computed(() => store.getters.feeds)
const isReady = (v) => v && v.info && v.info.ready ? 'READY' : 'NOT READY';
const feedReady = Vue.ref(isReady(feeds[feedid]));
Vue.watch(store.getters.feeds, (v) => {
feedReady.value = isReady(v[feedid])
});
return feedReady
}
const app = Vue.createApp({
setup() {
const store = Vuex.useStore();
const isReady = watchFeedState(1334); // <= the feed from a higher order function
return {
isReady,
change: ()=>{store.commit('change')}
}
}
});
app.use(store);
app.mount("#app");
<script src="https://unpkg.com/vue#3.2.37/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vuex#4.0.0/dist/vuex.global.js"></script>
<div id="app">
<button #click="change">toggle state</button>
{{ isReady }}
</div>
This creates a higher order function to watch a specific feed id. This will watch the feeds getter for every change, but only update for a provided feedid.
React JS class component
I know there have been many posts on this subject but I can't seem to get this scenario to work.
Basically on my HandleClickSave event I want to update an item in my object in state without affecting the other values and then passing this updated oblect onto my service to get updated in the db.
The 'item' in question is the 'design' from the (unLayer) React-email-editor.
Problem is after the service is run in 'HandleClickSave' point 3 below, the receiving field 'DesignStructure' in the db is NULL every time. The other two fields are fine as these are saved to state object elsewhere.
Part of the problem is that the Email-Editor doesn't have an 'onChange' property which is where I would normally update the state. The other two values in the object are input texts and they do have an onChange which is how their state counterparts are updated.
This is the object 'NewsletterDesign':
{
"DesignId": "1",
"DesignName": "DesignLayout 1 Test",
"DesignStructure": null
}
In my React class component...
this.state = {
NewsletterDesign: {}
}
And the HandleClickSave event....
HandleClickSave () {
const { NewsletterDesign } = this.state
this.editor.saveDesign((design) => {
this.setState(prevState => ({
NewsletterDesign: {
...prevState.NewsletterDesign,
DesignStructure: design
}
}));
// Update to database passing in the object 'NewsletterDesign'. Field 'DesignStructure' in db is null every time, but other two fields are updated.
NewsletterService.UpdateCreateNewsletterDesign(NewsletterDesign)
etc....etc..
React's setState is not update immediately. read more here.
You can simply do it inside setState by
this.setState(prevState => {
const newState = {
NewsletterDesign: {
...prevState.NewsletterDesign,
DesignStructure: design
}
};
NewsletterService.UpdateCreateNewsletterDesign(newState.NewsletterDesign);
return newState;
});
The setState is an async operation. Meaning, that it's not guaranteed that the new state that you have updated will be accessible just after the state is updated. You can read more here
So in such cases, one of the way is to do the required operation first and then use the result at multiple places.
HandleClickSave () {
const { NewsletterDesign } = this.state
this.editor.saveDesign((design) => {
let newNewsletterDesign = { ...NewsletterDesign,
DesignStructure: design
};
this.setState(newNewsletterDesign);
NewsletterService.UpdateCreateNewsletterDesign(newNewsletterDesign)
i want to ask something, i have a custom store like
const BlaBla = (Data) => {
const { subscribe, set, update } = writable(Data);
return {
subscribe,
update,
set,
setData: (NewData) => {
set(NewData)
},
getData: () => {
return <<<<<<< "Here lies the problem, how i can get the "newData"?."
}
}
}
i will explaying the scenario, im creating a script for a fivem server and im using svelte, i create a store that get a Vehicle with some properties like Name, Last Name, Plate and bla bla, i create the setData(Vehicle) and pass a set(Vehicle) then in another method i want to "get" the plate only, one solution i did was creating a variable in the scope and instead of a set i did an update like this
const VehicleStore = (Vehicle) => {
let Data = {} //Variable inside the scope
const { subscribe, set, update } = writable(Vehicle);
return {
subscribe,
update,
set,
setData: (NewData) => {
update((s) => {
s = NewData
Data = s
return s
})
},
getData: () => {
return Data.Plate
}
}
}
i don't know if this is the actual solution, i think im missing something
Svelte exports a get function that can be used to resolve the value of a store once (it is syntactic sugar around subscribe).
So first you have to get the value of the store, then you can access its property:
import { get } from 'svelte/store';
// ...
const store = writable(Data);
const { subscribe, set, update } = store;
// ...
return get(store).Plate
Note that accessing data like this will not be reactive because there is no persistent subscription to the store. You are generally not meant to use stores like that.
Instead you usually would use the store in a component's markup using auto subscriptions via $:
$VehicleStore.Plate
I'm getting TypeErrors when using NgRx select functions when accessing nested properties.
I have my root store configured in app.module.ts like this:
StoreModule.forRoot({ app: appReducer }),
where app reducer is just a standard reducer. It sets the state correctly; I can see that in the redux dev tools. The selectors for some nested properties that are erroring are:
const getAppFeatureState = createFeatureSelector<IAppState>('app');
export const getAppConfig = createSelector(getAppFeatureState, state => {
return state.appConfig.data;
});
export const getConfigControls = createSelector(getAppConfig, state => {
console.log({ state }) // logs values from initial state
return state.controls;
});
export const getConfigDropdowns = createSelector(
getConfigControls,
state => state.dropdowns,
);
When I subscribe to these selectors in app.compontent.ts like this
ngOnInit() {
this.store.dispatch(new appActions.LoadAppConfig());
this.store
.pipe(select(appSelectors.getConfigDropdowns))
.subscribe(data => {
console.log('OnInit Dropdowns Data: ', data);
});
}
app.component.ts:31 ERROR TypeError: Cannot read property 'dropdowns' of null
at app.selectors.ts:18
When I add logging to the selectors higher up the chain, I can see that the only elements logged are the initialState values, which are set to null. I don't think this selector function should fire until the value changes from its initial value. But since it doesn't, its unsurprising that I'm getting this error, since it is trying to access a property on null. Is it a necessity that initialState contain the full tree of all potential future nested properties in order not to break my selectors?
How can I prevent this selector firing when its value is unchanged?
Also, Is the StoreModule.forRoot configured correctly? It is somewhat puzzling to me that creating a "root" store, creates the app key in my redux store parallel to my modules' stores, ie, the module stores are not underneath app.
Edit:
Adding general structure of app.reducer.ts. I use immer to shorten boilerplate necessary for updating nested properties, however I have tried this reducer also as the more traditional kind with spread operator all over the place and it works identically.
import produce from 'immer';
export const appReducer = produce(
(
draftState: rootStateModels.IAppState = initialState,
action: AppActions,
) => {
switch (action.type) {
case AppActionTypes.LoadAppConfig: {
draftState.appConfig.meta.isLoading = true;
break;
}
/* more cases updating the properties accessed in problematic selectors */
default: {
return draftState; // I think this default block is unnecessary based on immer documentation
}
}
}
Edit: Add initialState:
const initialState: rootStateModels.IAppState = {
user: null,
appConfig: {
meta: {isError: false, isLoading: false, isSuccess: false},
data: {
controls: {
dropdowns: null,
}
},
},
};
Because you updated your question the answer is https://www.learnrxjs.io/learn-rxjs/operators/filtering/distinctuntilchanged
it allows to emit values only when they have been changed.
store.pipe(
map(state => state.feature.something),
distinctUntilChanged(),
)
requires state.feautre.something to have been changed.
The right way would be to use createSelector function that returns memorized selectors that works in the same way as distinctUntilChanged.
You can use filter operator to make sure it emits values only for valid values, and after that you can use pluck operator to emit value of respective nested property.
store.pipe(
filter(value => state.feature.something),
pluck('feature', 'something'),
)
The dispatch method is async.
So:
ngOnInit() {
this.store.dispatch(new appActions.LoadAppConfig());
this.store
.pipe(select(appSelectors.getConfigDropdowns))
.subscribe(data => {
console.log('OnInit Dropdowns Data: ', data);
});
}
Here the subscription runs faster than the dispatch so the select returns with null value from your initial state. Simply check this in the selector or add initial state. EX:
const getAppFeatureState = createFeatureSelector<IAppState>('app');
export const getAppConfig = createSelector(getAppFeatureState, state => {
return state.appConfig.data;
});
export const getConfigControls = createSelector(getAppConfig, state => {
console.log({ state }) // logs values from initial state
return state.controls;
});
export const getConfigDropdowns = createSelector(
getConfigControls,
state => state ? state.dropdown : null,
);
Ok, I took a look again in code and updated my answer.
Can you try below given sample.
this.store
.pipe(
// Here `isStarted` will be boolean value which will enable and disable selector.
//This can be derived from initial state, if null it wont go to next selector
switchMap(data => {
if (isStarted) {
return never();
} else {
return of(data);
}
}),
switchMap(data => select(appSelectors.getConfigDropdowns))
)
.subscribe(data => {
console.log("OnInit Dropdowns Data: ", data);
});
I'm using React Apollo to query all records in my datastore so I can create choices within a search filter.
The important database model I'm using is Report.
A Report has doorType, doorWidth, glass and manufacturer fields.
Currently when the query responds, I'm passing allReports to multiple dumb components which go through the array and just get the unique items to make a selectable list, like so..
const uniqueItems = []
items.map(i => {
const current = i[itemType]
if (typeof current === 'object') {
if (uniqueItems.filter(o => o.id !== current.id)) {
return uniqueItems.push(current)
}
} else if (!uniqueItems.includes(current)) {
return uniqueItems.push(current)
}
return
})
Obviously this code isn't pretty and it's a bit overkill.
I'd like to dispatch an action when the query returns within my SidebarFilter components. Here is the query...
const withData = graphql(REPORT_FILTER_QUERY, {
options: ({ isPublished }) => ({
variables: { isPublished }
})
})
const mapStateToProps = ({
reportFilter: { isPublished }
// filterOptions: { doorWidths }
}) => ({
isAssessment
// doorWidths
})
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
resetFilter,
saveFilter,
setDoorWidths,
handleDoorWidthSelect
},
dispatch
)
export default compose(connect(mapStateToProps, mapDispatchToProps), withData)(
Filter
)
The Redux action setDoorWidths basically does the code above in the SidebarFilter component but it's kept in the store so I don't need to re-run the query should the user come back to the page.
It's very rare the data will update and the sidebar needs to change.
Hopefully there is a solution using the props argument to the graphql function. I feel like the data could be taken from ownProps and then an action could be dispatched here but the data could error or be loading, and that would break rendering.
Edit:
Query:
query ($isPublished: Boolean!){
allReports(filter:{
isPublished: $isPublished
}) {
id
oldId
dbrw
core
manufacturer {
id
name
}
doorWidth
doorType
glass
testBy
testDate
testId
isAssessment
file {
url
}
}
}
While this answer addresses the specific issue of the question, the more general question -- where to dispatch a Redux action based on the result of a query -- remains unclear. There does not, as yet, seem to be a best practice here.
It seems to me that, since Apollo already caches the query results in your store for you (or a separate store, if you didn't integrate them), it would be redundant to dispatch an action that would also just store the data in your store.
If I understood your question correctly, your intent is to filter the incoming data only once and then send the result down as a prop to the component's stateless children. You were on the right track with using the props property in the graphql HOC's config. Why not just do something like this:
const mapDataToProps = ({ data = {} }) => {
const items = data
const uniqueItems = []
// insert your logic for filtering the data here
return { uniqueItems } // or whatever you want the prop to be called
}
const withData = graphql(REPORT_FILTER_QUERY, {
options: ({ isPublished }) => ({
variables: { isPublished }
}),
props: mapDataToProps,
})
The above may need to be modified depending on what the structure of data actually looks like. data has some handy props on it that can let you check for whether the query is loading (data.loading) or has errors (data.error). The above example already guards against sending an undefined prop down to your children, but you could easily incorporate those properties into your logic if you so desired.