Skip to content

state overwritten: useSelector variable not getting updated following action #1481

@JESii

Description

@JESii

Do you want to request a feature or report a bug?
Bug, possibly.
(If this is a usage question, please do not post it here—post it on Stack Overflow instead. If this is not a “feature” or a “bug”, or the phrase “How do I...?” applies, then it's probably a usage question.)

What is the current behavior?
A component's useSelector value is not updated when an action is fired from a utility function that changes that state. The action that is fired is clearly visible (and successful) in the Redux state timeline.
See discussion and code sample below; unable so far to get a sandox demo running... sorry.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have dependencies other than React. Paste the link to a CodeSandbox (https://codesandbox.io/s/new) or RN Snack (https://snack.expo.io/) example below:

What is the expected behavior?
Since useSelector is subscribing to changes to it's values, its value should be updated upon the change.

Which versions of React, ReactDOM/React Native, Redux, and React Redux are you using? Which browser and OS are affected by this issue? Did this work in previous versions of React Redux?
This is a new use case for me, so cannot compare to previous versions.
Redux : 4.0.4
React : 16.12.0
React-Redux : 7.1.1
(FYI: already posted on SO:

Discussion
I have several components that use a Modal to upload files and then submit them for processing by our backend. The user may upload one or more files and they are stored in Redux and then listed on the main page. The submission to the backend is done by a utility function which issues the API calls and checks status to ensure that the backend processing was correctly submitted. Each file submitted to the backend is identified by a groupId. The upload and subsequent submission is kicked off when the user clicks the "CONTINUE" button on the FileDrop component.

This was all working just fine until the requirement came along to exclude any duplicate files (by fileName for now). If a duplicate file is detected (by querying the list of files in the Redux store), the user is offered the opportunity to cancel the request or replace the existing file... and that's where the trouble is.

Here's a skeleton of the code, showing the relevant pieces. Cancelling a request works just fine; but I'm unable to correctly update the Redux store when replacing a file.

The flow through the code is as follows:
[1] User requests a file upload and component ModalFileDropPage is invoked
[2] The FileDrop component is invoked; the modal is displayed, the user selects a file, and clicks the "CONTINUE" button.
[3] The handleModalUpload function is invoked, which submits the uploaded file to the backend. It is passed the function removeGroupIdFromRedux as a parameter.
[4] If the file is a duplicate and the user requests that it replace the previous file, we delete the file from the backend and then request the associated Redux item be removed via the link to the removeGroupIdFromRedux function. The current request for the uploaded file is handled in the normal fashion, returning to continueToNextPage with the mediaInfo (or possibly an error)

// The main component that requests the file upload and submits it for processing
import React from 'react';

// [1]
const ModalFileDropPage = props => {
  const logos = useSelector(state => state.files.logos);

// [4]
  const removeGroupIdFromRedux = async id => {
    await dispatch(storeLogos([...logos.filter(({ groupId }) => groupId !== id)]));
  };

  const continueToNextPage = async currentPage => {
    setMediaRoleId(mediaRoles);
// [3]
    const mediaInfo = await handleModalUpload();
    if (!mediaInfo.response) { // update redux if no errors
      await dispatch(storeLogos([...logos, {
        // SUCCESS: store uploaded file info in Redux
      }]));
    } else {
      // ERROR: clear file data and cancel FileDrop modal
    }
  };

// [2]
  return (
    <FileDrop>
      <Button onClick={event => continueToNextPage(1, event)}>CONTINUE</Button>
    </FileDrop>
  );
};

// from the separate file containing handleModalUpload utility function
const handleModalUpload = async (
  file,                 // file name being processed
  allFiles,             // function to provide all files data from Redux
  removeGroupFromRedux, // function to delete original file from Redux
  setUploading,         // Uploading state function
) => {
  setUploading(true);

  try {
    /* Test for duplicate fileName and prompt user to either:
     * 1) cancel the upload
     * 2) replace the original file with the new copy of the file
     */
    const replace = swal('cancel or replace', { buttons : true }); // uses SweetAlert
  } catch (err) {
    if (!replace) {
      throw new DuplicateFileName('Duplicate file name'); // this works just fine
    } else {
      const success = await deleteMediaAsset(oldFile.groupId); // Delete file from backend
      if (!success) {
        throw new ReplaceDeleteError('Unable to delete existing file for replacement');
      }

// Excerpt from the reducers...
import { filesActions } from '../actions/actionTypes';
import filesInitialState from '../../components/Files/initialState';

export default function reducer(state = filesInitialState, action) {
  switch (action.type) {
    case filesActions.STORE_LOGOS: {
      return {
        ...state,
        logos : action.payload
      };
    }
    case filesActions.STORE_LOGOS_PAGE: {
      return {
        ...state,
        logosPage : action.payload
      };
    }
    case filesActions.STORE_LOGOS_URL: {
      const { logos, logosPageCurrentGroup } = state;
      const logoIndex = logos.findIndex(item => item.groupId === logosPageCurrentGroup);
      const logo = logos[logoIndex];
      logo.url = action.payload;
      logos[logoIndex] = logo;
      return {
        ...state,
        logos
      };
    }
    default:
      return state;
  }

When the user opts to replace the existing file, the removeGroupIdFromRedux function is successfully called and the log shows that the action was successfully performed and there is now one less item in Redux (in the image below, there was only one file for testing, so the result from this call is an empty logos array).

The next thing that happens is that the SUCCESS code in continueToNextPage is executed and it sets the logos array to two items -- the original one and the newly-added one. The previous update to the Redux store is not recognized.
image

Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions