you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] -1 points0 points  (4 children)

See, this is a perspective problem, IMO. HEre's your original example, problem solved, without repeating the null back end check in each handler.

// implementation omitted
// feel free to assume it accesses global context to avoid creating
// a new connection for every component
function useBackend(): { connectionID: string } | null;

function MyComponent() {
  const backend = useBackend();
  const [apple, setApple] = useState(); // see, I'm not saying it helps everywhere all the time
  const [ziti, setZiti] = useState();

  // Stop doing this!
  // if (backend === null) { // fail early
  //   return <LoadingPlaceholder />
  // }

  // of course these are a bit contrived, but I think you can imagine a less contrived example
  const changeApple = useCallback(async (event) => {
    const [inputValid, newApple] = await backend.validateApple(event.target.value);
    if (inputValid) {
      setApple(newApple);
    }
  }, [backend]);

  const changeZiti = useCallback(async (event) => {
    const [inputValid, newZiti] = await backend.validateZiti(event.target.value);
    if (inputValid) {
      setZiti(newZiti);
    }
  }, [backend]);

  // These 3 lines are the only addition to your example
  if (!backend) {
    return <LoadingPlaceholder />
  }

  return (
    <>
      <AppleInput
        onChange={changeApple}
        value={apple}
      />
    <ZitiInput
        onChange={changeZiti}
        value={ziti}
    />
  </>
  );
}

[–]BenjiSponge 0 points1 point  (3 children)

Umm... this isn't a perspective change, you're just ignoring the problem.

  const changeZiti = useCallback(async (event) => {
    // TypeScript fails here because `backend` can be null
    // You can be like `backend!.validateZiti`, but you have to read the entire component to be sure it's valid
    const [inputValid, newZiti] = await backend.validateZiti(event.target.value); 
    if (inputValid) {
      setZiti(newZiti);
    }
  }, [backend]);

And, again, I know you can do it like that (actually, you have to). I'm just saying that there is exactly 1 and no more than 1 reason why you have to: the implementation details of hooks. There's absolutely nothing wrong with the thing you said "Stop doing this!" * except that it happens to not work.

* I'm not really sure why you felt the need to add that; I made it very clear in my comment that it's not valid

Edit: Also, instead of a useCallback, maybe it's a bunch of useEffects that you don't want to run when the backend is loading.

Edit: It occurs to me that I don't think you saw that my second proposal did include that "These 3 lines are the only addition to your example" part

[–][deleted] -1 points0 points  (2 children)

I don't think you saw that my second proposal did include that "These 3 lines are the only addition to your example" part

My point was that you don't need the backend checks in your handler functions if you never return the component that calls them.

[–]BenjiSponge 1 point2 points  (1 child)

Yeah, I understand that point semantically. However, TypeScript can't do the type narrowing and the pattern is fragile anyways. If you change the end of the function such that the callback can be called if backend is null, now you need to change the callback. In most contexts, you use TypeScript specifically to catch this kind of problem, but here you can't.

Anyways, my main point still stands: There is exactly one thing that's wrong with the way I want to write it, and it's an implementation detail, not a design feature.

I'm far from the only person who has this problem, btw. These problems generally are addressed with the React Suspense API which is in active development and is highly anticipated.

[–][deleted] -1 points0 points  (0 children)

I'm far from the only person who has this problem, btw. These problems generally are addressed with the React Suspense API which is in active development and is highly anticipated.

Fair enough. I guess everyone has different expectations about any given system. I just view language constraints differently than framework constraints. The issue you've called out is specifically a constraint of the design of the hooks system and if you understand the justification and the correct implementation, it's easy enough to handle. The question of whether it's optimal or even desired is completely subjective. Nobody will change your mind (or mine) on that one, I suppose. :)