'React - component doesn't re-render after delete request

I have problem that after deleting element from my list, component doesn't want to re-render. When I go through routing to some subpage and then back, item is already removed, same if I sort list, then it also disappears. The thing is that I don't want to force user to refresh the website or sort to see the effect. It used to work if I pass to

deviceList props as a "props.data" instead of "devices"

(will bold the place in the code). Does anyone has idea how to fix it? I'm still kinda new to react hooks and perhaps I just gotta adjust useEffect a bit better, props.selectedDevices is taken from redux and just gets the ID of deleted item, my code:

Parent:

function App(props) {
  const [data, setData] = useState("")
  const [filter, setFilter] = useState([])


  useEffect(() => getDeviceDataTotal(), [])

  const getDeviceDataTotal = () => {
    console.log('refresh clicked')
    fetch("http://localhost:4000/device")
      .then(res => res.json())
      .then(res => setData(res))
      .catch(err => {
        throw err;
      });
  };

  const deleteDevices = (e) => {
    console.log('delete clicked')
    props.selectedDevices.map(el => {
      return fetch(`http://localhost:4000/device/delete/${el}`, {
        method: "DELETE"
      })
        .then(res => res.json())
        .then(() => getDeviceDataTotal())
        .catch(err => {
          throw err;
        });
    });
  }

  const filterList = e => {
    let filteredList;
    filteredList = data.filter(el => {
      return el.device_id.includes(searched);
    });
    setFilter(filteredList)
  };


  return (
    <BrowserRouter>
      <MuiThemeProvider theme={createMuiTheme(theme("#96BF1B", "#ffffff"))}>

        <Switch>
          <Route
            exact
            path="/"
            render={() => <Dashboard classes={classes} dataDevice={data} refresh={getDeviceDataTotal} filtered={filter} filterList={filterList} deleteDevices={deleteDevices}/> }
          />
          ......
        </Switch>
      </MuiThemeProvider>
    </BrowserRouter>
  );
}

Child Component:

function Dashboard(props) {
  const [selectedSort, setSelectedSort] = useState("1")
  const [devices, setDevices] = useState(props.dataDevice)


  useEffect(() => {
    let isSubscribed = true
    const fetchData = async() => {
      try {
        const res = await fetch("http://localhost:4000/device")
        const response = await res.json()
        if (isSubscribed) {
          setDevices(response)
         }
      } catch (err) {
        console.log(err)
      }
    }

    fetchData()
    return () => isSubscribed = false
  },[])

  useEffect(() => props.refresh() ,[devices])


  useEffect(() => sortDeviceData(), [selectedSort])

  const sortDeviceData = () => {
    switch(selectedSort) {
      case "device":
        (filtered.length ? filtered : dataDevice).sort(function(a,b) {
          return (a.device_id.toUpperCase() < b.device_id.toUpperCase()) ? -1 : (a.device_id.toUpperCase() > b.device_id.toUpperCase()) ? 1 : 0
        })
        setDevices(dataDevice)
        break;
      case "customer":
        (filtered.length ? filtered : dataDevice).sort(function(a,b) {
          return (a.customer.toUpperCase() < b.customer.toUpperCase()) ? -1 : (a.customer.toUpperCase() > b.customer.toUpperCase()) ? 1 : 0
        })
        setDevices(dataDevice)
        break;
      case "server":
        (filtered.length ? filtered : dataDevice).sort(function(a,b) {
          return (a.server.toUpperCase() < b.server.toUpperCase()) ? -1 : (a.server.toUpperCase() > b.server.toUpperCase()) ? 1 : 0
        })
        setDevices(dataDevice)
        break;
      case "creation":
        (filtered.length ? filtered : dataDevice).sort(function(a,b) {
          return (a.createdAt.toUpperCase() < b.createdAt.toUpperCase()) ? -1 : (a.createdAt.toUpperCase() > b.createdAt.toUpperCase()) ? 1 : 0
        })
        setDevices(dataDevice)
        break;
      case "update":
        (filtered.length ? filtered : dataDevice).sort(function(a,b) {
          return (a.updatedAt.toUpperCase() < b.updatedAt.toUpperCase()) ? -1 : (a.updatedAt.toUpperCase() > b.updatedAt.toUpperCase()) ? 1 : 0
        })
        setDevices(dataDevice)
        break;    
      default: 
        return setDevices(dataDevice)
    }
  }


  const { classes, logged, dataDevice, filtered } = props;
  return logged ? (
    <main style={{ display: "flex" }} key={dataDevice}>
      <section className={classes.sectionLeftContainer}>

        <div className={classNames(classes.search, classes.tableBackground)}>
          <InputBase
            placeholder="Search…"
            classes={{
              root: classes.inputRoot,
              input: classes.inputInput
            }}
            inputProps={{ "aria-label": "Search" }}
            onChange={e => props.getSearched(e.target.value)}
          />
          <Button
            variant="outlined"
            className={classes.searchBtn}
            onClick={e => props.filterList(e)}
          >
            Search
          </Button>
          <Button
            variant="outlined"
            className={classes.btnAddDevice}
            onClick={e => {
              if (window.confirm("Are you sure you want to delete this device")) {
                props.deleteDevices(e);
              }
            }}
          >
            <DeleteIcon />
            Delete devices
          </Button>
        </div>
        <div>
          <FormControl className={classes.selectContainer}>

            <Select 
              value={selectedSort} 
              style={{position: 'absolute', right: 0, bottom: 10, width: 150}}
              onChange={e => setSelectedSort(e.target.value)}
              >
              <MenuItem value="1">Default</MenuItem>
              <MenuItem value="device">Device</MenuItem>
              <MenuItem value="customer">User</MenuItem>
              <MenuItem value="server">Server</MenuItem>
              <MenuItem value="creation">Creation date</MenuItem>
              <MenuItem value="update">Update date</MenuItem>
            </Select>
          </FormControl>
        </div>
        <div>
          <DeviceList
            classes={classes}
            **deviceData={devices} if props.data - works well but sorting doesn't work**
            filtered={filtered}
          />
        </div>
      </section>
    </main>
}


Solution 1:[1]

There are multiple ways to force a React component render but they are essentially the same. The first is using this.forceUpdate(), which skips shouldComponentUpdate:

someMethod() {
    // Force a render without state change...
    this.forceUpdate();
}

Assuming your component has a state, you could also call the following:

someMethod() {
    // Force a render with a simulated state change
    this.setState({ state: this.state });
}

There's likely a better, more "React-y" way to render a component properly, but if you are desperate to get a component render on command, this will do.

Solution 2:[2]

You should pull this delete function up to the parent component that holds the list of devices. Then pass the function down as props and call it from the child component with the device you want to delete.

const deleteDevices = (e) => {
      return fetch(`http://localhost:4000/device/delete/${e}`, {
        method: "DELETE"
      })
        .then(res => res.json())
        .then(_ => setDevice(devices.filter(dvice => device !== e)))
        .catch(err => {
          throw err;
        });
  }

Solution 3:[3]

Try updating your parent component useEffect hook.

//...

const getDeviceDataTotal = () => { 
    // Api call...
};

// Reference the getDeviceDataTotal function inside the hook.
useEffect(getDeviceDataTotal, []);

//...

Alternatively wrap your api call function in a useCallback hook and include it as a dependency in your useEffect hook.

//...

const getDeviceDataTotal = useCallback(() => { 
    // Api call...
},[]);

// Include getDeviceDataTotal as a dependency.
useEffect(() => getDeviceDataTotal(), [getDeviceDataTotal]);

//...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Huzaifa
Solution 2 Ahmed Radi
Solution 3