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);
}
});
});
}
);
Related
This thunk is being dispatched on a user event (click log in) on a Formik element.
For some reason it dispatches twice. I have commented out strictMode to exclude that possibility.
In my redux slice:
// userSlice.js
const initialState = {
user: Cookies.get('user') ? JSON.parse(Cookies.get('user')) : null,
};
export const loginUser = createAsyncThunk('users/login', async userInputs => {
try {
const { data } = await axios.post('url', userInputs);
Cookies.set('user', JSON.stringify(data));
return data;
} catch (error) {
return error.response.data;
}
});
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers(builder) {
builder.addCase(loginUser.fulfilled, (state, action) => {
state.user = action.payload;
});
},
});
In the component dispatching the thunk:
// loginForm.js
const loginSubmitHandler = async () => {
const data = await dispatch(
loginUser({
email,
password,
})
).unwrap();
if (data.message) {
setError(data.message);
}
else {
setError('');
navigate('/');
}
};
<Formik
enableReinitialize
initialValues={{
email,
password,
}}
validationSchema={loginvalidation}
onSubmit={loginSubmitHandler}></Formik>;
Any ideas?
I'm trying to fetch a list from database and add to my state. but the action.payload is undefined on the api the result is correct.
mySlice
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { fileFetch } from "./fileAPI";
const initialState = {
loading: "idle" | "pending" | "succeeded" | "failed",
error: false,
filesUploaded: null,
};
export const fetchFiles = createAsyncThunk("files/fetchFiles", () => {
return fileFetch();
});
export const fileSlice = createSlice({
name: "files",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchFiles.pending, (state, action) => {
state.loading = "pending";
})
.addCase(fetchFiles.fulfilled, (state, action) => {
console.log(action);
state.loading = "succeeded";
state.filesUploaded = action.payload;
})
.addCase(fetchFiles.rejected, (state, action) => {
state.loading = "failed";
state.filesUploaded = [];
state.error = action.error.message;
});
},
});
export default fileSlice.reducer;
myAPI
const api = axios.create({
baseURL: "http://localhost:8081/file/",
headers: {
"content-type": "application/json",
},
});
export const fileFetch = async () => {
await api
.get("getAll")
.then((res) => {
console.log(res.data);
return res.data;
})
.catch((err) => {
throw new Error(err);
});
};
the console.log on the api is returning the correct data.
any idea why the payload is undefined?
thanks.
Might be because you are using both async-await and Promise methods in fileFetch function. Try updating it to this
export const fileFetch = async () => {
const response = await api.get("getAll")
return response.data
};
myApi
export const fileFetch = () => api.get("getAll")
mySlice
export const fetchFiles = createAsyncThunk(
"files/fetchFiles",
async ({ rejectWithValue }) => {
try {
const response = await fileFetch();
return response.data;
} catch (error) {
return rejectWithValue(error.response.data);
}
}
);
Also working lifecycle methods
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 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
In my userSlice.js file, I have the following code. and when I was debugging the loginStart reducer was working then it jumps to loginError
Edit: Sometimes it works and sometimes it doesn't and I can't figure out why
import { createSlice } from '#reduxjs/toolkit'
import Cookies from 'js-cookie';
const userSlice = createSlice({
name: 'user',
initialState: {
userInfo: null,
loginPending: false,
loginError: false,
isLoggedIn: false,
token: '',
},
reducers: {
loginStart: (state) => {
state.loginPending = true;
},
loginSuccess: (state, action) => {
state.loginPending = false;
state.userInfo = action.payload.dashboardUser;
state.token = action.payload.token;
state.isLoggedIn = true;
},
loginError: (state) => {
state.loginError = true;
state.loginPending = false;
},
logout: async (state) => {
state.token = '';
state.isLoggedIn = false;
state.userInfo = null;
}
},
});
export const { loginStart, loginSuccess, loginError, logout } = userSlice.actions;
export default userSlice.reducer;
and here is the login function:
export const login = async (email, password, dispatch) => {
try {
dispatch(loginStart());
const res = await axios.post(`${process.env.REACT_APP_SERVER}/api/auth/login`, {
email: email,
password: password,
});
dispatch(loginSuccess(res.data));
} catch (err) {
dispatch(loginError());
}
}
and the login api call is working. So the problem isn't there. I appreciate your help everyone
I saw you wrong url: process.env.REACT_APP_SERVER}/api//auth/login. So axios cannot post data and it jumps to loginError.
update your URL:
process.env.REACT_APP_SERVER}/api/auth/login