Home > Software engineering >  How can I get my delete button to remove items from state more than once consecutively?
How can I get my delete button to remove items from state more than once consecutively?

Time:02-19

Sorry about the confusing wording, but I'll do my best to explain. Here is the relevant chunk of code. The idea is to take a keyboard input, and add it to an array saved in state. It works partially, in that when I click a key, it adds to the state array. When I click delete, it removes the last item by copying the array, removing the last item, then overwriting the entire original in state.

Here's the issue. If delete is clicked again, nothing happens. I'm guessing it has something to do with react not re-rendering, but I'm a noob so idk.

Here is a link to the project on expo, for what its worth(you cant see the console logs obviously). Sorry about the bright colors, helps my noob brain keep things separate. https://expo.dev/@qtheginger/projects/Wordley

Any help would be appreciated, Thanks!

import React, { useState, useContext } from "react";
import { View, Text, Pressable, StyleSheet, Alert } from "react-native";
import Key from "./Key.js";
import KeyName from "../data/KeyName.json";

import { GameContext } from "../util/gameContext.js";

const styles = StyleSheet.create({
  keyBoard: {
    flex: 1,
    justifyContent: "space-around",
  },
  keyBoardLine: {
    flex: 1,
    flexDirection: "row",
    justifyContent: "space-around",
    // height: "80%",
  },
});

export default () => {
  const { keyVal, setKeyVal } = useContext(GameContext);
  const { cardVal, setCardVal } = useContext(GameContext);
  let rowOne = [];
  let rowTwo = [];
  let rowThree = [];
  
  const keyValSetter = (keyName) => {
    setKeyVal(keyName);
  };
  const cardValSetter = (keyName) => {
    setCardVal([...cardVal, keyName]);
  };
  const combiner = (keyName) => {
    keyValSetter(keyName);
    if (keyName !== "Delete") {
      cardValSetter(keyName);
    } else {
      const newArr = cardVal;
      newArr.pop();
      setCardVal(newArr);
    }
  };
  // loop through keyName to display keyboard
  for (let i = 0; i < KeyName.length; i  ) {
    x = i;
    if (i < 10) {
      rowOne.push(
        <Key onPress={() => combiner(KeyName[i])} key={i} index={i} />
      );
    } else if (i === 10 || i < 19) {
      rowTwo.push(
        <Key onPress={() => combiner(KeyName[i])} key={i} index={i} />
      );
    } else {
      rowThree.push(
        <Key onPress={() => combiner(KeyName[i])} key={i} index={i} />
      );
    }
  }
  console.log(keyVal);
  console.log(cardVal);
  return (
    <>
      <View style={styles.keyBoard}>
        <View style={styles.keyBoardLine}>{rowOne}</View>
        <View style={styles.keyBoardLine}>{rowTwo}</View>
        <View style={styles.keyBoardLine}>{rowThree}</View>
      </View>
    </>
  );
};

CodePudding user response:

Yes, when deleting from state you are mutating it instead of returning a new array.

const { cardVal, setCardVal } = useContext(GameContext);

...

const combiner = (keyName) => {
  keyValSetter(keyName);
  if (keyName !== "Delete") {
    cardValSetter(keyName);
  } else {
    const newArr = cardVal; // <-- reference to state
    newArr.pop();           // <-- mutation!!
    setCardVal(newArr);     // <-- state reference back into state
  }
};

The cardVal state doesn't appear to ever be an updated array reference. Since the state reference never changed React assumes state is still the same and doesn't trigger a rerender.

Use a functional state update to correctly update from a previous state (instead of whatever state is closed over in scope), and shallow copy the state into a new array reference.

Example:

const combiner = (keyName) => {
  keyValSetter(keyName);
  if (keyName !== "Delete") {
    cardValSetter(keyName);
  } else {
    setCardVal(cards => cards.slice(0, -1)) // <-- copy all but last element
  }
};

Similarly, cardValSetter should also use a functional state update to update from the previous state.

const cardValSetter = (keyName) => {
  setCardVal(cardVal => [...cardVal, keyName]);
};
  • Related