Mobs-state-tree action fails due to protection

461 Views Asked by At

In the following store I try to upload some photos and want to update the UI with the progress. This is my main store:

const Store = types
  .model({
    photos: types.array(Photo),
    isLoading: false,
  })
  .actions((self) => {
    return {
      addPhoto(url: string) {
        console.log("Add photo: ", url);
        const photo = Photo.create({ url });
        self.photos.push(photo);
      },
      send: flow(function* () {
        self.isLoading = true;
        console.log("Uploading photos...");
        for (let i = 0; i < self.photos.length; i += 1) {
          yield self.photos[i].upload();
        }
        console.log("Completed");
        self.isLoading = false;
      }),
    };
  });

But when I trigger the send action I get the error:

[mobx-state-tree] Cannot modify 'AnonymousModel@/photos/0', the object is protected and can only be modified by using an action.

This is the Photo model:

const Photo = types
  .model({
    url: types.string,
    progress: types.optional(types.number, 0),
    isLoading: false,
  })
  .actions((self) => {
    const dummyUpload = (duration: number, callback?: Callback) =>
      new Promise((resolve) => {
        let progress = 0;
        const interval = setInterval(() => {
          progress += 0.2;
          callback?.(progress);
          if (progress >= 1) {
            clearInterval(interval);
            resolve(undefined);
          }
        }, duration / 5);
      });

    return {
      upload: flow(function* () {
        self.isLoading = true;
        yield dummyUpload(3000, (progress) => {
           self.progress = progress;
         });
        self.isLoading = false;
      }),
    };
  });

Note: for now I have a dummy upload action with a progress callback.

I don't understand why the error states that the store is modified outside an action because I believe it is performed inside the Photo.upload action.

Can someone please tell me what's wrong with above code?

Here is a codesandbox to see it in action: https://codesandbox.io/s/mutable-pine-ukm5sv

1

There are 1 best solutions below

0
On

dummyUpload will be handled correctly by using yield, but the progress callback function will not.

You could e.g. create a separate actions block with the progress callback to make it an action and it will work as expected:

const Photo = types
  .model({
    url: types.string,
    progress: types.optional(types.number, 0),
    isLoading: false
  })
  .actions((self) => ({
    setProgress(progress: number) {
      self.progress = progress;
    }
  }))
  .actions((self) => {
    // ...
    
    return {
      upload: flow(function* () {
        self.isLoading = true;
        yield dummyUpload(3000, self.setProgress);
        self.isLoading = false;
      })
    };
  });