Reputation: 159
I’m having problems storing and accessing data with a useState
hook in React after it is retrieved from a Firebase database. The problem seems to be stemming from the fact that not all users have all values in the playerData
data packet – in this case, if the player has the ‘pioneer’ achievement, they can create a settlement, if not, they can only set their profile name (and in turn have an undefined settlementName
within playerData
). It works fine if the account has all of the relevant data, but if not then it hits a bunch of TypeErrors for null values.
Can anyone tell me what I am doing wrong here? I am wondering if I should use NoPioneer()
(which executes if the user is identified as not having the pioneer achievement) to populate empty values to the playerData
object so that it at least doesn’t hit null references in the object.
Additionally, I have found if I remove the playerData.settlement.settlementName
rendering reference (which is the data that may or may not exist depending on the player's pioneer status) the component renders. So, this makes me think that I may need to add a condition to the rendering call to check if the data exists (though sadly I am unsure of how to do this).
Here is my React component for reference:
function Dashboard() {
const [ error, setError ] = useState('');
const history = useHistory();
const pioneer_emails = Firebase.database().ref("pioneer_emails");
const player_achievements = Firebase.database().ref("players/" + currentUser.uid + "/achievements");
const [ pioneer, setPioneer ] = useState(false);
const [ playerData, setPlayerData ] = useState({
player_data: {
playerName: "",
},
settlement: {
settlementName: "",
}
});
const playerName = playerData.player_data.playerName;
const settlementName = playerData.settlement.settlementName;
var dateVariable = Date().toLocaleString()
async function CheckForPioneer(){
//check if email is on pioneer list
await pioneer_emails.on('value', gotEmail, errData);
//if not, then check if the player has the achievement already
if(!pioneer) {
player_achievements.on('value', gotAchievement, noPioneer);
}
}
//check to see if the player's email is on the pioneer list
function gotEmail(data){
//console.log(data.val())
var emails = data.val();
var keys = Object.keys(emails);
for(var i = 0; i < keys.length; i++){
let k = keys[i];
if(emails[k] === currentUser.email){
//set bool
setPioneer(true);
//add achievement to database
SetPioneerAchievement();
//delete email from database
pioneer_emails.child(k).remove();
}
}
}
//check to see if the player has the pioneer achievement
function gotAchievement(data){
var achievements = data.val();
var achievementKeys = Object.keys(achievements);
for(var i = 0; i < achievementKeys.length; i++){
if(achievementKeys[i] === "MISC001"){
setPioneer(true);
console.log("We got a pioneer, baby!");
}
}
}
function noPioneer(){
console.log('Player is not a pioneer!');
}
function errData(err){
console.log('Error!');
console.log(err)
}
function GetPlayerData(){
Firebase.database().ref().child("players").child(currentUser.uid).get().then((snapshot) => {
if (snapshot.exists()) {
setPlayerData(snapshot.val());
console.log(snapshot.val());
console.log(playerData);
console.log(playerData.player_data.playerName);
console.log(playerData.settlement.settlementName);
} else {
console.log("No data available");
}
}).catch((error) => {
console.error(error);
});
}
function SetPioneerAchievement(){
const achievement = {
"MISC001": {
name: "Kickstarter Pioneer",
completed: dateVariable
}
}
player_achievements.set(achievement).catch(alert);
console.log('Achievement added: ' + achievement.name)
}
useEffect(() => {
GetPlayerData();
CheckForPioneer();
}, []);
return (
<div className="dashboard">
<div className="dashboard-box">
<div className="dashboard-content">
<h1>DASHBOARD</h1>
<br /><alert>{error}</alert>
<br />
<center><h3>Name:</h3></center>
<center><h2>Ancestor {playerData && playerData.player_data.playerName}</h2></center>
<center>{pioneer ? <h3 className="pioneer-tag">Pioneer from the 2021 Kickstarter Campaign</h3> : null }</center>
<br /><br />
{ pioneer ? (
<div><center><h3>Founder of the:</h3></center>
<center><h2>{playerData && playerData.settlement.settlementName} Settlement</h2></center></div>
)
: null }
<center><Link to="/update-dashboard" className="link"><input type="submit" id="ancestor-signup-submit" name="dashboard-update-details" value="Update Details" /></Link></center>
<br />
<div class="footer-switch-login">
<center><Link to="/" onClick={handleLogout} className="link">Log out.</Link></center>
</div>
</div>
</div>
</div>
);
}
export default Dashboard;
Any help would be greatly appreciated as I am at a loss. Thanks!
Upvotes: 2
Views: 139
Reputation: 202686
So if I understand your question/issue correctly, the code is essentially working as expected, but sometimes some users/data/state is incomplete. For this the solution is to use null-checks/guard-clauses or the Optional Chaining operator.
You sort of already do this in a couple places:
playerData && playerData.player_data.playerName
and
playerData && playerData.settlement.settlementName
but you aren't checking at each depth of object property access.
Using null-checks/guard-clauses.
playerData &&
playerData.player_data &&
playerData.player_data.playerName
and
playerData &&
playerData.settlement &&
playerData.settlement.settlementName
Using Optional Chaining operator
playerData?.player_data?.playerName
and
playerData?.settlement?.settlementName
I am wondering if I should use
NoPioneer()
(which executes if the user is identified as not having the pioneer achievement) to populate empty values to theplayerData
object so that it at least doesn’t hit null references in the object.
This sounds like a fantastic idea as it helps eliminate the chances of potentially accessing into null or undefined object properties.
Upvotes: 1