Home > Software engineering >  React: Entering boolean 'true' as prop end up as 'false' in the component
React: Entering boolean 'true' as prop end up as 'false' in the component

Time:10-12

I am utterly confused by this. I have a select box component where I have a selected prop. when true I show a checkmark in the box, if false, not. Now the issue that I have is that after three times clicking it doesn't toggle anymore.

The strangest thing is that I do pass a true boolean to the component's 'selected' prop (see logs), but when I make a log of the 'selected' prop in that child component, it says it is false.

Anyone has a clue why this could be different?

Result of the logs seen below

enter image description here

Parent component: Services.tsx

    import React, { useReducer } from "react";
import { makeStyles } from "@material-ui/core";
import { ToggleBox } from "components/ToggleBox";

const useStyles = makeStyles((theme) => ({
  container: {
    display: "flex",
  },
}));

const servicesReducer = (state, action) => {
  switch (action.type) {
    case "toggle option":
      const isCurrentlySelected = state.selectedOptions.includes(
        action.payload.name
      );
      let newSelectedOptions = state.selectedOptions;

      if (isCurrentlySelected) {
        newSelectedOptions = newSelectedOptions.filter(
          (item) => item !== action.payload.name
        );
      } else {
        newSelectedOptions.push(action.payload.name);
      }

      return {
        ...state,
        selectedOptions: newSelectedOptions,
      };
    case "add options":
      return {
        ...state,
      };
  }
};

export const Services = () => {
  const classes = useStyles();
  const [state, dispatch] = useReducer(servicesReducer, {
    financialPlanning: {
      description: "",
      minHours: null,
      maxHours: null,
      minPrice: null,
      maxPrice: null,
    },
    selectedOptions: [],
  });

  const check = state.selectedOptions.includes("financialPlanning");
  console.log("check", check);
  console.log("check2", state);

  return (
    <div className={classes.container}>
        <ToggleBox
          selected={check}
          onClick={() => {
            console.log("click");
            dispatch({
              type: "toggle option",
              payload: { name: 'financialPlanning' },
            });
          }}
          title="Financiële planning"
        >
          Hey
        </ToggleBox>
    </div>
  );
};

child component: ToggleBox.tsx

import React from 'react';

import { Box, Card, CardContent, Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked';
import CheckCircleOutlineIcon from '@material-ui/icons/CheckCircleOutline';
import { responsivePadding } from 'helpers';

export interface ToggleBoxProps {
  title: string;
  description?: string;
  rightIcon?: React.ReactElement;
  selected: boolean;
  focused?: boolean;
  children?: React.ReactNode;
  onClick?: () => void;
}

const useStyles = makeStyles(theme => ({
  root: ({ selected, focused }: ToggleBoxProps) => {
    let borderColor = theme.palette.grey[300];
    if (focused) {
      borderColor = theme.palette.primary.main;
    } else if (selected) {
      // eslint-disable-next-line prefer-destructuring
      borderColor = theme.palette.grey[500];
    }

    return {
      border: `1px solid ${borderColor}`,
      height: '100%',
    };
  },
  content: {
    height: '90%',
    display: 'flex',
    flexDirection: 'column',
  },
  header: {
    display: 'flex',
    cursor: 'pointer',
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingBottom: theme.spacing(2),
    marginBottom: theme.spacing(2),
    borderBottom: `1px solid ${theme.palette.divider}`,
    color: theme.palette.text.secondary,
  },
  title: {
    flex: 1,
    marginLeft: theme.spacing(2),
  },
}));

export const ToggleBox: React.FC<ToggleBoxProps> = (
  props: ToggleBoxProps,
) => {
  console.log('props toggleBox', props);
  const { title, description, rightIcon, selected, children, onClick } = props;
  console.log('selected check prop Togglebox', selected);

  const classes = useStyles(props);

  return (
    <Card className={classes.root}>
      <CardContent className={classes.content}>
        <Box className={classes.header} onClick={onClick}>
          {selected ? <CheckCircleOutlineIcon /> : <RadioButtonUncheckedIcon />}
          <Typography className={classes.title} color='textSecondary'>
            {title}
          </Typography>
          {rightIcon}
        </Box>
        <Typography variant='body2' color='textSecondary'>
          {description}
        </Typography>
        {selected && children}
      </CardContent>
    </Card>
  );
};

CodePudding user response:

You seem to be mutating your state when adding a value to the selectedOptions array. .push mutates an existing array in-place.

case "toggle option":
  const isCurrentlySelected = state.selectedOptions.includes(
    action.payload.name
  );
  let newSelectedOptions = state.selectedOptions; // <-- saved reference to state

  if (isCurrentlySelected) {
    newSelectedOptions = newSelectedOptions.filter(
      (item) => item !== action.payload.name
    );
  } else {
    newSelectedOptions.push(action.payload.name); // <-- mutation!
  }

  return {
    ...state,
    selectedOptions: newSelectedOptions,
  };

In either case of adding or removing you necessarily need to return a new array reference. You can use Array.prototype.concat to add a value to an array and return a new array reference.

case "toggle option":
  const isCurrentlySelected = state.selectedOptions.includes(
    action.payload.name
  );

  let newSelectedOptions = state.selectedOptions;

  if (isCurrentlySelected) {
    newSelectedOptions = newSelectedOptions.filter(
      (item) => item !== action.payload.name
    );
  } else {
    newSelectedOptions.concat(action.payload.name); // <-- add to and return new array
  }

  return {
    ...state,
    selectedOptions: newSelectedOptions,
  };
  • Related