I have a walletReducer that can change depending on the user's actions, for example, when I change my wallet, I have to update its address and balance on the site. I receive this data in an asynchronous function and now the update fails.
const walletDefaultState = {
web3: {},
account: "",
balance: "",
};
const CONNECT_BY_METAMASK = "CONNECT_BY_METAMASK";
const WALLET_CONNECT = "WALLET_CONNECT";
export const walletReducer = (state = walletDefaultState, action) => {
switch (action.type) {
case CONNECT_BY_METAMASK:
return { ...state, ...action.payload };
case WALLET_CONNECT:
return { ...state, cash: state.cash - action.payload };
default:
return state;
}
};
export const connectByMetaMaskAction = (payload) => ({
type: CONNECT_BY_METAMASK,
payload,
});
import Web3 from "web3";
import { connectByMetaMaskAction } from "../../store/walletReducer";
export function createWeb3Listeners() {
if (window.ethereum) {
window.ethereum.on("accountsChanged", async () => {
console.log("accountsChanged");
const web3 = new Web3(window.ethereum);
let accounts = await web3.eth.getAccounts();
let balance = await web3.eth.getBalance(accounts[0]);
dispatch(connectByMetaMaskAction({ web3, accounts, balance }));
});
window.ethereum.on("chainChanged", () => {
console.log("chainChanged");
});
window.ethereum.on("disconnect", () => {
console.log("disconnect");
});
}
}
To dispatch the actions after the async, you might need to use the middleware called redux-thunk-middleware
Related
I have a async action name editLoginIdData() in loginsIdSlice.js,
which i am dispatching from certain component, which edits the data in mongoDB database, then when the action is fullfilled, i mutate the state in extraReducers
editLoginIdData.fulfilled.
But now what i want to do that whenever the editLoginIdData action is fullfilled
i want to also add the updated(which i will get from server responese -> updatedData at editLoginIdData()) data to activitiesData state, which i am handling in activitiesSlice.js
So basically is there a way that when editLoginIdData action is fullfilled we can
dispatch somehow mutate the state in other slice.
One approach i have taken is to import the editLoginIdData() action in activitiesSlice.js and then creating a extraReducer with editLoginIdData.fullfilled
and mutating activitiesData state.
I have done the above approach and seems its working correctly.
But there is a catch, like how show i get the response data at editLoginIdData()
to passed to activitiesSlice.js because i will required that updated data.
if the above appraoch is not correct, then how should i do it
loginsIdSlice.js
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import * as api from "../../api"
const initialState = {
loginsIdData: [],
}
export const fecthLoginIdsData = createAsyncThunk("loginIds/fetch", async ({ user_id }, { getState }) => {
const res = await api.fetchUserLoginIds(user_id);
console.log(res);
const { data } = res;
data.reverse();
return data;
});
export const addNewLoginIdData = createAsyncThunk("loginIds/add", async ({ data, user_id }, { getState }) => {
const res = await api.addNewLoginIdA(data, user_id)
const { loginIdsArray } = res.data;
return loginIdsArray[loginIdsArray.length - 1];
});
export const editLoginIdData = createAsyncThunk("loginIds/edit", async ({ updatedData, login_id }, { getState }) => {
const res = await api.editLoginId(login_id, updatedData);
// console.log(updatedData);
return updatedData;
});
export const deleteLoginData = createAsyncThunk("loginIds/delete", async ({ login_id, user_id }, { getState }) => {
const res = await api.deleteLoginId(login_id, user_id);
// console.log(res);
const { data } = res;
// console.log(data);
return data.reverse();
});
//* Slice
const loginsIdSlice = createSlice({
name: 'loginsId',
initialState: initialState,
extraReducers: (builder) => {
builder.
addCase(fecthLoginIdsData.fulfilled, (state, action) => {
return {
...state,
loginsIdData: action.payload
};
}).
addCase(addNewLoginIdData.fulfilled, (state, action) => {
return {
...state,
loginsIdData: [action.payload, ...state.loginsIdData]
};
}).
addCase(editLoginIdData.fulfilled, (state, action) => {
const newArray = state.loginsIdData.map((loginId) => {
if (loginId._id === action.payload._id) {
return action.payload;
} else {
return loginId;
}
});
return {
...state,
loginsIdData: newArray,
};
}).
addCase(deleteLoginData.fulfilled, (state, action) => {
return {
...state,
loginsIdData: action.payload
};
})
}
})
export const { deleteLoginId, editLoginId } = loginsIdSlice.actions;
export default loginsIdSlice.reducer;
activitiesSlice
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import * as api from "../../api"
import { editLoginIdData } from "../loginsId/loginsIdSlice"
const initialState = {
activitiesData: [],
}
const activitiesSlice = createSlice({
name: 'activities',
initialState: initialState,
extraReducers: (builder) => {
builder.
addCase(editLoginIdData.fullfilled, (state, action) => {
console.log("ss")
return {
...state,
activitiesData: []
};
})
}
})
export default activitiesSlice.reducer;
Is there a way that when editLoginIdData action is fullfilled we can
dispatch somehow mutate the state in other slice.
I'm trying to implement Firebase Authentication via Redux Toolkit. But I think I'm missing something due to lack of knowledge.
My monitorAuthChange returns undefined.
I have two separate files - first list of firebase functions, second Redux Toolkit slice.
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
} from "firebase/auth";
import { auth } from "./firebaseConfig";
export const createAccount = async (email, password) => {
try {
await createUserWithEmailAndPassword(auth, email, password);
} catch (error) {
console.log(error);
}
};
export const monitorAuthChange = () => {
onAuthStateChanged(auth, (user) => {
if (user) {
return true;
} else {
return false;
}
});
};
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { createAccount, monitorAuthChange } from "../../service/userServices";
export const createUser = createAsyncThunk(
"users/createUser",
async ({ username, password }) => {
await createAccount(username, password);
}
);
const initialState = { loginStatus: false };
const userSlice = createSlice({
name: "users",
initialState,
reducers: {},
extraReducers: {
[createUser.fulfilled]: (state, action) => {
const result = monitorAuthChange();
state.loginStatus = result;
},
[createUser.rejected]: (state, action) => {
state.loginStatus = false;
},
},
});
export const selectAllUsers = (state) => state.users;
export default userSlice.reducer;
Two things make me confused:
Thunk works - it creates account and I see it in Firebase. Do I need to track result of request in a different way?
If add console.log(user) inside monitorAuthChange it logs data depends if user was created or not. But still returns undefined.
Would appreciate any hint or advice or article to read to understand my mistake. Thanks in advance.
It seems you want to track user auth with onAuthStateChanged
You have plenty way to plug this callback to redux.
You cannot call monitorAuthChange inside the reducer as they must be pure.
Using global store
// users.slice.ts
const userSlice = createSlice({
name: "users",
initialState,
reducers: {
setLoginStatus: (state, action) {
state.loginStatus = action.payload;
}
},
extraReducers: {
[createUser.fulfilled]: (state, action) => {
state.loginStatus = true;
},
[createUser.rejected]: (state, action) => {
state.loginStatus = false;
},
},
});
// trackUserAuth.ts
onAuthStateChanged(auth, (user) => {
if (user) {
store.dispatch(setLoginStatus(true))
} else {
store.dispatch(setLoginStatus(true))
}
});
Using hooks
export const useAuth = () => {
const dispatch = useDispatch();
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
dispatch(setLoginStatus(true))
} else {
dispatch(setLoginStatus(true))
}
});
return unsubscribe;
}, []);
}
Using thunks
export const checkAuthStatus = () => (dispatch) {
const unsubscribe = Firebase.auth().onAuthStateChanged(user => {
if (user) {
dispatch(setLoginStatus(true))
} else {
dispatch(setLoginStatus(true))
}
});
return unsubscribe;
}
I am trying to create load more functionality by fetching only the necessary date i.e. the next one that needs to be added to the existing state that I have in the redux store, but I have a problem my redux actions are duplicated.
Component App.js
function App() {
const dispatch = useDispatch();
const data = useSelector(questionsData);
useEffect(() => {
const fetchQuestions = async () => {
dispatch(fetchQuestionsBegin());
try {
const { data } = await mainUrl(`/questions?last=5`);
return dispatch(fetchQuestionsSuccess(data));
} catch (err) {
return dispatch(fetchQuestionsFailure());
}
};
fetchQuestions();
}, [dispatch]);
return (
<>
<div>TEST</div>
</>
);
}
creating store
const store = configureStore({
reducer: {
questionsStore: questionsReducer,
},
});
export default store;
slice
const initialState = {
loading: false,
questions: [],
error: "",
};
const questionsSlice = createSlice({
name: "questions",
initialState,
reducers: {
fetchQuestionsBegin(state) {
return { ...state, loading: true, error: "" };
},
fetchQuestionsSuccess(state, action) {
return {
...state,
loading: false,
questions: [...state.questions, ...action.payload],
};
},
fetchQuestionsFailure(state, action) {
return { ...state, loading: false, error: action.payload };
},
},
});
export const { reducer: questionsReducer, actions } = questionsSlice;
export const {
fetchQuestionsBegin,
fetchQuestionsSuccess,
fetchQuestionsFailure,
} = actions;
redux
When I exclude <React.StrictMode> everything works fine.
Refer to link. Strict mode can cause multiple methods to invoke multiple times. Its most likely that your redux is ran twice when the component mounts for the first time. You can implement useRef to detect initial mount and then conditionally render after
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
}, [])
useEffect(() => {
if (isMounted.current) {
const fetchQuestions = async () => {
dispatch(fetchQuestionsBegin());
try {
const { data } = await mainUrl(`/questions?last=5`);
return dispatch(fetchQuestionsSuccess(data));
} catch (err) {
return dispatch(fetchQuestionsFailure());
}
};
fetchQuestions();
}
}, [dispatch]);
import create from 'zustand';
import createContext from 'zustand/context';
import { persist } from 'zustand/middleware';
let store;
const initialState = {
loading: false,
cart: {
cartItems: {},
invoiceData: {},
count: 0,
},
};
const zustandContext = createContext();
export const Provider = zustandContext.Provider;
// An example of how to get types
/** #type {import('zustand/index').UseStore<typeof initialState>} */
export const useStore = zustandContext.useStore;
export const initializeStore = (preloadedState = {}) => {
return create(
persist(
(set, get) => ({
...initialState,
...preloadedState,
updateCart: (cartData) => {
set({
cart: cartData,
});
},
setLoading: (val) => {
set({
loading: val,
});
},
modifyCart: (product, qty, type) => {
const cartData = get().cart;
// cart operations
set({
cart: tmpCartData,
});
},
}),
{
name: 'cartData',
}
)
);
};
export function useCreateStore(initialState) {
const [cartData, setCartData] = useState(null);
const [userCart, setCart] = useLocalStorage('cartData', {});
const { state: { cart = {} } = {} } = userCart;
if (typeof window === 'undefined') {
return () => initializeStore(initialState);
}
store = store ?? initializeStore(initialState);
useLayoutEffect(() => {
if (initialState && store) {
store.setState({
...store.getState(),
...initialState,
});
}
}, [initialState]);
useLayoutEffect(() => {
(async () => {
store.setState({
...store.getState(),
cart: { ...cart },
loading: true,
});
})();
}, []);
return () => store;
}
This code is inspired by Zustand documentation and by the NextJS and Zustand boilerplate. I need to sync this data with the browser's localstorage. However, calling the 'set' method inside modifyCart causes an infinite render. I have not found enough documentations regarding this.
How should I go about debugging such an issue?
i can log value of onAuthStateChanged but when i return it, extraReducers seem did not get it.
function check state of auth
import { createAsyncThunk, createSlice} from "#reduxjs/toolkit";
import {
onAuthStateChanged,
signOut
} from "firebase/auth";
export const checkUserSignIn = createAsyncThunk(
"auth/checkUSerSignIn",
async () => {
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
console.log(user)
return true
} else {
return false
}
});
}
);
where i get return value
const authSlice = createSlice({
name: "auth",
initialState: {
auth: {
isLoading: false,
isAuthenticate: false,
user: null,
},
},
reducers: {},
extraReducers: (builder) => {
//Check User SignIn
builder
.addCase(checkUserSignIn.pending, (state, action) => {
state.auth.isLoading = true;
console.log(`CheckUserSignIn Pending: ${action.payload}`);
})
.addCase(checkUserSignIn.fulfilled, (state, action) => {
state.auth.isLoading = false;
action.payload
? (state.auth.isAuthenticate = true)
: (state.auth.isAuthenticate = false);
console.log(`CheckUserSignIn Fulfilled: ${action.payload}`);
})
.addCase(checkUserSignIn.rejected, (state, action) => {
console.log(`CheckUserSignIn Rejected: ${action.error.message}`);
});
},
});
action.payload of fulfilled case always return undefined. how can i fix it?
have a nice day, everyone!
onAuthStateChanged is an asynchronous call, but it doesn't return a promise itself. Even if it did, you're not returning anything from the top-level code in the checkUserSignIn function.
This is probably closer to what you need/want:
export const checkUserSignIn = createAsyncThunk(
"auth/checkUSerSignIn",
async () => {
return new Promise((resolve, reject) {
const auth = getAuth();
const unsubscribe = onAuthStateChanged(auth, (user) => {
unsubscribe();
if (user) {
resolve(true);
} else {
resolve(false);
}
});
});
}
);