Freshdesk client: app.initialized() returns a promise that never gets resolved

Hello!

I am facing an issue when I run a React Freshdesk app locally (or at least locally) with the latest fdk 6.11.0.

For some reason the app.initialized() promise never gets resolved, there are no errors in .catch() , and even nothing in .finally() .
The global variable app is accessible and the initialized() method is available when I log it. setTimeout() didn’t help.

Here is the relevant piece of code based on the sample React Todo app.

const FreshdeskClientContextProvider = (props) => {
  const [loaded, setLoaded] = useState(false);
  const [fdClient, setFdClient] = useState(null);

  console.log('[PROVIDER] fdClient', fdClient);

  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://static.freshdev.io/fdk/2.0/assets/fresh_client.js';
    script.addEventListener('load', () => {
      console.log('[APP] Loaded!');
      setLoaded(true);
    });
    script.defer = true;
    document.head.appendChild(script);
  }, [setLoaded]);

  useEffect(() => {
    console.log('[APP] Running useEffect 2', loaded);
    if (!loaded) {
      return;
    }

    console.log('[APP] Initializing app');
    window.app.initialized()
      .then((client) => {
        const fd = new FresdeskClient(client);
        console.log('[INIT] FD', fd); // <-- Never gets logged
        setFdClient(fd);
      })
      .catch((error) => {
        console.error('[ERROR] Could not init app', error)); // <-- Never gets logged
      });
  }, [loaded, setFdClient]);

  return (
    <FreshdeskClientContext.Provider value={{ fdClient }}>
      {props.children}
    </FreshdeskClientContext.Provider>
  );
};

And here are the logs as the result:

What can be the reason of such app.initialized() behavior?

hey @ilya.belyavskiy

My guess would be that it should be app.initialized() instead of window.app.initialized()

could you please try that way and let us know?

Hope this helps!

Hi @velmurugan!

Thank you for the response!

What is the difference? :slight_smile:
The app variable is global (in window) anyway.

I’ve tried to use the app.initialized() without window previously while trying to resolve the issue. So changing it back won’t fix the issue, unfortunately.

Also, my current project is written in TypeScript, so it is important for me to keep this window for that purposes, but this is not the reason anyway :slight_smile:

I’ve tried refreshing the page differently, cleaning cache - nothing helps.
I’ve already faced this issue already some time ago (sample app, no TypeScript) and have even created an issue on GitHub.

The only solution for now is simply including the <script> into the HTML template. However, I would still prefer to include it the React.useEffect() way if possible.

Here is the proof that the app variable is accessible in window.

Code:

useEffect(() => {
    console.log('[APP] Running useEffect 2', loaded);
    if (!loaded) {
      return;
    }
    console.log('[APP] Initializing app', window.app);
    window.app.initialized()
      .then((client) => {
        const fd = new FresdeskApi(client);
        console.log('[INIT] FD', fd);
        setFdClient(fd);
      })
      .catch((error) => console.error('[ERROR] Could not init app', error));
  }, [loaded, setFdClient]);

Logs:

Hey @ilya.belyavskiy

Can you make the callback method inside useEffect async method and use awaits inside
This is just to verify if there are problems with chaining promises inside useEffect

1 Like

Also, usually, only state variables are given as an array in the second argument in useEffect. Why are you sending setFdClient which seems to be the event handler for the state variable?

I referred this :link: How to use the useEffect React hook.

1 Like

Hello @shravan.balasubraman,

The useEffect() should be fine for Promises and chaining. Just to be clear - the reason why I’ve created this thread is because I want to try setting up the external script the same way as provided in the sample React Todo App. I’ve explored different ways of including external scripts in React and this way seems to be quite a popular one, however it does not work in my case specified above (multiple different apps tested, including the mentioned sample demo app).
Everything works perfectly when the script is simply inserted as an external one in the template.html file.
Other Promise chains work fine in different parts of the app. The piece of code provided above is related to a React Context file (FreshdeskClientContext).
Here is a part of the <App/> component of the very same project for example. It uses Promise chaining in useEffect(), too:

const App: FC = () => {
  const { fdClient, setIparams } = useContext(FreshdeskClientContext) as ClientContext;
  const [areIparamsCollected, setAreIparamsCollected] = useState(false);

  useEffect(() => {
    if (!fdClient) {
      return;
    }
    console.log('[INFO] Collecting iparams...');
    fdClient
      .getParams()
      .then((config: IparamsConfig) => {
        console.log('[INFO] Iparams collected', config);
        setIparams(config);
        setAreIparamsCollected(true);
      })
      .catch((error) => {
        console.error('[ERROR] Could not initialize app', error);
      });
  }, [fdClient, setIparams]);

   ...
};

As for the second note regarding the setState() (or setFdClient() in my case) - you are right, that’s my bad. In the example above both variables fdClient and setIparams from destructuring the object from useContext() are required by the ESlint rule to be included into the useEffect() dependencies array.
I got confused by this and accidentally included the setState()-like functions as useEffect() dependencies in the previous code example (FreshdeskClientContextProvider).
I’ve removed them from these arrays. Thank you for noticing this! :slight_smile:

Hey @ilya.belyavskiy,

I am still not sure if I understand the purpose yet. are you trying to pass the client as a context instead of passing it as props? is that your requirement? or is there something else you are trying here?

and what is the instance FresdeskClient(client) could you log something before const fd = new FresdeskClient(client) inside the then() and check if there are any logs?

if using the context API is the requirement I am also not sure why is it?

  useEffect(() => {
    if (!loaded) return;
    window.app.initialized()
      .then((client) => {
        const fd = new FresdeskClient(client);
        setFdClient(fd);
      })
      .catch((error) => {
        console.error('[ERROR] Could not init app', error)); // <-- Never gets logged
      });
  }, [loaded, setFdClient]);

and not just set the client directly to the state?

  useEffect(() => {
    if (!loaded)  return;
    window.app.initialized()
      .then((client) => {
        setFdClient(client);
      })
      .catch((error) => {
        console.error('[ERROR] Could not init app', error)); // <-- Never gets logged
      });
  }, [loaded, setFdClient]);

Hi @velmurugan,

The purpose of my whole thread is to include the external script using the suggested React useEffect approach and to understand why the app.initialized() does not return anything.

Yes, I am storing the Freshdesk client in Context, because I don’t want to get the “props drilling” which is sort of anti-pattern - I want to be able to use the Freshdesks’s client-based class where necessary only and not to pass it with props (obviously).
This FreshdeskClient class contains all required wrappers for the client methods. For example, wrappers for making requests, so that they are sent in a more convenient comfortable way, e.g. fdClient.getTicketFields(). I can provide the code of the FreshdeskClient if required - however, there is nothing special there - just a constructor and a setter.

To be honest, I am a little confused how can this be relevant to the raised issue. All of this works when the Freshdesk client JS script is included into the HTML template and does not work with the provided sample approach due to the never-resolving app.initialized().

hey @ilya.belyavskiy,

Would you be able to send the whole app in the DM or a private thread for us to investigate?