简体   繁体   中英

push values in to new array only when material ui checkbox is checked

I am new to react and I am making a simple todo app using react js and material ui. I have separate components to take in user input (TodoInput.js) which sends props to a component that renders individual todo tasks and displays a checkbox (TodoCards.js). What I want to do is display the total number of completed tasks onto the page which is updated when the user completes a todo by checking a checkbox. To achieve this, I have an array that stores all the user's completed tasks. At the moment whenever a checkbox is checked, all tasks are added to this array. I ran into a problem where I am unsure of how to only push values into this new array when the checkbox of that specific task is checked. Any guidance or explanations towards the right direction is greatly appreciated.

TodoInput.js

import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { TextField, Button } from '@material-ui/core';
import { TodoCards } from '../UI/TodoCards';
import { Progress } from '../UI/Progress';

const useStyles = makeStyles((theme) => ({
    root: {
        '& > *': {
            margin: theme.spacing(1),
            width: '25ch',
            textAlign: 'center'
        },
    },
}));

export default function TodoInput() {
    const classes = useStyles();
    const [userInput, setUserInput] = useState({
        id: '',
        task: ''
    });

    const [todos, setTodos] = useState([])
    //state for error
    const [error, setError] = useState({
        errorMessage: '',
        error: false
    })

    //add the user todo with the button
    const submitUserInput = (e) => {
        e.preventDefault();

        //add the user input to array
        //task is undefined
        if (userInput.task === "") {
            //render visual warning for text input
            setError({ errorMessage: 'Cannot be blank', error: true })
            console.log('null')
        } else {
            setTodos([...todos, userInput])
            console.log(todos)
            setError({ errorMessage: '', error: false })
        }
        console.log(loadedTodos)
    }

    //set the todo card to the user input
    const handleUserInput = function (e) {
        //make a new todo object
        setUserInput({
            ...userInput,
            id: Math.random() * 100,
            task: e.target.value
        })
        //setUserInput(e.target.value)
        //console.log(userInput)
    }

    const loadedTodos = [];
    for (const key in todos) {
        loadedTodos.push({
            id: Math.random() * 100,
            taskName: todos[key].task
        })
    }

    return (
        <div>
            <Progress taskCount={loadedTodos.length} />
            <form className={classes.root} noValidate autoComplete="off" onSubmit={submitUserInput}>
                {error.error ? <TextField id="outlined-error-helper-text" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} error={error.error} helperText={error.errorMessage} />
                    : <TextField id="outlined-basic" label="Today's task" variant="outlined" type="text" onChange={handleUserInput} />}
                <Button variant="contained" color="primary" type="submit">Submit</Button>
                {userInput && <TodoCards taskValue={todos} />}
            </form>
        </div>
    );
}

TodoCards.js

import React, { useState } from 'react'
import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '@material-ui/core';
import { CompletedTasks } from './CompletedTasks';


export const TodoCards = ({ taskValue }) => {
    const [checked, setChecked] = useState(false);

    //if checked, add the task value to the completed task array
    const completedTasks = [];

    const handleChecked = (e) => {
        setChecked(e.target.checked)
        for (const key in taskValue) {
            completedTasks.push(taskValue[key])
        }

        console.log(completedTasks.length)
    }

    return (
        < div >
            <CompletedTasks completed={completedTasks.length} />
            <Card>
                {taskValue.map((individual, i) => {
                    return (
                        <CardContent key={i}>
                            <Typography variant="body1">
                                <FormControlLabel
                                    control={
                                        <Checkbox
                                            color="primary"
                                            checked={checked[i]}
                                            onClick={handleChecked}
                                        />
                                    }
                                    label={individual.task} />
                            </Typography>
                        </CardContent>
                    )
                })}


            </Card>
        </div >
    )
}

CompletedTasks.js (displays the total number of completed tasks)

import React from 'react'
import InsertEmoticonOutlinedIcon from '@material-ui/icons/InsertEmoticonOutlined';
import { Typography } from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';

const useStyles = makeStyles((theme) => ({
    root: {
        flexGrow: 1,
    },
    paper: {
        padding: theme.spacing(2),
        marginTop: '20px',
        textAlign: 'center',
        color: theme.palette.text.secondary,
    },
}));

export const CompletedTasks = ({ completed }) => {
    const classes = useStyles();
    return (
        <div className={classes.root}>
            <InsertEmoticonOutlinedIcon fontSize="large" />
            <Typography variant="h6">
                Completed tasks:{completed}
            </Typography>
        </div>
    )
}

One issue I see here is that you start with a boolean type checked state in TodoCards , and only ever store a single boolean value of the last checkbox interacted with. There's no way to get a count or to track what's previously been checked.

Use an object to hold the completed "done" checked values, then count the number of values that are checked (ie true ) after each state update and rerender. Use the task's id as the key in the checked state.

export const TodoCards = ({ taskValue = [] }) => {
  const [checked, setChecked] = useState({});

  const handleChecked = id => e => {
    const { checked } = e.target;
      setChecked((values) => ({
        ...values,
        [id]: checked
      }));
  };

  return (
    <div>
      <CompletedTasks
        completed={Object.values(checked).filter(Boolean).length}
      />
      <Card>
        {taskValue.map(({ id, task }) => {
          return (
            <CardContent key={id}>
              <Typography variant="body1">
                <FormControlLabel
                  control={
                    <Checkbox
                      color="primary"
                      checked={checked[id]}
                      onClick={handleChecked(id)}
                    />
                  }
                  label={task}
                />
              </Typography>
            </CardContent>
          )
        })}
      </Card>
    </div >
  )
}

编辑 push-values-in-to-new-array-only-when-material-ui-checkbox-is-checked

To do this, you first need to only push the checked key to your array:


  • First send your key to the eventHandler:
{taskValue.map((individual, i) => {
                    return (
                        <CardContent key={i}>
                            <Typography variant="body1">
                                <FormControlLabel
                                    control={
                                        <Checkbox
                                            color="primary"
                                            checked={checked[i]}
                                            onClick={() => handleChecked(individual)}
                                        />
                                    }
                                    label={individual.task} />
                            </Typography>
                        </CardContent>
                    )
                })}
  • Then in your Handler, push it into the array:
    const handleChecked = (key) => {
        //setChecked(e.target.checked)
        completedTasks.push(key)
        console.log(completedTasks.length)
    }
  • BUT

Because you are not modifying any state so the changes won't be updated to the UI, you need to use a state to store your completedTasks .

  const [completedTasks, setCompletedTasks] = useState([]);

  const handleChecked = (key) => {
        setCompletedTasks([...completedTasks, key])
    }

Please note that this is only a guide so you can get to the right way, not a complete working example

In the hopes that someone else may find this useful, I was able to come up with a solution thanks to the suggestions given. Below is the updated code for the TodoCards.js component:

import React, { useState } from 'react'
import { Card, CardContent, Typography, FormControlLabel, Checkbox } from '@material-ui/core';
import { CompletedTasks } from './CompletedTasks';


export const TodoCards = ({ taskValue }) => {
    const [checked, setChecked] = useState(false);
    //if checked, add the task value to the completed task array
    const [completedTasks, setCompletedTasks] = useState([]);

    const handleChecked = key => {
        setCompletedTasks([...completedTasks, key])
        completedTasks.push(key)
        console.log(completedTasks.length)
        setChecked(true)
    };

    if (taskValue.length === completedTasks.length) {
        console.log('all tasks complete')
    }

    return (
        <div>
            <CompletedTasks completed={completedTasks.length} />
            <Card>
                {taskValue.map((individual, i) => {
                    return (
                        <CardContent key={i}>
                            <Typography variant="body1">
                                <FormControlLabel
                                    control={
                                        <Checkbox
                                            color="primary"
                                            checked={checked[i]}
                                            onClick={() => handleChecked(individual)}
                                        />
                                    }
                                    label={individual.task}
                                />
                            </Typography>
                        </CardContent>
                    )
                })}
            </Card>
        </div >
    )
}

Only the checked todo items are pushed into the new array (completedTasks) and this is updated using 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