Search code examples
reactjssetstatereact-state-management

Using spread operator to avoid mutating state in React function


In my React project I have a function within a class-based component that handles video uploads. It is working as expected and desired. However, I realized upon inspection that it violates React's don't mutate state mandate. I think that's the case, though I want to ensure that's true, and that the solution I've come up with deals with this.

Here is my component state:

  state = {
    streamingRes: {},
    uploadFailed: false
  }

My initial function looked like this (notice there are 3 places where I am setting the state):

  fileUploadHandler = (id, file, fileId) => {
    const isValid = this.validateVideoFileType(file);
    if(!isValid) this.props.showError(`${file.name} is of the wrong file type (${file.type}).  File must be an acceptable video format.`);

    let dataStream = io.Stream.createStream();

    io.Socket.on('userVideos.uploadProgress', (data) => {
      this.setState( { streamingRes: data });
      if(fileId === data.data.guid) {
        this.uploadCompletionPercentage = data.data.progress;
      } 
    });

    io.Stream(io.Socket).emit('userVideos.upload', dataStream, {
      guid: fileId,
      size: file.size
    }, (data) => {
      if(data.status === "failure") {
        this.props.onUploadFailed();
        this.setState( { uploadFailed: true })
      }
      else if(data.status === "success") {
        this.props.upload(id)
      }
    });

    this.setState( { uploadFailed: false });

    io.Stream.createBlobReadStream(file).pipe(dataStream);
    return;
  }

To avoid mutating state I updated this function to look like this:

  handleFileUpload = (id, file, fileId) => {
    let newState = {...this.state};
    const isValid = this.validateVideoFileType(file);
    if(!isValid) this.props.showError(`${file.name} is of the wrong file type (${file.type}).  File must be an acceptable video format.`);

    let dataStream = io.Stream.createStream();

    io.Socket.on('userVideos.uploadProgress', (data) => {
      this.setState( { streamingRes: data });
      if(fileId === data.data.guid) {
        this.uploadCompletionPercentage = data.data.progress;
      } 
    });

    io.Stream(io.Socket).emit('userVideos.upload', dataStream, {
      guid: fileId,
      size: file.size
    }, (data) => {
      if(data.status === "failure") {
        this.props.onUploadFailed();
        newState.uploadFailed = true;
        this.setState( { uploadFailed: newState.uploadFailed });
      }
      else if(data.status === "success") {
        this.props.upload(id)
      }
    });

    newState.uploadFailed = false;
    this.setState( { uploadFailed: newState.uploadFailed });

    io.Stream.createBlobReadStream(file).pipe(dataStream);
    return;
  }

Notice I am using the spread operator right at the top of the function now. My question is: does this effectively deal with the issue of avoiding state mutation?


Solution

  • Yes, you have avoided mutating state. However, your way of doing it is completely unnecessary because there is no need to copy the state into a new object if you don't use that object.

    Instead of:

    newState.uploadFailed = true;
    this.setState( { uploadFailed: newState.uploadFailed });
    

    You can simply do:

    this.setState({ uploadFailed: false });
    

    There was no problem in your code in the first place.