Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] How to do async actions the Pullstate Way™ #26

Open
Antonio-Laguna opened this issue Feb 5, 2020 · 2 comments
Open

[Question] How to do async actions the Pullstate Way™ #26

Antonio-Laguna opened this issue Feb 5, 2020 · 2 comments
Labels
documentation Related to documenting the use of the library question Further information is requested

Comments

@Antonio-Laguna
Copy link

Hi there!

I've been looking for some time for a decent alternative and less convoluted than Redux and I was ecstatic when I saw your module. Looks really nice!

I went through the docs and tried implementing it but felt a tad odd. I think my case is really straightforward and realized some people could run into this same pitfall or wonder so it could do for good examples that could enhance the docs/code.

Here's an "entity" which is a "page":

import { createAsyncAction, errorResult, successResult } from 'pullstate';
import ItemsStore from '../stores/items-store';

export function getPages() {
  return fetch(`${process.env.SERVER}/api/pages/pages?token=${process.env.TOKEN}`)
    .then(response => response.json())
    .then(json => json.map((name, index) => ({ index, name })));
}

export default createAsyncAction(
  async () => {
    const result = await getPages();

    if (result.length > 0) {
      return successResult(result);
    }

    return errorResult([], `Couldn't get pages: ${result.errorMessage}`);
  },
  {
    postActionHook: ({ result, stores }) => {
      if (!result.error) {
        ItemsStore.update(s => {
          s.pages = result.payload;
        });
      }
    },
    cacheBreakHook: ({ result, timeCached }) => {
      const CACHE_TIME = 60 * 60 * 1000; // 1 hour in milliseconds
      return timeCached + CACHE_TIME < Date.now();
    }
  },
);

Then on the view, I want to render this list of pages:

export function HomePage() {
  const [finished, result] = usePages.useBeckon();

  if (!finished) {
    return (
      <div>
        Loading...
      </div>
    );
  }

  if (result.error) {
    return <div>{result.message}</div>;
  }

  return (
    <div>
      <ul>
        {result.payload.map(page => (<li key={page.index}>{page}</li>))}
      </ul>
    </div>
  );
}

However, it feels a bit convoluted and the result.payload feels kind of dirty to me which is what got me thinking if we were doing it right.

@lostpebble
Copy link
Owner

lostpebble commented Feb 5, 2020

Hi there! :)

You're right, there could definitely be more examples about use cases - especially with the Async Actions. I've been meaning to make a short and quick video series about it too.

So basically, what it seems you're doing is making use of the both postActionHook and making use of the result.payload - where usually that is the choice that you need to make, which ever one suites the situation that you are in.

If you make use of postActionHook you should just be getting your pages from your ItemsStore using useStoreState in your component.

You also still use useBeckon to trigger and watch the execution state of your action in your UI, as you've done.

So what you could do is:

export function HomePage() {
  const pages = ItemsStore.useStoreState(s => s.pages);
  const [finished, result] = usePages.useBeckon();

  if (!finished) {
    return (
      <div>
        Loading...
      </div>
    );
  }

  if (result.error) {
    return <div>{result.message}</div>;
  }

  return (
    <div>
      <ul>
        {pages.map(page => (<li key={page.index}>{page}</li>))}
      </ul>
    </div>
  );
}

The other option would be to not make use of postActionHook and just make use of the result directly, as you've done. I know it feels dirty, but its the only way to encapsulate the full state of a running async action. You could make it look slightly less messy like so:

export function HomePage() {
  const [finished, result] = usePages.useBeckon();

  if (!finished) {
    return (
      <div>
        Loading...
      </div>
    );
  }

  if (result.error) {
    return <div>{result.message}</div>;
  }

  // Typescript guarentees that this is now definitely the
  // non-undefined payload since "result.error" = false
  const pages = result.payload;

  return (
    <div>
      <ul>
        {pages.map(page => (<li key={page.index}>{page}</li>))}
      </ul>
    </div>
  );
}

Or, of course pull out the payload earlier (cleanest looking but you lose some Typescript goodness):

  const [finished, { error, message, payload: pages }] = usePages.useBeckon();

  if (!finished) {
    return (
      <div>
        Loading...
      </div>
    );
  }

  if (error) {
    return <div>{message}</div>;
  }

  // Typescript unfortunately isn't smart enough to infer that "pages" is
  // non-undefined here because we separated it out earlier

  return (
    <div>
      <ul>
        {pages.map(page => (<li key={page.index}>{page}</li>))}
      </ul>
    </div>
  );

So, you were actually already kinda doing things the Pullstate™ way :)

I know it feels a little verbose, but it definitely beats keeping track of all that state for each action. If you want you could also give the newer React Suspense stuff a go which should feel less verbose and more declarative - (https://lostpebble.github.io/pullstate/docs/async-action-use#react-suspense-read-an-async-action)

@Antonio-Laguna
Copy link
Author

Wow, thanks for the insights! I think that's useful!

@lostpebble lostpebble added question Further information is requested documentation Related to documenting the use of the library labels Feb 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Related to documenting the use of the library question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants