skip to Main Content

I am new to React Native and I try to implement a "reset feature" in my game.
And allthough the game data is reset correctly I can’t find out how to rerender the components that are displaying the elements…

Note: some components have been simplified, but they should still reflect the purpose

My gameData is structured as follows:

{
    "puzzles" : [
        { "id": 0, "icon": "0", "codes": ["abc], "unlocked": true },
        { "id": 1, "icon": "1", "codes": ["def"] },
        { "id": 2, "icon": "2", "codes": ["ghi"] }
    ]
}

so the types for this are

export type GameData = {
    puzzles: Puzzle[],
}

export type Puzzle = {
    id: number,
    icon: string,
    codes: string[],
    unlocked: boolean,
};

In my game I create a gameContext that allows unlocking the individual Puzzles and (if you want to start over) resetting the state. Therefore I initially load the gameData from a json and from there on I save the unlocked states in the AsyncStorage (for a better overview I removed the save & load part – this works fine):

const game = (() => {
    // load the initial state from file
    const gameData: GameData = require('./gamedata.json');

    async function unlockPuzzle(item: Puzzle) {
        item.unlocked = true;
        // save to AsyncStorage
        saveItemState("puzzle", item.id, true); 
    };

    async function loadProgress() {
        gameData.puzzles.forEach((item) => {
            // load from AsyncStorage
            loadItemState("puzzle", item.id).then(unlocked => {
                item.unlocked ||= unlocked;
            });
        });
    };

    async function resetProgress() {
        console.log("reset progress");
        gameData.puzzles.forEach((item) => {
            item.unlocked = false;
            // save to AsyncStorage
            saveItemState("puzzle", item.id, item.unlocked); 
        });
    };

    return {
        gameData,
        unlockPuzzle,
        loadProgress,
        resetProgress,
    };
}) ();

export const gameContext = createContext(game);

Now I created a PuzzleCard component to render the Puzzle items:

export type PuzzleCardProps = {item: Puzzle} & View['props'];
export function PuzzleCard(props: PuzzleCardProps) {
    const game = useContext(gameContext);
    const { item, ...otherProps } = props;  
    const [unlocked, setUnlocked] = useState(item.unlocked);
    const handlePress = function() {
        game.unlockPuzzle(item);
        setUnlocked(true);
    };
    return  <Pressable onPress={handlePress} style={styles.card}>
                <Text style={styles.cardIcon}>{item.icon}</Text>
                <Text style={styles.cardState}>{unlocked ? "🔓" : "🔒"}</Text>
            </Pressable>;
}

and added these items to a list on a tab in my application:

        <View style={styles.container}>
            {game.gameData.puzzles.map((puzzle: Puzzle, index: number) => {
                return  <PuzzleCard key={index} item={puzzle}/>
            })}
        </View>

Everything works fine so far: If the PuzzleCard is clicked, then

  1. the matching Puzzle item is unlocked
  2. the state is stored to AsyncStorage
  3. the PuzzleCard is updated (the state changes from "🔓" to "🔒")

Now I want to add a button on a different view in the application to reset the progress, when the player wants to start all over:

export function ResetButton() {
    const game = useContext(gameContext);
    return <Pressable onPress={ game.resetProgress(); }>Reset</Pressable>
}

Resetting works as intended (in the background)

  1. all Puzzle items are locked
  2. the state is stored to AsyncStorage

But

  1. the list of PuzzleCards is NOT updated (state remains "🔒")

If I reload the app / refresh the browser, all items are rerendered and displayed correctly with the new (reset) state.

How can I make sure, that my app rerenders to reflect the changes? (without the need of a reload)

2

Answers


  1. In your context you should use a state to store the data you send, so the updates can be hooked by the components using it.

    For example, you can use a state for gameData

    const [gameData, setGameData] = useState(require('./gamedata.json'));
    

    Then update it with the setGameData function

    async function resetProgress() {
      setGameData({
        ...gameData,
        puzzles: gameData.puzzles.map((item) => {
          item.unlocked = false;
          saveItemState("puzzle", item.id, item.unlocked); 
          return item;
        });
      });
    };
    
    

    That way, the parts of the template that render the data of gameData context will be updated everytime setGameData is called.

    Does this approach suit you ?

    Login or Signup to reply.
  2. It seems like you are not fully understanding the use of context within React. Your gameData is not stateful and therefore will not trigger any re-render of any components relying on it.

    const jsonGameData: GameData = require('./gamedata.json');
    
    const GameContext = createContext({
     gameData: undefined as GameData | undefined,
     unlockPuzzle: (item: Puzzle) => void
     ...yourOtherMethods
    })
    
    const GameContextProvider = ({ children }) => {
     const [gameData, setGameData] = useState<GameData>(jsonGameData)
    
     async function unlockPuzzle(item: Puzzle) {
       item.unlocked = true;
       // save to AsyncStorage
       const newState = await saveItemState("puzzle", item.id, true);  // Make sure to return the new state
       // Setting the state will trigger a re-render of your components
       setGameData(newState);
     };
    
     return <GameContext.Provider value={{
       gameData,
       unlockPuzzle,
       ...yourOtherMethods
      }}>
       {children}
     </GameContext.Provider>
    }
    
    // App.tsx
    
    const App = () => {
     <GameContextProvider>
      // ... your app
    
     </GameContextProvider>
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search