I'm creating a game that user can login with just a username as a guest and play. Normally, when user visits the website I'm fetching all users data from firebase and assigning it to users state and then if user click Play button I'm checking if username exist.
Where I create function to fetch users using firebase
import { async } from "#firebase/util";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { collection, query, where, getDocs } from "firebase/firestore";
import { db } from '../firebase/auth';
export interface User{
isUser: boolean;
id: number;
username: string
}
interface UserState{
users:User[];
isLoading:boolean;
errorMessage:string;
}
export const fetchUsers = createAsyncThunk("users/fetchUsers", async()=>{
const usersArray:User[] = []
const usersRef = collection(db, "users");
const q = query(usersRef, where("isUser", "==", true))
const querySnapshot = await getDocs(q);
querySnapshot.forEach(doc =>{
const user = doc.data() as User;
usersArray.push(user);
})
return usersArray;
})
const initialState: UserState = {
users:[],
isLoading:false,
errorMessage:""
}
const usersSlice = createSlice({
name:"users",
initialState,
reducers:{},
extraReducers:(builder)=>{
builder.addCase(fetchUsers.pending, (state, action)=>{
state.isLoading = true;
})
builder.addCase(fetchUsers.fulfilled, (state, action)=>{
state.isLoading = false;
state.users = action.payload;
state.errorMessage = "";
})
builder.addCase(fetchUsers.rejected, (state, action)=>{
state.isLoading = false;
state.users = [];
if(typeof action.error.message === "string" ){
state.errorMessage = action.error.message
}
})
}
})
export const usersActions = usersSlice.actions;
export const userReducer = usersSlice.reducer
where I fetch all data
useEffect(() => {
dispatch(guestInputActions.changeInputValue(randomUser))
dispatch(fetchUsers())
}, [])
Where I check if user exsits
const handleGuestLogin = () => {
async function checkIfUsernameExist() {
if (users) {
const existUser = users.find((user: User) => user.username === inputValue.toLowerCase())
if (existUser) {
dispatch(userLoginActions.setLoginStatus(false))
dispatch(userLoginActions.setErrorMessage("This username already exists!"))
return
} else {
dispatch(userLoginActions.checkUserName(inputValue))
}
}
}
checkIfUsernameExist();
}
But I don't think it is efficent way. I want to fetch all users data when user click play button not on initial render. But when I try to do that with async/await my users array always empty before I check if username exists. So, user can play game even username is in already database
What I tried to fetch users data when user click Play button (not on initial render)
useEffect(() => {
dispatch(guestInputActions.changeInputValue(randomUser))
// dispatch(fetchUsers()) commented out this line
}, [randomUser])
const handleGuestLogin = async () => {
async function checkIfUsernameExist() {
if (users) {
const existUser = users.find((user: User) => user.username === inputValue.toLowerCase())
if (existUser) {
dispatch(userLoginActions.setLoginStatus(false))
dispatch(userLoginActions.setErrorMessage("This username already exists!"))
return
} else {
dispatch(userLoginActions.checkUserName(inputValue))
}
}
}
const val = await dispatch(fetchUsers());
checkIfUsernameExist();
}
Related
I have a problem, I'm looking for a solution, please help.
I am building a React application and using Redux thunk to call the data from the backend
This is the code inside the useEffect
let [events, setEvents] = useState([]);
let [featured, setFeatured] = useState([]);
let [categories, setCategories] = useState([]);
let [interests, setInterests] = useState([]);
useEffect(() => {
let params = new FormData();
if (user) {
params.append('phone_number', user.phone_num);
}
dispatch(
getHomeData(params, json => {
if (json.success) {
console.log(data);
let {data} = json;
setEvents(data?.events);
setFeatured(data?.featured);
setCategories(data?.categories);
setInterests(data?.interests);
if (user) {
dispatch(setNotificationCount(data?.notifications));
}
}
}),
);
}, []);
redux action
import {AppConstant, httpHelperApp} from '../../common';
import {
NOTIFICATION_SET,
HOME_DATA_PENDING,
HOME_DATA_FULFILLED,
HOME_DATA_REJECTED,
} from '../constant';
let HomeApi = 'api/adjusted/essentials.php';
export let getHomeData = (payload, callBack) => {
return async dispatch => {
dispatch({type: HOME_DATA_PENDING});
let data = await httpHelperApp.postApi(payload, HomeApi);
if (data.success) {
dispatch({type: HOME_DATA_FULFILLED, payload: data});
} else {
dispatch({type: HOME_DATA_REJECTED});
}
};
};
export let setNotificationCount = payload => {
return {
type: NOTIFICATION_SET,
payload,
};
};
redux-reducer
let initialState = {
notificationCount: 0,
};
export const HomeReducer = (state = initialState, action) => {
const {type, payload} = action;
switch (type) {
case NOTIFICATION_SET:
return {...state, notificationCount: payload};
default:
return state;
}
};
The problem is that I get an infinite loop and the useEffect keeps working but it stops when I stop the dispatch notification dispatch(setNotificationCount(data?.notifications)) or stop any update to state redux.what did I do wrong please help.
I am trying to extend my frontend code with another redux call but the data is not appearing in store.
Here is store definition
const store = configureStore({
reducer: {
login: loginSlice.reducer,
cart: cartSlice.reducer,
product: productSlice.reducer,
notification: notificationSlice.reducer
}
});
Here is a slice
const productSlice = createSlice({
name: 'product',
initialState: {
products: []
},
reducers: {
replaceData(state,action) {
console.log(action)
state.products = action.payload.products;
}
}
});
export const productActions = productSlice.actions
export default productSlice
And action
export const fetchProducts = () => {
return async (dispatch) => {
const fetchHandler = async () => {
const resp = await fetch("https://shoppingcart-a62bb-default-rtdb.europe-west1.firebasedatabase.app/products.json")
const data = await resp.json();
}
try {
const productData = await fetchHandler();
dispatch(productActions.replaceData(productData))
} catch (err) {
dispatch(notificationActions.showNotification({
open: true,
message: "Error reading product data",
type: 'error'
}));
}
}
}
That's what I call in APP.js
useEffect(()=>{
dispatch(fetchCartData())
dispatch(fetchProducts())
},[dispatch]);
Here I read data from store in component
let respProducts = useSelector(state => state.product.products);
console.log(respProducts)
The problem is that fetch in action works,but payload in dispatch empty and no data in useSelector.
I really don't get what's wrong as similar code in the same app works.
Your fetchHandler is missing a return statement.
const fetchHandler = async () => {
const resp = await fetch("https://shoppingcart-a62bb-default-rtdb.europe-west1.firebasedatabase.app/products.json")
const data = await resp.json();
return data
}
use 'useReduxSelector' instead of 'useSelector'
I would know how to make user authentication properly. Could you tell me what's the logic behind it?
My environment:
React with TypeScript
reduxjs/toolkit
Node.js with Express
I have implemented this functionality but this is not working as expected. My component are rendering many times and when i refresh the page router navigates me always to the homepage.
I know this is wrong approach because at the begining state is always null.
On my app.tsx every time i'm checking if token exists on localStorage, if not then get token and authUser.
useEffect(() => {
if (token) return;
if (getToken()) dispatch(authUser());
}, [dispatch, currentUser, token]);
For my routes i have created ProtectedRouteComponent
But in this scenario currentUser is null at the begining (when initial state is null) so router navigates to '/auth' because async thunk need time to fullfill auth.
const ProtectRoute = ({ children }: { children: JSX.Element }) => {
const { currentUser, isFetching } = useAppSelector(state => state.auth);
return isFetching ? <LoadingSpinner /> : currentUser ? children : <Navigate to="/auth" />;
};
SignIn Method
export const signIn = createAsyncThunk<ILoggedUser, IUserCredentials>(
'auth/signIn',
async (userCredentials, thunkAPI) => {
try {
const response: {
status: any;
data: {
token: string;
data: {
id: string;
name: string;
role: string;
avatarUrl: string;
};
};
} = await api.post('/users/login', userCredentials);
if (response.status === 200) {
localStorage.setItem('token', response.data.token);
return response.data.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
console.log('Error: ', e);
return thunkAPI.rejectWithValue(e);
}
}
);
AuthUser Method
export const authUser = createAsyncThunk<IAuthenticatedUser>(
'auth/userAuthentication',
async (_, { rejectWithValue }) => {
try {
const accessToken = getToken();
if (!accessToken) rejectWithValue('Invalid token');
const config = {
headers: { Authorization: `Bearer ${accessToken}` },
};
const response: IAuthUser = await api.get('/users/authenticateUser', config);
const userData = response.data.data.data;
return {
userData,
accessToken,
};
} catch (error) {
removeToken();
return rejectWithValue(error);
}
}
);
AuthSlice
export const authSlice = createSlice({
name: 'auth/signIn',
initialState,
reducers: {
signOut(state) {
state.currentUser = null;
},
},
extraReducers: builder => {
builder.addCase(signIn.pending, state => {
state.isFetching = true;
state.errorMessage = null;
});
builder.addCase(signIn.fulfilled, (state, action) => {
state.isFetching = false;
state.currentUser = action.payload;
});
builder.addCase(signIn.rejected, state => {
state.isFetching = false;
state.errorMessage = 'Niepoprawny adres email lub hasło.';
});
builder.addCase(authUser.pending, state => {
state.isFetching = true;
});
builder.addCase(authUser.fulfilled, (state, action) => {
const { accessToken, userData }: IAuthenticatedUser = action.payload;
state.token = accessToken;
state.currentUser = userData;
state.isFetching = false;
});
builder.addCase(authUser.rejected, state => {
state.token = '';
state.currentUser = null;
state.isFetching = false;
});
builder.addCase(signOut.fulfilled, state => {
state.token = '';
state.currentUser = null;
state.isFetching = false;
});
},
});
I am creating a login and in the .then function, setting the state of user.
The data is set correctly but after redirect (through code) to the next page, I could not access the new state using useSelector function.
Login.js
const handleSubmit = useCallback((e) => {
async function getUser(){
return await login(username, password);
}
e.preventDefault();
getUser()
.then((userObj) => {
if(userObj.user._id !== undefined){
dispatch(setAuthUser(userObj.user));
message.current.innerHTML = '';
window.location = `/Shops/Near/`;
history.push('/Shops/Near/');
}
else{
message.current.innerHTML = 'Invalid username or password, try again!';
}
});
}, [username, password]);
Shops.js
import { useSelector } from 'react-redux';
const Shops = () => {
const [shops, setShops] = useState([]);
const [isPreferedPage, setIsPreferedPage] = useState(false);
const message = useRef(null);
const user = useSelector((state) => {
console.log(state.user);
return state.user;
}); //bug
userSlice.js
import { createSlice } from '#reduxjs/toolkit'
const initialState = {
};
export const user = createSlice({
name: 'user',
initialState,
reducers: {
setAuthUser: (state, action) => {
state = action.payload;
},
I want to know what is the problem, I tried to remove the async call and even set the state in empty call but still it never been updated.
Thanks
You cannot do state = as that would not modify the object in state, but throw the object in state away and put a new one into the variable.
state.foo = would be okay, state = is not.
Do return action.payload instead.
See also https://redux-toolkit.js.org/usage/immer-reducers#resetting-and-replacing-state
The component renders as it should at first. After refreshing the page then does not render. Again if I change the key to different value it works and after refreshing page its gone again.
My component
const Book = ({ membersDetail }) => {
const history = useHistory();
const takeToBookNav = () => {
history.push("/recordbook/booknav");
};
return (
<div className="book">
{membersDetail.map(({ name, phoneNumber }) => (
<div className="select-members">
<div key={phoneNumber} classname="member" onClick={takeToBookNav}>{name}</div>
</div>
))}
</div>
);
};
const mapStateToProps = createStructuredSelector({
membersDetail: selectMembersList,
});
export default connect(mapStateToProps)(Book);
MEMBER_REDUCER
import MemberActionTypes from "./members-action.type";
const ININTIAL_STATE = {
members: null,
errorMessage: "",
isGettingMembers: false,
isAddingMembers: false,
isRemovingMembers: false,
};
const memberReducer = (state = ININTIAL_STATE, action) => {
switch (action.type) {
case MemberActionTypes.GET_MEMBER_START:
return {
...state,
isGettingMembers: true,
};
case MemberActionTypes.GET_MEMBER_SUCESS:
return {
...state,
members: action.payload,
errorMessage: "",
};
case MemberActionTypes.GET_MEMBER_FAILURE:
return {
...state,
errorMessage: action.payload,
isAddingMembers: false,
isRemovingMembers: false,
isGettingMembers: false,
};
default:
return state;
}
};
export default memberReducer;
SAGAs
export function* gettingMembers() {
try {
const membersList = yield getMembers();
yield put(getMembersSucess(membersList));
} catch (error) {
yield put(getMembersFailure(error));
}
}
export function* getInintialMembersList() {
yield put(getMembersStart());
}
export function* onSigningInSucess() {
yield takeLatest(
UserActionTypes.GOOGLE_SIGNIN_SUCESS,
getInintialMembersList
);
}
export function* onGettingMembers() {
yield takeLatest(MemberActionTypes.GET_MEMBER_START, gettingMembers);
}
FIREBASE
//members data to firebase
const database = firebase.database(); //gets the database
const membersRef = database.ref("members");
export const createMember = (memberCredentials) => {
//pushing the object to the reference members
membersRef.push(memberCredentials);
};
//get array of members from firebase
export const getMembers = () => {
const membersList = [];
membersRef.on("value", function (snapshot) {
snapshot.forEach(function (childSnapshot) {
const item = childSnapshot.val();
item.key = childSnapshot.key;
membersList.push(item);
});
console.log(membersList);
});
return membersList;
};
firebase DATABASE is like
myproject-default-rtdb
| -members
|-MaApwTgulOH0bl1zSH
| -name: "Jhon Doe"
|-phoneNumber: "984215789"
Thank you for Helping.
The code const membersList = yield getMembers(); does not make any sense since getMembers in your firebase code is not a generator.
Since the value event triggers whenever something changes you should have that event listener dispatch a redux action to update the redux state and your saga can just be deleted. Your saga looks like code from a classic api where you fetch data but firebase doesn't work like that.
In your component you can start and stop listening to updates from members but you won't fetch data like with classic REST api.
So your getMembers can just be something like the following listenToMembers:
//your firebase code
let listening = false;
const onNewMembers = (() => {
let allMembers = [];
return (snapshot) => {
//concat new members to existing members
allMembers = allMembers.concat(
snapshot.map(function (childSnapshot) {
const item = childSnapshot.val();
item.key = childSnapshot.key;
return item;
})
);
//store is your redux store
store.dispatch(getMembersSucess(allMembers));
};
})(); //IIFE that has all members
export const listenToMembers = () => {
//check if we are already listening to members
if (listening) {
return;
}
listening = true;
membersRef.on('value', onNewMembers);
};
//you can call this function if you're no longer interested in
// changes in members
export const stopListenToMembers = () => {
membersRef.off('value', onNewMembers);
};
I have not worked with firebase much but I think you should set allMembers first when your app starts or use onSnapShot instead of .on('value'.