简体   繁体   中英

Checkboxes in map not updating on array update after refactor to react hooks

I converted a class component into a function component using hooks. Currently, I'm struggling to figure out why the checkboxes within this map is not updating with checked value, despite the onChange handler firing, and updating the array as necessary. (The onSubmit also works, and updates the value within the DB properly).

import {
  Container,
  Typography,
  Grid,
  Checkbox,
  FormControlLabel,
  Button
} from "@material-ui/core";
import Select from "react-select";
import localeSelect from "../services/localeSelect";
import {
  linkCharactersToGame,
  characterLinked,
  linkCharacters
} from "../data/locales";
import dbLocale from "../services/dbLocale";
import { LanguageContext } from "../contexts/LanguageContext";
import { UserContext } from "../contexts/UserContext";
import { GameContext } from "../contexts/GameContext";
import { CharacterContext } from "../contexts/CharacterContext";
import { Redirect } from "react-router-dom";

export default function LinkCharacter() {
  const { language } = useContext(LanguageContext);
  const { user } = useContext(UserContext);
  const { games, loading, error, success, connectCharacters } = useContext(
    GameContext
  );
  const { characters } = useContext(CharacterContext);
  const [game, setGame] = useState("");
  const [selectedCharacters, setSelectedCharacters] = useState([]);
  if (!user) {
    return <Redirect to="/" />;
  }
  return (
    <section className="link-character">
      <Container maxWidth="sm">
        <Typography variant="h5">
          {localeSelect(language, linkCharactersToGame)}
        </Typography>
        {error && (
          <p className="error">
            <span>Error:</span> {error}
          </p>
        )}
        {success && <p>{localeSelect(language, characterLinked)}</p>}
        <Select
          options={games.map(game => {
            return {
              label: dbLocale(language, game),
              value: game._id
            };
          })}
          onChange={e => {
            setGame(e.value);
            const selected = [];
            const index = games.findIndex(x => x._id === e.value);
            games[index].characters.forEach(character => {
              selected.push(character._id);
            });
            setSelectedCharacters(selected);
          }}
        />
      </Container>
      <Container maxWidth="md">
        {game !== "" && (
          <>
            <Grid container spacing={2}>
              {characters.map((character, index) => {
                return (
                  <Grid item key={index} md={3} sm={4} xs={6}>
                    <FormControlLabel
                      control={
                        <Checkbox
                          value={character._id}
                          onChange={e => {
                            const index = selectedCharacters.indexOf(
                              e.target.value
                            );
                            if (index === -1) {
                              selectedCharacters.push(e.target.value);
                            } else {
                              selectedCharacters.splice(index, 1);
                            }
                          }}
                          color="primary"
                          checked={
                            selectedCharacters.indexOf(character._id) !== -1
                          }
                        />
                      }
                      label={dbLocale(language, character)}
                    />
                  </Grid>
                );
              })}
            </Grid>
            <Button
              variant="contained"
              color="primary"
              onClick={e => {
                e.preventDefault();
                connectCharacters(game, selectedCharacters);
              }}
            >
              {localeSelect(language, linkCharacters)}
            </Button>
          </>
        )}
      </Container>
    </section>
  );
}

I feel like there's something I'm missing within Hooks (or there's some sort of issue with Hooks handling something like this). I have been searching and asking around and no one else has been able to figure out this issue as well.

The state returned by [state, setState] = useState([]) is something that you should only be reading from. If you modify it, React won't know that the data has changed and that it needs to re-render. When you need to modify data, you have to use setState , or in your case setSelectedCharacters .

Also, modifying the data by reference might lead to unpredictable results if the array is read elsewhere, later on.

In addition to that, if you give the same value to setState , that the hook returned you in state , React will skip the update entirely. It is not a problem when using numbers or strings, but it becomes one when you use arrays, because the reference (the value React uses to tell if there is a difference) can be the same, when the content might have changed. So you must pass a new array to setState .

With that in mind, your onChange function could look like:

onChange={e => {
  const index = selectedCharacters.indexOf(
    e.target.value
  );
  if (index === -1) {
    // creating a new array with [], so the original one stays intact
    setSelectedCharacters([...selectedCharacters, e.target.value]);
  } else {
    // Array.filter also creates new array
    setSelectedCharacters(selectedCharacters.filter((char, i) => i !== index));
  }
}}

Doc is here https://en.reactjs.org/docs/hooks-reference.html#usestate

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM