What is the best way to update the state with signalr? At this moment I dispatching data inside on method callback and accessing it using useSelector inside other component.
I wonder if this is the optimal approach.
useEffect(()=> {
const connection = $.hubConnection("http://x.x.x.x/signalr", { useDefaultPath: false })
const proxy = connection.createHubProxy("exampleHub")
proxy.on("action", function(data) {
dispatch(setStatus(data))
}
})
Slice.js:
export const monitorSlice = createSlice({
name: "example",
initialState: {
status: {},
},
reducers: {
setStatus(state, {payload}) => {
state.status = payload
}
}
});
Your approach will work,
Here is other approach that you can have:
Using your own middleware
Another approach that is more agnostic is to use a middleware
import { Middleware } from 'redux'
import { monitorActions } from './monitorSlice';
const monitorMiddleware: Middleware = store => next => action => {
if (!monitorActions.startConnecting.match(action)) {
return next(action);
}
const connection = $.hubConnection("http://x.x.x.x/signalr", { useDefaultPath: false })
const proxy = connection.createHubProxy("exampleHub")
proxy.on("action", function(data) {
dispatch(setStatus(data))
});
next(action);
}
export default monitorMiddleware;
Then inside your react app when you want to establish the connection
useEffect(()=> {
dispatch(monitorActions.startConnecting())
}, [])
Then when you setup your store, do not forget to add your new middleware monitorMiddleware:
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => {
return getDefaultMiddleware().concat([monitorMiddleware])
},
});
Using create api
Another approach is to use createApi.
Related
I have a React app in which a global state is setted by using redux, in one component a Form is filled and based on the inputs I do an axios request to the same endpoint from where redux fetch the data then I want to redirect to another component and filter the state based on this new request. The problem is that when I am redirected the same state that redux defined is shown and no the updated one.
My redux logic is this:
actionsCreator productActions.js
import { fetchProductsStart, fetchProductsSuccess, fetchProductsFailure } from '../slices/productsSlice';
import { baseUrl } from '../../shared/baseUrl';
export const fetchProducts = () => async dispatch => {
try {
dispatch(fetchProductsStart());
const response = await fetch(baseUrl+"products");
const data = await response.json();
dispatch(fetchProductsSuccess(data));
} catch (error) {
dispatch(fetchProductsFailure(error));
}
};
the slice reducer is productsSlice.js
import { createSlice } from "#reduxjs/toolkit";
const productsSlice = createSlice({
name: "products",
initialState: {
products: [],
loading: false,
error: null
},
reducers: {
fetchProductsStart(state) {
state.loading = true;
state.error = null;
},
fetchProductsSuccess(state, action) {
state.products = action.payload;
state.loading = false;
},
fetchProductsFailure(state, action) {
state.error = action.payload;
state.loading = false;
}
}
});
export const { fetchProductsStart, fetchProductsSuccess, fetchProductsFailure } = productsSlice.actions;
export default productsSlice.reducer;
and my store configureStore.js
import { configureStore } from "#reduxjs/toolkit"
import productsReducer from "./slices/productsSlice"
export const store = configureStore({
reducer: {
products: productsReducer
}
})
the logic in the form that I mentioned above in the handleSubmit is:
handleSubmit(event){
event.preventDefault()
// redirect to the store component with the search criteria
// the search criteria will be passed as query parameters
var tipo = this.state.tipo
var marca = toTitleCase(this.state.marca)
var linea = this.state.linea
axios.get(baseUrl+'products' + '/?tipo=' + tipo + '&marca=' + marca + '&linea=' + linea )
.then((response) => {
console.log('response.data',response.data)
// here I am using the useNavigate hook to redirect and set the state
// with the response that is returned with the axios request
this.props.navigate("/store",{
state:{
products:response.data
}
});
})
.catch((error) => {
console.log(error)
})
}
How could I correctly update my state in order to filter this based on the inputs gotten from the form inputs? Is there a better way to do this I am kinda newbie with redux I can´t figure how to update the state.
EDIT: I forgot to mention that the component from where I am redirecting is a class component and I can´t change it to functional one
EDIT2: I just could change the logic so now the component from where I am redirecting is a functional Component
I'm using Redux Toolkit to connect to an API with Axios.
I'm using the following code:
const products = createSlice({
name: "products",
initialState: {
products[]
},
reducers: {
reducer2: state => {
axios
.get('myurl')
.then(response => {
//console.log(response.data.products);
state.products.concat(response.data.products);
})
}
}
});
axios is connecting to the API because the commented line to print to the console is showing me the data. However, the state.products.concat(response.data.products); is throwing the following error:
TypeError: Cannot perform 'get' on a proxy that has been revoked
Is there any way to fix this problem?
I would prefer to use createAsyncThunk for API Data with extraReducers
Let assume this page name is productSlice.js
import { createSlice,createSelector,PayloadAction,createAsyncThunk,} from "#reduxjs/toolkit";
export const fetchProducts = createAsyncThunk(
"products/fetchProducts", async (_, thunkAPI) => {
try {
//const response = await fetch(`url`); //where you want to fetch data
//Your Axios code part.
const response = await axios.get(`url`);//where you want to fetch data
return await response.json();
} catch (error) {
return thunkAPI.rejectWithValue({ error: error.message });
}
});
const productsSlice = createSlice({
name: "products",
initialState: {
products: [],
loading: "idle",
error: "",
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchProducts.pending, (state) => {
state. products = [];
state.loading = "loading";
});
builder.addCase(
fetchProducts.fulfilled, (state, { payload }) => {
state. products = payload;
state.loading = "loaded";
});
builder.addCase(
fetchProducts.rejected,(state, action) => {
state.loading = "error";
state.error = action.error.message;
});
}
});
export const selectProducts = createSelector(
(state) => ({
products: state.products,
loading: state.products.loading,
}), (state) => state
);
export default productsSlice;
In your store.js add productsSlice: productsSlice.reducer in out store reducer.
Then for using in component add those code ... I'm also prefer to use hook
import { useSelector, useDispatch } from "react-redux";
import {
fetchProducts,
selectProducts,
} from "path/productSlice.js";
Then Last part calling those method inside your competent like this
const dispatch = useDispatch();
const { products } = useSelector(selectProducts);
React.useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
And Finally, you can access data as products in your component.
It is happening because your reducer function is not a pure function, it should not be having any asynchronous calls.
something like this should work. it will fix the error you are getting
const products = createSlice({
name: "products",
initialState: {
products: []
},
reducers: {
reducer2: (state, { payload }) => {
return { products: [...state.products, ...payload]}
})
}
}
});
and api should be called outside
const fetchProducts = () => {
axios.get('myurl')
.then(response => {
//console.log(response.data.products);
store.dispatch(products.actions.reducer2(response.data.products))
})
};
PS: haven't tried running above code, you may have to make modifications as per your need.
sorry about my english, How can i in other js files use vuex.store in nuxt project
in store
export const state = () => ({
token: 'test',
name: '',
avatar: ''
}),
export const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
}
},
export const getters = {
token: state => {
return state.token
}
}
in test.js
export function() => {
//how can i updata vuex token?
}
export function() => {
//how can i getter vuex token?
}
export default ({ app, store, route, redirect }) => {
some code
}
it can't work
Wanted to know if it's good practice to do that and what would be the best way to do that?
A basic implementation would look like this
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// you only need State OR Getter here not both!!! You don't need a
// getter for just returning a simple state
...mapState('yourStoreName', ['token'])
...mapGetters('yourStoreName', ['token']),
},
methods: {
methodThatNeedsToChangeState (){
this.setToken('newToken')
},
...mapActions('yourStoreName', ['setToken']),
}
}
In your store you need actions though, you don't call mutations directly! Because Mutations can't be asynchronous.
export const actions = {
setToken: (context, token) => {
context.commit(SET_TOKEN, token)
}
},
I would highly recommend you to study the Vuex documentation in more detail.
https://vuex.vuejs.org/guide/
Using NuxtJS (a VueJS framework), I’m trying to get a bunch of datas from a REST API in a layout template (which can’t use the classic fech() or asyncData() methods).
So I'm using vuex and the nuxtServerInit() action.
This way, I should be able to gather all the datas directly during the load of the app, regardless of the current page.
But I can’t get it to work.
Here’s my map.js file for the store:
import axios from 'axios'
const api = 'http://rest.api.localhost/spots'
export const state = () => ({
markers: null
})
export const mutations = {
init (state) {
axios.get(api)
.then((res) => {
state.markers = res.data
})
}
}
export const actions = {
init ({ commit }) {
commit('init')
}
}
And the index.js (that can fire the nuxtServerInit()):
export const state = () => {}
export const mutations = {}
export const actions = {
nuxtServerInit ({ commit }) {
// ??
console.log('test')
}
}
But I can’t get it to work. The doc says:
If you are using the Modules mode of the Vuex store, only the primary module (in store/index.js) will receive this action. You'll need to chain your module actions from there.
But I don’t know how I shall do this. How do I call an action defined in another module/file?
I tried to copy various example, but never got them to work ; this is the best I could come up with.
What did I missed? If needed, here’s the repo and the store folder
Thanks!
I ran into the same problem, a few weeks ago, and here is how I solved it:
======== CLASSIC MODE =========
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth'
import auth from './modules/base'
Vue.use(Vuex)
export default () => {
return new Vuex.Store({
actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user && req.session.token) {
commit('auth/SET_USER', req.session.user)
commit('auth/SET_TOKEN', req.session.token)
}
}
},
modules: {
auth,
base
}
})
}
store/modules/auth.js
const state = () => ({
user: null,
token: null
})
const getters = {
getToken (state) {
return state.token
},
getUser (state) {
return state.user
}
}
const mutations = {
SET_USER (state, user) {
state.user = user
},
SET_TOKEN (state, token) {
state.token = token
}
}
const actions = {
async register ({ commit }, { name, slug, email, password }) {
try {
const { data } = await this.$axios.post('/users', { name, slug, email, password })
commit('SET_USER', data)
} catch (err) {
commit('base/SET_ERROR', err.response.data.message, { root: true })
throw err
}
},
/* ... */
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
Please notice the lines commit('base/SET_ERROR', err.response.data.message, { root: true }), which calls the mutation in another module (called base). And the namespaced: true option, which is required for this to work.
To learn more about namespacing in vuex modules, please refer to the documentation: https://vuex.vuejs.org/en/modules.html
======== MODULES MODE =========
The new 'modules mode' makes this much easier. You can have all the files in one folder and 'namespaced = true' is not required anymore.
Here is how the above files look in modules mode:
store/index.js
export const state = () => ({})
export const actions = {
async nuxtServerInit ({ commit }, { req }) {
if (req.session.user && req.session.token) {
commit('auth/SET_USER', req.session.user)
commit('auth/SET_TOKEN', req.session.token)
}
}
}
store/auth.js
const state = () => ({
user: null,
token: null
})
const getters = {
getUser (state) {
return state.user
},
getToken (state) {
return state.token
}
}
const mutations = {
SET_USER (state, user) {
state.user = user
},
SET_TOKEN (state, token) {
state.token = token
}
}
const actions = {
async register ({ commit }, { name, slug, email, password }) {
try {
const { data } = await this.$axios.post('/users', { name, slug, email, password })
commit('SET_USER', data)
} catch (err) {
commit('base/SET_ERROR', err.response.data.message, { root: true })
throw err
}
}
}
export default {
state,
getters,
mutations,
actions
}
To learn more about modules mode in nuxtjs, please refer to the documentation:
https://nuxtjs.org/guide/vuex-store/#modules-mode
I'm storing my settings with redux-persist and would like to ignore some of them to have them reset on every restart, e.g. after a crashing.
It's possible to add an array of reducer-names as blacklist or whitelist, but I'd like to ignore specific keys, e.g. settings.isLoggedIn instead of settings.
// ...
function configureStore(initialState) {
const store = createStore(
RootReducer,
initialState,
enhancer
);
persistStore(store, {
storage: AsyncStorage,
blacklist: ['router', 'settings'] // works, 'settings.isLoggedIn' doesn't.
}, () => {
// restored
});
return store;
}
// ...
Do I have to create another reducer or does anyone a solution to this problem?
Thanks in advance!
As per the documentation, the blacklist parameter contains: 'keys (read: reducers) to ignore', so I am afraid it is not possible to implement the behaviour that you want. You can try and implement that functionality yourself, but I think the codebase of the package is really focused on blacklisting reducers instead of properties (see this). I am afraid that the only solution is to create a separate reducer for your non-persistent keys (in my experience it is not much of a hassle).
Use transforms for save separate fields, for example for username in redux-form MyForm inside state.form.MyForm:
const formName = `MyForm`
const formTransform = createTransform(
(inboundState, key) => {
return {
...inboundState,
[formName]: {
values: {
username: _.get(inboundState, `${ MyForm }.values.username`)
}
}
}
},
(outboundState, key) => {
return outboundState
},
{ whitelist: [`form`] }
)
persistStore(store, {
whitelist: [
`form`
],
transforms: [
formTransform
]
})
You can use Nested Persists for this.
import { persistStore, persistReducer } from 'redux-persist';
const rootPersistConfig = {
key: 'root',
storage: storage,
blacklist: ['auth']
}
// here you can tell redux persist to ignore loginFormData from auth reducer
const authPersistConfig = {
key: 'auth',
storage: storage,
blacklist: ['loginFormData']
}
// this is your global config
const rootReducer = combineReducers({
auth: persistReducer(authPersistConfig, authReducer),
other: otherReducer,
})
// note: for this to work, your authReducer must be inside blacklist of
// rootPersistConfig
const myReducerConfig = {
key: "cp",
storage: storage,
blacklist: ["authReducer"],
debug: true
};
you have to create reducer for every prop you want to save.
A simple solution is to save the whole reducer in the whitelist and after in the reducer using 'persist/REHYDRATE' action to filter only the keys that you want to keep.
Example:
// configureStore.js
const persistConfig = {
keyPrefix: 'webapp',
whitelist: ['filters'],
}
// filtersReducer.js
const projectsBase = {
[KEYS.SORT]: PROJECTS_SORT_TYPE.NAME,
[KEYS.TEXT]: '',
}
const itemsBase = {
[KEYS.SORT]: ITEMS_SORT_TYPE.INDEX,
[KEYS.TEXT]: '',
}
const base = {
[KEYS.PROJECTS]: projectsBase,
[KEYS.ITEMS]: itemsBase
}
export const filters = (state = base, action) => {
const { type } = action
switch (type) {
case PERSIST_REHYDRATE_ACTION_TYPE: {
if (action.payload.filters) {
const filters = action.payload.filters
const projectsSort = _.get(filters, [KEYS.PROJECTS, KEYS.SORT])
const itemsSort = _.get(filters, [KEYS.ITEMS, KEYS.SORT])
const newBase = { ...base,
[KEYS.PROJECTS]: {
[KEYS.SORT]: projectsSort
},
[KEYS.ITEMS]: {
[KEYS.SORT]: itemsSort
}}
state = newBase
}
}
break
default:
break
}
return state
}
As #martinarroyo mentioned to create a separate reducer which is a good option and if we follow it and create a seperate reducer for errors, we can simply return an empty state as the default;
const initialState = {
error: null
}
export default errorReducer = (state = initialState, action) => {
switch (action.type) {
...
default:
return {
...state,
error: null
}
}
}
This will clear the state everytime we visit the site as defualt is setting the errors to null.