Hello I am making login app used firebase google login. I want to save the user in the user-reducer.
The console shows user info from the firebase! but, It keeps shows undefined. I don't know why it shows like these.
Please advised!
[user-action ]
import { UserType } from './user.types';
export const login = ({ currentUser }) => ({
type: UserType.USER_LOGIN,
payload: currentUser,
});
[user-types ]
export const UserType = {
USER_LOGIN: 'USER_LOGIN',
};
[user-reducer]
import { UserType } from './user.types';
const INITAIL_STATE = {
user: [],
};
const userReducer = (state = INITAIL_STATE, action) => {
switch (action.type) {
case UserType.USER_LOGIN:
return {
...state,
user: action.payload,
};
default:
return state;
}
};
export default userReducer;
[Login.js]
const Login = ({ googleLogin }) => {
const [currentUser, setCurrentUser] = useState(null);
const getUser = new Promise((resolve, reject) => {
console.log('doing...');
firebase.auth().onAuthStateChanged(setCurrentUser);
resolve(currentUser);
console.log(currentUser);
});
getUser
.then(user => {
googleLogin(user);
})
.catch(error => {
console.log(error);
});
....
const mapDispatchToProps = dispatch => ({
onModal: () => dispatch(modalHandler()),
googleLogin: currentUser => dispatch(login(currentUser)),
});
export default connect(null, mapDispatchToProps)(Login);
Your problem is on getUser, you are resolving promise passing a state variable (currentUser) that may have been not setted (because setCurrentUser is async). I would suggest to modify you code in this way:
const getUser = new Promise((resolve, reject) => {
console.log('doing...');
firebase.auth().onAuthStateChanged(firebaseUser => {
setCurrentUser(firebaseUser);
resolve(firebaseUser);
console.log(firebaseUser);
});
});
export const login = ({ currentUser }) => ({
type: UserType.USER_LOGIN,
payload: currentUser,
});
should be -
export const login = (currentUser) => ({
type: UserType.USER_LOGIN,
payload: currentUser,
});
i.e no destructuring of currentUser
OR
googleLogin: currentUser => dispatch(login(currentUser))
should be
googleLogin: currentUser => dispatch(login({currentUser}))
Related
Please help me with a situation around react-redux toolkit. I have issues in updating the state ( which i try to do in a immutable way ), and the component where I use it, never rerenders.
export const updateUser = createAsyncThunk("users/updateUser", async ({ id, name, username }) => {
const response = await axios.patch(`https://jsonplaceholder.typicode.com/users/${id}`,{
name,
username
});
return response.data;
});
const userEntity = createEntityAdapter({
selectId: (user) => user.id
})
const userSlice = createSlice({
name: "user",
initialState: userEntity.getInitialState(),
extraReducers: {
[getUsers.fulfilled]: (state, action) => {
userEntity.setAll(state, action.payload);
},
[updateUser.fulfilled]: (state, action) => {
userEntity.updateOne(state, { id: action.payload.id, update: action.payload});
}
},
});
export const userSelectors = userEntity.getSelectors(state => state.user)
export default userSlice.reducer;
const EditUser = () => {
const [name, setName] = useState('');
const [username, setUserName] = useState('');
const dispatch = useDispatch();
const navigate = useNavigate();
const { id } = useParams();
const user = useSelector((state) => userSelectors.selectById(state, id));
useEffect(() => {
dispatch(getUsers());
},[dispatch]);
useEffect(() => {
if(user){
setName(user.name);
setUserName(user.username);
}
},[user]);
const handleUpdate = async (e) => {
e.preventDefault();
await dispatch(updateUser({id, name, username}));
navigate('/user');
}
whenever I click on button and update the state. it updates on redux. but state does not update. What am i doing wrong?
Image Explanation 1
Image Explanation 2
Image Explanation 3
My problem is I cannot store the currentUser when I login with google , previously I have used only react to store the currentUser when he login and set null when he signout . So what i want is to store the currentUser
this is my code :
class HomeHeaderW extends React.Component {
authListener = null;
componentDidMount() {
const { setCurrentUser, currentUser } = this.props
this.authListener = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
console.log(currentUser)
const userRef = await handleUserProfile(userAuth);
userRef.onSnapshot(snapshot => {
setCurrentUser({
id: snapshot.id,
...snapshot.data()
});
})
}
setCurrentUser(userAuth)
})
}
render() {
const { currentUser } = this.props
return (...
}
HomeHeaderW.defaultProps = {
currentUser: null
};
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser
})
export default connect(mapStateToProps, null)(HomeHeaderW)
user.types.js
const userTypes = {
SET_CURRENT_USER: 'SET_CURRENT_USER'
};
export default userTypes
user.reducer.js
import userTypes from "./user.types";
const INITIAL_STATE = {
currentUser: null
};
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case userTypes.SET_CURRENT_USER:
return {
...state,
currentUser: action.payload
}
default:
return state;
}
};
export default userReducer
user.actions.js
import userTypes from "./user.types";
export const setCurrentUser = user =>
({
type: userTypes.SET_CURRENT_USER,
payload: user
})
utils.js
export const handleUserProfile = async({ userAuth, additionalData }) => {
if (!userAuth) return;
const { uid } = userAuth;
const userRef = firestore.doc(`users/${uid}`);
const snapshot = await userRef.get();
if (!snapshot.exists) {
const { displayName, email } = userAuth;
const timestamp = new Date();
const userRoles = ['user'];
try {
await userRef.set({
displayName,
email,
createdDate: timestamp,
userRoles,
...additionalData
});
} catch (err) {
console.log(err);
}
}
return userRef;
};
So when I check the currentUser in the console i see null
Also I get another problem in the console :
HomeHeaderW.js:28 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'onSnapshot')
In my Login component I'm making two api requests. One is for authentication with JWT and another is getting data (which are some packs). They are getting called sequentially. I'm also using Redux to store the packs (response got from API call). For that I've set all the Actions and reducers. Note that I should get 4 objects in an array as response from the API call. To prevent auto logout on page refresh I've set the JWT token in localStorage and set that logic in the initialState of actions in Redux. But after logging in I'm getting an empty array as response. I'm also getting a 401 error for the second api call. But in login.js when I set the initialState to false I'm getting the response Here's what the response look like. But when I set the initialState based on localStorage it is giving me This 401 Error.
Login.js
function Login() {
const login = useSelector((state) => state.login);
const packs = useSelector((state) => state.packs);
const dispatch = useDispatch();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
const data = {
email: email,
password: password,
};
await Promise.all([
axios
.post(process.env.REACT_APP_LOGIN_URL_API, data)
.then((res) => localStorage.setItem("token", res.data.token))
.then(() => dispatch(setLogin()))
.catch(() => console.log("username or password do not match")),
axios
.get(process.env.REACT_APP_PACK_URL)
.then((res) => dispatch(getPacks(res.data.packs)))
.catch((err) => {
console.log(err);
}),
]);
};
console.log(packs);
if (login.isLogged) return <Redirect to="/" />;
...
login.js (reducer)
const initialState = {
isLogged: localStorage.getItem("token") ? true : false,
};
const login = (state = initialState, action) => {
switch (action.type) {
case "SET_LOGIN":
return {
isLogged: true,
};
default:
return state;
}
};
export default login;
packs.js (reducer)
const packs = (packs = [], action) => {
switch (action.type) {
case "FETCH_PACK":
return {
...packs,
packs: action.payload.packs,
};
default:
return packs;
}
};
export default packs;
actions.js
export const setLogin = () => ({
type: "SET_LOGIN",
payload: {
isLogged: true,
},
});
export const getPacks = (packs) => ({
type: "FETCH_PACK",
payload: {
packs: packs,
},
});
I have two separate components. I want to have a button that when clicked on will add an element to an array in my reducer and redirect to another component, this component that gets redirected to needs to render the data that was just added to the array. The page redirects to the component I want but the data does not load and the console.logs don't show anything.
This is the component that has the redirect button. On this component the console.log(socialNetworkContract.members[0]) shows the string I expect.
const Posts = () => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
window.location.href='/member'
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default Posts;
This is my reducer
import { connect, useDispatch, useSelector } from "react-redux";
let init = {
posts:[],
post:{},
profiles:[],
profile:{},
members:[],
member:{}
}
export const socialNetworkContract = (state = init, action) => {
const { type, response } = action;
switch (type) {
case 'ADD_POST':
return {
...state,
posts: [...state.posts, response]
}
case 'SET_POST':
return {
...state,
post: response
}
case 'ADD_PROFILE':
return {
...state,
profiles: [...state.profiles, response]
}
case 'SET_PROFILE':
return {
...state,
profile: response
}
case 'ADD_MEMBER':
return {
...state,
members: [...state.members, response]
}
case 'SET_MEMBER':
return {
...state,
member: response
}
default: return state
}
};
and this is the component that is redirected to. this just says undefined in console.log(socialNetworkContract.members[0])
const Member = () => {
const [user, setUser] = useState({});
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
useEffect(async()=>{
try {
const pro = socialNetworkContract.members[0]
console.log(socialNetworkContract.members[0])
await setUser(pro)
console.log(socialNetworkContract.members[0])
} catch (e) {
console.error(e)
}
}, [])
I have the route set in Routes.js as
<Route path="/member" exact component={Member} />
Use history.push('/') instead of window.location.href which will reload your whole page and you will lost your local state data.
const {withRouter} from "react-router-dom";
const Posts = (props) => {
const dispatch = useDispatch();
const getProfile = async (member) => {
const addr = await dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member');
console.log('----------- member------------')
console.log(socialNetworkContract.members[0])
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter( Posts );
Being a newbie with RN and Redux, I'm confused as to why my props are undefined after reading from AsyncStorage.
I log in, save the state to the store and storage... I reload the app and read from the storage and update the state. The storage is retrieving my object but the props are undefined.
actions.js:
export const getSession = (data) => ({
type: 'GET_SESSION',
payload: {
user: data
}
});
export const getUserSession = () => dispatch => {
return AsyncStorage.getItem('userSession').then((data) => {
console.log('Props at asynsstorage: ', data);
// {"current_user":{"uid":"1","roles":["authenticated","administrator"], ...}
dispatch(loading(false));
dispatch(getSession(data));
})
.catch((err) => {
})
}
reducer.js
import { combineReducers } from 'redux';
const defaultState = {
xcsrf: '',
user: {},
loading: false,
error: '',
};
const authReducer = ( state = defaultState, action ) => {
switch(action.type) {
case 'GET_SESSION':
return {
...state,
user: action.payload.user,
loading: false,
}
case 'SAVE_SESSION':
return {
...state,
user: action.payload.user,
loading: false,
}
default:
return state;
}
}
export default combineReducers({
authReducer: authReducer
});
authLoading.js // screen
class AuthLoadingScreen extends React.Component {
constructor() {
super();
}
componentDidMount = () => {
this.props.getUserSession().then(() => {
console.log( 'Props at loading: ', this.props.user );
// undefined
})
.catch(error => {
})
};
// Render any loading content that you like here
render() {
return ();
}
}
const mapStateToProps = state => ({
user: state.user,
});
const mapDispatchToProps = dispatch => ({
getUserSession: () => dispatch(getUserSession()),
});
export default connect(mapStateToProps, mapDispatchToProps)(AuthLoadingScreen);
You cannot access directly user of reducer. So change
const mapStateToProps = state => ({
user: state.user,
});
To
const mapStateToProps = state => ({
user: state.authReducer.user,
});
And one more thing AsyncStorage's getItem() method return string of stored data. You have not converted to it json. So please also convert that as below :
export const getUserSession = () => dispatch => {
return AsyncStorage.getItem('userSession').then((data) => {
console.log('Props at asynsstorage: ', data);
// {"current_user":{"uid":"1","roles":["authenticated","administrator"], ...}
dispatch(loading(false));
dispatch(getSession(JSON.parse(data))); //convert to json here
})
.catch((err) => {
})
}