简体   繁体   中英

Re-render component with useEffect when something changes in a child component

I have an application that lets a user to add tasks in a list. The tasks are fetched from the API and are displayed with the "List" component. When a user adds a new task from the "AddButton" component the task is stored in the database. I want the to re-render the "List" component when the handleSubmit function happens on the "AddButton" component and adds the task to the database. The "addTask" and "getTasks" are fetching data from the API. Thanks for your help in advance.

List component

import React, { useState, useEffect } from 'react';
import { makeStyles } from '@material-ui/styles';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Moment from 'react-moment';
import { getTasks } from './services/getTasks';
import AddButton from './AddButton';
import './App.css';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    flexDirection: 'column',
    width: '100%',
    justifyContent: 'space-between',
    height: '100%',
    fontSize: '16px',
  },
  listItemLinkRoot: {
    paddingLeft: theme.spacing(3),
    width: '100%',
    '&:hover': {
      backgroundColor: '#212121',
      color: 'white',
    },
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'flex-start',
  },

  buttonContainer: {
    display: 'flex',
    width: '100%',
    flexDirection: 'column',
    justifyContent: 'flex-end',
  },

  list: {
    flexGrow: 1,
    overflow: 'auto',
  },

  listItemText: {
    marginBottom: 8,
    // fontSize: 20,
  },
}));

function ListItemLink(props) {
  return <ListItem button component="a" {...props} />;
}

export default function TaskList() {
  const classes = useStyles();
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const result = await getTasks();
      setTasks(result);
    };

    fetchData();
  }, []);

  return (
    <div className={classes.root}>
      <List classes={{ root: classes.list }}>
        {tasks.map(task => (
          <ListItemLink
            divider
            key={task.id}
            classes={{ root: classes.listItemLinkRoot }}
            href="simple-list"
          >
            <ListItemText
              classes={{ root: classes.listItemText }}
              primary={task.description}
            />
            <Moment
              classes={{ root: classes.listItemDate }}
              format="DD/MM/YYYY"
            >
              {task.createdAt}
            </Moment>
          </ListItemLink>
        ))}
      </List>
      <div className={classes.buttonContainer}>
        <AddButton classes={{ root: classes.add }} />
      </div>
    </div>
  );
}


AddButton component


import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Fab from '@material-ui/core/Fab';
import AddIcon from '@material-ui/icons/Add';
import TextField from '@material-ui/core/TextField';
import { addTask } from './services/postTask';

const useStyles = makeStyles(theme => ({
  cont: {
    display: 'flex',
    flexDirection: 'row',
    paddingBottom: '24px',
    justifyContent: 'space-between',
    backgroundColor: '#e0e0e0',
    width: '100%',
    alignItems: 'center',
    felxGrow: 1,
  },
  fab: {
    marginTop: theme.spacing(2),
    marginRight: theme.spacing(2),
    width: '100%',
  },

  textField: {
    marginLeft: theme.spacing(3),
    marginTop: 0,
    marginBottom: 0,
    flexGrow: 1,
  },
}));

export default function AddButton() {
  const classes = useStyles();
  const [task, setTask] = useState({
    description: '',
    completed: false,
  });

  const handleChange = ev => {
    setTask({ ...task, [ev.target.id]: ev.target.value });
  };

  const handleSubmit = () => {
    addTask(task);
  };

  return (
    <div className={classes.cont}>
      <TextField
        onChange={handleChange}
        id="description"
        label="Add a task"
        rowsMax="4"
        className={classes.textField}
        margin="normal"
      />
      <Fab
        onClick={handleSubmit}
        variant="extended"
        size="small"
        color="primary"
        aria-label="add"
        className={classes.fab}
      >
        <AddIcon />
        Add
      </Fab>
    </div>
  );
}

In your list component you can have your handleSubmit function and pass it down to your child AddButton component:

<AddButton classes={{ root: classes.add }} handleSubmit={handleSubmit} />

One solution I can think of is to move the fetchData function outside of your useEffect hook and pass it to the Button as a prop:


const fetchData = async () => {
     const result = await getTasks();
     setTasks(result);
 };

useEffect(() => {
    fetchData();
  }, []);

...

<AddButton classes={{ root: classes.add }} refetch={fetchData}/>

Then in AddButton (assuming addTask() is async).

const handleSubmit = () => {
    addTask(task)
      .then(res => props.refetch())
  };

Though it may make more sense to handle all of the state functionality in the parent component.

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