I am trying to pass data to my redux store with the help of redux toolkit, but I'm unable to do so. I double checked that there is no problem with my data. Here is my code:
//USER DETAILS
export const userDetailsSlice = createSlice({
name: 'userDetails',
initialState: { loading: 'idle', user: {} },
reducers: {
userDetailsLoading(state) {
if (state.loading === 'idle') {
state.loading = 'pending';
}
},
userDetailsReceived(state, action) {
if (state.loading === 'pending') {
state.loading = 'idle';
state.user = action.payload;
}
},
userDetailsError(state, action) {
if (state.loading === 'pending') {
state.loading = 'idle';
state.error = action.payload;
}
},
},
});
export const {
userDetailsLoading,
userDetailsReceived,
userDetailsError,
} = userDetailsSlice.actions;
export const getUserDetails = (id) => async (dispatch, getState) => {
dispatch(userRegisterLoading());
try {
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
console.log(data);
dispatch(userDetailsReceived(data));
} catch (error) {
dispatch(userDetailsError(error.toString()));
}
};
I console logged my data right before the dispatch action: so the data is definitely there. Therefore I think there's something wrong with my code. Here is my code for my redux store:
import { configureStore } from '#reduxjs/toolkit';
import { photoListSlice, photoCreateSlice } from './photo';
import { userRegisterSlice, userLoginSlice, userDetailsSlice } from './user';
const reducer = {
userRegister: userRegisterSlice.reducer,
userLogin: userLoginSlice.reducer,
userDetails: userDetailsSlice.reducer,
};
const store = configureStore({ reducer });
export default store;
As seen from this photo: the action userDetailsReceived was called but the state for user did not change.
Any help or hint would be greatly appreciated!!
I made a typo on the first dispatch call of getUserDetails function, I dispatched userRegisterLoading instead of userDetailsLoading... Now it works fine after the change.
export const getUserDetails = (id) => async (dispatch, getState) => {
dispatch(userDetailsLoading());
try {
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/users/${id}`, config);
console.log(data);
dispatch(userDetailsReceived(data));
} catch (error) {
dispatch(userDetailsError(error.toString()));
}
};
Related
i am trying to save user settings, [dark mode/ light mode] on local storage using redux on nextjs
I can save the data on the local storage but i can't pull the data into the initialSatte. here is my code
import { createSlice } from "#reduxjs/toolkit";
import Cookies from "js-cookie";
const getFromLocalStorage = (key: string) => {
if (!key || typeof window === "undefined") {
return "";
}
try {
// #ts-ignore
return JSON.parse(localStorage.getItem(key)) || {};
} catch (error) {
return {};
}
};
export const uiSettings = createSlice({
name: "uiSettings",
initialState: {
theme: getFromLocalStorage("uiSettings")?.theme || "dark",
},
reducers: {
themeSwitch: (state, action) => {
state.theme = action.payload;
window.localStorage.setItem(
"uiSettings",
JSON.stringify({
theme: action.payload,
})
);
},
},
extraReducers: (builder) => {},
});
export const uiSettingsReducer = uiSettings.reducer;
export const { themeSwitch } = uiSettings.actions;
i tried alot of ways to solve it but it didn't work.
I am new to RTK query and need only one WebSocket connection for my entire application as you can see below I implemented it like an example in GitHub.
I need to somehow send my payload to this WebSocket by subscribing to it.
and then whenever the message comes in I update the other injected Endpoints' cache.
import { ApiSlice } from 'api';
import { instrumentsAdapter } from './marketSlice';
const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
// Connection opened
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
console.log('err', err);
reject(err);
}
});
export const socketApi = ApiSlice.injectEndpoints({
endpoints: (builder) => ({
socketChannel: builder.mutation({
async queryFn(arg) {
await socketConnected;
const { type, topic } = arg;
const sendPayload = { type, path: topic };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
},
async onCacheEntryAdded(arg, { cacheDataLoaded, cacheEntryRemoved }) {
console.log('arg', arg);
await cacheDataLoaded;
// Listen for messages
socket.onmessage = (res) => {
const message = JSON.parse(res.data);
try {
// ApiSlice.util.updateQueryData('getInstrumentByRefId', arg, (draft) => {
// console.log('arg', arg);
// draft = { ...message.value, baseVolume: 3 };
// });
} catch (err) {
console.log('err', err);
}
};
await cacheEntryRemoved;
socket.close();
}
})
})
});
export const { useSocketChannelMutation } = socketApi;
after so much reading docs and researching I finally find this solution working but I do not know if this is a best practice or not.
Here is my not-empty ApiSlice.
/* eslint-disable import/prefer-default-export */
// Or from '#reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi } from '#reduxjs/toolkit/query/react';
import axiosBaseQuery from './axiosBaseQuery';
export const socket = new WebSocket(process.env.REACT_APP_SOCKET_BASE_URL);
const socketConnected = new Promise((resolve, reject) => {
try {
socket.addEventListener('open', (event) => {
resolve(event);
});
} catch (err) {
reject(err);
}
});
// initialize an empty api service that we'll inject endpoints into later as needed
export const ApiSlice = createApi({
reducerPath: 'api',
baseQuery: axiosBaseQuery(),
endpoints: (builder) => ({
subscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'SUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
unsubscribeSocket: builder.mutation({
async queryFn(arg) {
await socketConnected;
const sendPayload = { type: 'UNSUBSCRIBE', path: arg };
socket.send(JSON.stringify(sendPayload));
return { data: { messages: [] } };
}
}),
channel: builder.mutation({
async queryFn(onMessage) {
await socketConnected;
socket.addEventListener('message', onMessage);
return { data: { messages: [] } };
}
})
})
});
export const { useUnsubscribeSocketMutation, useSubscribeSocketMutation, useChannelMutation } =
ApiSlice;
and this is my enhanced Api slice
import { createEntityAdapter } from '#reduxjs/toolkit';
import { ApiSlice } from 'api';
export const instrumentsAdapter = createEntityAdapter({
selectId: (item) => item?.state?.symbol
});
export const marketApi = ApiSlice.injectEndpoints({
overrideExisting: false,
endpoints: (builder) => ({
getMarketMap: builder.query({
query: (type) => ({
url: `/market/map?type=${type}`,
method: 'get'
})
}),
getInstruments: builder.query({
query: (type) => ({
url: `/market/instruments?type=${type}`,
method: 'get'
})
}),
getInstrumentByRefId: builder.query({
query: (refId) => ({
url: `/market/instruments/${refId}/summary`,
method: 'get'
}),
transformResponse: (res) => {
return instrumentsAdapter.addOne(instrumentsAdapter.getInitialState(), res);
},
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
getCandles: builder.query({
query: ({ refId, bucket, end, limit = 1 }) => ({
url: `/market/instruments/${refId}/candles?bucket=${bucket}&end=${end}&limit=${limit}`,
method: 'get'
})
})
})
});
export const {
useGetMarketMapQuery,
useGetInstrumentByRefIdQuery,
useGetInstrumentsQuery,
useGetCandlesQuery
} = marketApi;
and I try to dispatch my socket endpoints from ApiSlice inside of onCacheEntryAdded.
async onCacheEntryAdded(
arg,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved, dispatch }
) {
await cacheDataLoaded;
const payload = `instruments.${arg}.summary`;
// subs to socket
dispatch(ApiSlice.endpoints.subscribeSocket.initiate(payload));
// Listen for messages
const onMessage = (res) => {
const message = JSON.parse(res.data);
try {
updateCachedData((draft) => {
instrumentsAdapter.setOne(draft, message.value);
});
} catch (err) {
// eslint-disable-next-line no-console
console.log('err', err);
}
};
dispatch(ApiSlice.endpoints.channel.initiate(onMessage));
await cacheEntryRemoved;
// unsubs to socket
dispatch(ApiSlice.endpoints.unsubscribeSocket.initiate(payload));
}
}),
```
I am using REDUX-TOOLKIT-QUERY, Now I have a situation, I have to call one mutation. once that mutation returns a response, and I have to use that response in the other three mutations as a request parameter. How can we implement this in a proper way?
const [getSettings, { isLoading, data: Settings }] =useSettingsMutation();
const [getMenu] =useMenuMutation();
const [getServices] = useServicesMutation();
useEffect(() => {
const obj = {
Name: "someNme",
};
if (obj) getSettings(obj);
if (Settings?._id ) {
const id = settings._id;
getServices(id);
getMenu(id);
getServices(id);
}
}, []);
useMutation will return a promise, you can directly await this promise to get the return result:
const [getSettings, { isLoading, data: Settings }] = useSettingsMutation()
const [getMenu] = useMenuMutation()
const [getServices] = useServicesMutation()
useEffect(() => {
;(async () => {
try {
const obj = {
Name: 'someNme',
}
const data = await getSettings(obj).unwrap()
if (data._id !== undefined) {
const id = data._id
getServices(id)
getMenu(id)
getServices(id)
// ...
}
} catch (err) {
// ...
}
})()
}, [])
I cant understand why the code above runs infinite loop. My actions are different. Could u please take a look?
import { call, put, takeLatest ,delay} from 'redux-saga/effects'
import { saveFetchedDevices,fetchDevicesRequest } from './devicesRedux'
import axios from "axios"
const getDevices= () => {
return axios.get("http://localhost:3131/devices")
}
function* fetchDevicesHandler(action) {
try {
const response = yield call(getDevices);
yield delay(3000);
yield put(saveFetchedDevices(response.data));
} catch (e) {
console.log(e)
}
}
function* mySaga() {
yield takeLatest(fetchDevicesRequest,fetchDevicesHandler);
}
export default mySaga;
fetchDevicesRequest is dispatched from button. Then loop starts
delay is just to slow down infinite loop
this is my redux file
const createActionName = function(name) {
return `app/devices/${name}`
}
const ADD_DEVICE = createActionName("ADD_DEVICE");
const UPDATE_DEVICE = createActionName("UPDATE_DEVICE");
const REMOVE_DEVICE = createActionName("REMOVE_DEVICE");
const SAVE_FETCHED_DEVICES = createActionName("SAVE_FETCHED_DEVICES");
const FETCH_DEVICES_REQUEST= createActionName("FETCH_DEVICES_REQUEST");
//action creators
export const addDevice = payload => ({ type: ADD_DEVICE, payload })
export const updateDevice = payload => ({ type: UPDATE_DEVICE, payload })
export const removeDevice = payload => ({ type: REMOVE_DEVICE, payload })
export const saveFetchedDevices = payload => ({ type: SAVE_FETCHED_DEVICES, payload })
export const fetchDevicesRequest = payload => ({ type: FETCH_DEVICES_REQUEST })
//selectors
export const getAllDevices = state => state.devices.data;
const reducer = function(statePart = [], action = {}) {
switch(action.type) {
case SAVE_FETCHED_DEVICES:
return { data: action.payload }
case ADD_DEVICE:
return { ...statePart, data: [ ...statePart.data, action.payload ] }
case UPDATE_DEVICE:
return { ...statePart, data: [ ...statePart.data.map((device)=>device.id===action.payload.id?action.payload:device)] }
case REMOVE_DEVICE:
return { ...statePart, data: [ ...statePart.data.filter((device)=>device.id!==action.payload.id)] }
default:
return statePart
}
}
export default reducer
Programming is weird, if you think not then check this case 🤣, I'm using createSlices as Redux and I have two slices with their own states.
First one is orderSlice:
export const orderSlice = createSlice({
name: 'order',
initialState: {
order: null,
message: null,
isLoading: true,
}
})
While the second slice is ordersSlice:
export const orderSlice = createSlice({
name: 'orders',
initialState: {
orders: null,
message: null,
isLoading: true,
}
})
And I have this method to fetch the order and the fulfilled phase where the state is set from:
Fetching the order:
export const fetchOrder = createAsyncThunk('', async ({ token, id }) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/view/${id}`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
Filling the order state:
extraReducers: {
[fetchOrder.fulfilled]: (state, action) => {
state.order = action.payload.data;
state.message = 'Succesfully fetched the Order.';
state.isLoading = false;
}
}
While here is method for fetching the orders:
export const fetchAllOrders = createAsyncThunk('', async (token) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/all`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
And here updating the orders state:
extraReducers: {
[fetchAllOrders.fulfilled]: (state, action) => {
state.orders = action.payload.data;
state.message = 'Succesfully fetched all Orders.';
state.isLoading = false;
}
}
So the case is that I'm calling the fetchAllOrders in the Order page with UseEffect, here is how:
import { fetchAllOrders } from '../redux/ordersSlice';
useEffect(() => dispatch(fetchAllOrders(user.token)), [user]);
So this is how i run the method to fetch orders with dispatch and it works. But the problem is that when I run this function beside the orders state that is filled with the same data, also the order state is filled with the same data and this is impossible as I've cheked all the cases where I could misstyped a user,users typo but there is none I found, and I don't know.
And here is the store:
import orderSlice from './redux/orderSlice';
import ordersSlice from './redux/ordersSlice';
const store = configureStore({
reducer: {
order: orderSlice,
orders: ordersSlice
},
});
You have to give your thunks an unique name: If you name both '' they will be handled interchangably.
Also, you should be using the builder notation for extraReducers. We will deprecate the object notation you are using soon.