Redux-toolkit + Signalr - javascript

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

React - Redux How to filter the global state when redirecting to other component

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

Redux Toolkit and Axios

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.

In the nuxt project, other files are introduced separately into the store

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/

NuxtJS + Vuex — datas in the store

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

React native + redux-persist: how to ignore keys (blacklist)?

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.

Categories