BettyWhite
BettyWhite

Reputation: 159

How to set/access data object correctly using React with a Firebase database?

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

Answers (1)

Drew Reese
Drew Reese

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.

  1. Using null-checks/guard-clauses.

     playerData &&
     playerData.player_data &&
     playerData.player_data.playerName
    

    and

     playerData &&
     playerData.settlement &&
     playerData.settlement.settlementName
    
  2. 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 the playerData 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

Related Questions