0

I am learning React, and trying to use Suspense.

On the First, I try to use wrapped promise object for props.
Wrapper throw promise when it not solved. It loop forever.
Then I try it with useEffect, but it has same problem.

Here is my code and sandbox. Please lecture me how to solve this problem.

textsandbox EternalLoop
* useEffect block comment outed in sandbox because it loops forever.

import React, { Suspense, useEffect, useState } from "react";

const lazyTimer = () => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(" from 10sec past");
    }, 1000);
  });
  return promise;
};

const wrapPromise = promise => {
  let status = "pending";
  let result;
  console.log("looping....");

  const suspender = promise.then(
    r => {
      status = "fulfilled";
      result = r;
    },
    e => {
      status = "rejected";
      result = e;
    }
  );
  const read = () => {
    if (status === "pending") {
      throw suspender;
    } else if (status === "rejected") {
      throw result;
    } else {
      return result;
    }
  };
  return { read };
};

const Hallo = () => {
  const [text, setText] = useState("your on time");
  // useEffect(
  //   setText(
  //     wrapPromise(lazyTimer()).read()
  //   ), [text],
  // );
  return <h2>hello world, {text}</h2>;
};

export default function App() {
  return (
    <div className="App">
      <Suspense fallback={<p>Loading...</p>}>
        <Hallo />
      </Suspense>
    </div>
  );
}
MIsoku
  • 25
  • 5

1 Answers1

1

Edit 3: What has been confusing for me with suspense that I don't see documented is how it works. IE, how the children work. It appears to me that you throw a promise from looking at the demo's code in the documentation. But I don't see this documented anywhere.

So you throw a promise that when resolved your component is now ready (ie. the timer promise - or an Apollo Client promise - fetch promise, etc.). When that promise resolves, your component can now mount. I love that, I wish it was documented clearly if I'm right on how it works.


This problem has nothing to do with suspense. Your code:

const Hallo = () => {
  const [text, setText] = useState("your on time");
  useEffect(
  //   setText(
  //     wrapPromise(lazyTimer()).read()
  //   ), [text],
  // );
  return <h2>hello world, {text}</h2>;
};

Has this problem. It runs setText, then it has text as a dependency. So you change text, then it runs again because text changes. Hence the infinite loop.

You have 3 ways to fix this

1) Do some if statement to make it not an infinite loop (ie. you know what text shouldn't be or check if it is the same).

2) Remove text from the dependency list (bad!)

3) remove it by using the alternative setText method, calling it as a function instead of providing a value. See here for documentation.

   useEffect(
       setText(text => wrapPromise(lazyTimer()).read())
     ), [],
   );

then text will not be a dependency.

I recommend 3.


Edit:

On top of that, you were using the wrapper incorrectly. I looked at the tutorial you probably used and its Github. They create the wrapper. Then in the data fetching portion (your timer) wrap their promise in the promise wrapper.

I tried to keep your code as similar as possible using useEffect:

import React, { Suspense, useEffect, useState } from "react";
import "./styles.css";

const wrapPromise = promise => {
  let status = "pending";
  let result;
  console.log(`looping.... + ${new Date().toString()}`);

  const suspender = promise.then(
    r => {
      status = "fulfilled";
      result = r;
    },
    e => {
      status = "rejected";
      result = e;
    }
  );
  const read = () => {
    if (status === "pending") {
      throw suspender;
    } else if (status === "rejected") {
      throw result;
    } else {
      return result;
    }
  };
  return { read };
};

const lazyTimer = () => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(" from 10sec past");
    }, 10000);
  });
  return wrapPromise(promise);
};

const data = lazyTimer();

const Hallo = () => {
  const [text, setText] = useState(data.read());
  useEffect(() => {
    console.log("Im running");
    setText(data.read());
  }, []);

  return <h2>hello world, {text}</h2>;
};

export default function App() {
  return (
    <div className="App">
      <Suspense fallback={<p>Loading...</p>}>
        <Hallo />
      </Suspense>
    </div>
  );
}

This works. Sandbox here.

Of note, halo could just be:

const Hallo = () => {
  const text = data.read()
  return <h2>hello world, {text}</h2>;
};
Diesel
  • 5,099
  • 7
  • 43
  • 81
  • Hi, Diesel. Thanks for anser. also I love Diesel brand. I tried your suggestion on sandbox. But still it loops. Do I have other mistakes? I fix sandbox with your suggestion. – MIsoku Apr 16 '20 at 05:27
  • Great Answer Diesel ! I really appreciate your help. I try to study again how wrapper works in tutorial. My problem was perfectly solved. This is best answer. – MIsoku Apr 17 '20 at 00:08