Reputation: 25
I am trying to make my navbar dynamically show different things depending on whether the user is signed in or not using React and Redux. And actually, that has proven to be manageable, I hooked up action creators that check whether there is a user object and if there is then to switch the isSignedIn property to true and show content accordingly.
The challenge has been that I want to show that user's profile photo in the navbar too. In order to do that, my plan was to have an action creator fire off a function that will get the user's documents form the database (firebase) and return the details in state to the navbar component. Then mapStateToProps would take that content and move it into props for me to manipulate onto the users view.
I can get this all to work to a point, the component fires up and it takes a bit of time to get the users details form firebase, so theres a couple of seconds where the props.user property is empty. That's fine but then when it does return with the users content, they are stored in object in an array and I have no idea how to access them.
So this.props.user is an array with one object that has key-value pairs like userDateOfBirth: "12-12-2020" and so on. I need to access those values. I thought either this.props.user.userDateOfBirth or this.props.user[0].userDateOfBirth would work but neither does. The first just returns 'undefined' and the second returns an error "Cannot read property 'userDateOfBirth' of undefined.
Please help, this is driving me insane.
I've updated the question to include some of the code that should make this a bit more understandable. I've left the render function out of the navbar component for the sake of brevity.
The Action Creators signedIn and signedOut work as I expected. It's the currentUser Action Creator which I'm having difficulties with. I've shared as much as I think necessary, and maybe a little too much. Thanks for any help.
Code Below:
* Navbar Component *
import './NavBar.css';
import {Link} from "react-router-dom";
import React from "react";
import {connect} from "react-redux";
import * as firebase from "firebase";
import {signedIn, signedOut, currentUser} from "../actions";
componentDidMount()
{
this.unregisterAuthObserver = firebase.auth().onAuthStateChanged((user) => {
let currentUser = user;
if (user) {
this.props.signedIn()
} else this.props.signedOut();
if (this.props.auth.isSignedIn) {
this.props.currentUser(currentUser.uid);
}
});
}
render() {
return (
this.props.auth.isSignedIn ?
//If the user is signed in, show this version of the navbar
//Overall NavBar
<div id="navBar">
{/*Left side of the NavBar*/}
<div id="navBarLeft">
<Link to ={"/"}
>
{/* OLAS Logo*/}
<img
alt={"Olas Logo"}
id="logo"
src={require("../img/Olas_Logo&Brandmark_White.png")}
/>
</Link>
</div>
{/*Right side of the NavBar*/}
<div id="navBarRight">
<div id="home" className="navBarItem">
<Link to={"/"} >
HOME
</Link>
</div>
<div id="careerPaths" className="navBarItem">
<Link to={"/careerPath"} >
CAREER PATHS
</Link>
</div>
<div id="jobPostInsights" className="navBarItem">
<Link to={"/jobPostInsights"} >
JOB POST INSIGHTS
</Link>
</div>
<div id="careerQ&AForum" className="navBarItem">
<Link to={"/forum"} >
CAREER Q&A FORUM
</Link>
</div>
<div id="userProfile" className="navBarItem">
<Link to={"/userprofile"}>
{this.props.user.length ? (<div>{this.props.user.user[0].userFirstName}</div>): <div>PROFILE</div>}
</Link>
</div>
</div>
</div>
//This is a critical part of the ternary operator - DO NOT DELETE
:
//If the user is not signed in, show this version of the navbar
<div id="navBar">
{/*Left side of the NavBar*/}
<div id="navBarLeft">
<Link to ={"/"}
>
{/* OLAS Logo*/}
<img
alt={"Olas Logo"}
id="logo"
src={require("../img/Olas_Logo&Brandmark_White.png")}
/>
</Link>
</div>
{/*Right side of the NavBar*/}
<div id="navBarRight">
<div/>
<div/>
<div id="about" className="navBarItem">
<Link to={"about"}>
<span className="navBarItemInner">ABOUT OLAS</span>
</Link>
</div>
<div id="home" className="navBarItem">
<Link to={"/"} >
<span className="navBarItemInner">HOME</span>
</Link>
</div>
<div id="signIn" className="navBarItem" >
<Link to={"/signIn"} >
<span className="navBarItemInner">SIGN IN / REGISTER</span>
</Link>
</div>
</div>
</div>
)};
const mapStateToProps = state => {
console.log(state);
return {auth: state.auth, user: state.user}
};
export default connect(mapStateToProps, {signedIn, signedOut, currentUser})(NavBar);
* currentUser Action Creator *
export const currentUser = (currentUserUid) => {
return async (dispatch) => {
const response = await getUserDocFromDB(currentUserUid);
dispatch({
type: GET_CURRENT_USER,
payload: response,
});
}
};
* getUserDocFromDB *
import getFirestoreDb from '../getFirestoreDb';
let db = getFirestoreDb();
const getUserDocFromDB = async (currentUserUid) => {
let userDoc = [];
await
db.collection("users")
.where("userFirebaseUid", "==", currentUserUid)
.onSnapshot(
(querySnapshot) => {
if (querySnapshot.empty) {
return;
}
querySnapshot.forEach((doc) => {
const {
userDateOfBirth,
userGender,
userState,
userCity,
userTitle,
userDescription,
userFirstName,
userLastName,
userProfileImageUrl,
} = doc.data();
userDoc.push({
userDateOfBirth,
userGender,
userState,
userCity,
userTitle,
userDescription,
userFirstName,
userLastName,
});
});
},
function(error) {
console.log("Error getting document Error: ", error);
},
);
return userDoc;
};
export default getUserDocFromDB;
* User Reducer *
import {GET_CURRENT_USER} from "../actions/ActionTypes";
const INITIAL_STATE = {
user: null,
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_CURRENT_USER:
return {user: action.payload};
default:
return INITIAL_STATE;
}
};
* Combine Reducers *
import { combineReducers } from "redux";
export default combineReducers({
user: UserReducer,
});
Upvotes: 0
Views: 97
Reputation: 749
Drew Reese's answer is spot on and I just wanted to add to it. You can check if the property exists, or simply render nothing if does not, as his examples show. When the state finally loads, it will force a re-render, the conditional will then be true, and you can then show the user profile photo or whatever other components require info from the returned api call.
This is why you might have seen a pattern of dispatching LOADING reducers, setting loading to true until the api returns and then it is set to false. You can check if loading is true in the component, and choose to render a spinner or something else instead.
Upvotes: 0
Reputation: 1144
I hope you are using private route then simply get pathname by using window.location.pathname === "user/dashboard" ? print username : "login " Minimize tge filtration .. Happy coding
Upvotes: 0
Reputation: 202618
The second way, this.props.user[0].userDateOfBirth
is the correct way of accessing user properties after the data is loaded. What is happening is the array is empty while the data is being fetched and populated in app state. Using a guard will prevent the undefined error.
Assuming the default state is an empty array:
this.props.user.length && this.props.user[0].userDateOfBirth
In react this is a pattern called Conditional Rendering, and would likely look more like:
{this.props.user.length ? (
/* Render user component using this.props.user[0] */
) : null}
To make consuming the user
object a little cleaner in components I'd convert the default state to be null
, and have the reducer that handles the fetch response do the unpacking from the response array. Then the check is reduced to something like:
{this.props.user ? (
/* Render user component using this.props.user */
) : null}
Upvotes: 1