1

Recently updated SWR - now for some reason my data is not fetching properly.

const { data: expressionsData, error: expressionsError } = useSWRImmutable(
       [`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
       apiRequest
  );

Using this fetching,

import firebase from "./firebase";

export async function apiRequest(path, method = "GET", data) {
  const accessToken = firebase.auth().currentUser
    ? await firebase.auth().currentUser.getIdToken()
    : undefined;
    //this is a workaround due to the backend responses not being built for this util.
  if (path == "dashboard/get-settings") {
    return fetch(`/api/${path}`, {
      method,
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
      body: data ? JSON.stringify(data) : undefined,
    })
      .then((response) => response.json())
      .then((response) => {
        if (response.error === "error") {
          throw new CustomError(response.code, response.messages);
        } else {
          return response;
        }
      });
  }
  return fetch(`/api/${path}`, {
    method,
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: data ? JSON.stringify(data) : undefined,
  })
    .then((response) => response.json())
    .then((response) => {
      console.log("error", response);

      if (response.status === "error") {
        // Automatically signout user if accessToken is no longer valid
        if (response.code === "auth/invalid-user-token") {
          firebase.auth().signOut();
        }

        throw new CustomError(response.code, response.message);
      } else {
        return response.data;
      }
    });
}

// Create an Error with custom message and code
export function CustomError(code, message) {
  const error = new Error(message);
  error.code = code;
  return error;
}

// Check if a indexDb database exists
export function indexedDbdatabaseExists(dbname, callback) {
  const req = window.indexedDB.open(dbname);
  let existed = true;
  req.onsuccess = function () {
    req.result.close();
    if (!existed) window.indexedDB.deleteDatabase(dbname);
    callback(existed);
  };
  req.onupgradeneeded = function () {
    existed = false;
    callback(existed);
  };
}

Now I'm looking at this StackOverflow thread,

useSWR doesn't work with async fetcher function

And thinking I'll just remake the fetcher to be without Async. I'm just wondering why this has stopped working though in general, and if I can just keep my existing codebase.

The error is a 400 message, it only happens with this expressions API call which takes longer to load due to the amount of data I think,

 xxxx/dashboard/expression/get-expression-analytics?startTime=1648183720488&endTime=1650865720488 400 (Bad Request)

with error log

enter image description here

These calls are working fine, they have substantly less data though.

const { data: overall, error: psychometricError } = useSWRImmutable(
    `dashboard/psychometric/get-psychometric-home?starttime=infinite`,
    apiRequest
  );

  const { data: sentimentData, error: sentimentError } = useSWRImmutable(
     [`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
    fetchSentiment
  );

Made an update to the fetch call to be more readable and specifically about the URL pathway.

import firebase from './firebase';

// Create an Error with custom message and code
export function CustomError(code, message) {
  const error = new Error(message);
  error.code = code;
  return error;
}

export async function expressionsRequest(path, method = 'GET') {
  const accessToken = firebase.auth().currentUser
    ? await firebase.auth().currentUser.getIdToken()
    : undefined;
  return fetch(`/api/${path}`, {
    method,
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${accessToken}`,
    },
  })
    .then((response) => {
      if (!response.ok) {
        throw `Server error: [${response.status}] [${response.statusText}] [${response.url}]`;
      }
      return response.json();
    })
    .then((receivedJson) => {
      if (receivedJson.status === 'error') {
        // Automatically signout user if accessToken is no longer valid
        if (receivedJson.code === 'auth/invalid-user-token') {
          firebase.auth().signOut();
        }

        throw new CustomError(receivedJson.code, receivedJson.message);
      } else {
        return receivedJson.data;
      }
    })
    .catch((err) => {
      console.debug('Error in fetch', err);
      throw err;
    });
}

Additionally, this is what the lambda function (using next API folder) looks like,

const requireAuth = require('../../_require-auth');
const { db } = require('../../_sql');

export default requireAuth(async (req, res) => {
  const { uid: id } = req.user;

  const startTime = Math.round(req.query.startTime * 0.001);
  const endTime = Math.round(req.query.endTime * 0.001);

  const parameters = [id, startTime, endTime];

  //sql injection definitely possible here, need to work out better method of dealing with this.
  const sqlText = `SELECT a,b,c,d,e,f,g,h,i FROM tablename WHERE a=$1 AND i BETWEEN $2 AND $3;`;

  try {
    const { rows } = await db.query(sqlText, parameters);
    return res.status(200).json({
      code: 0,
      data: rows,
    });
  } catch (error) {
    return res.status(200).json({
      code: 0,
      message: 'Error occurred in getting tablename',
      error,
    });
  }
});

using postman with the same query, i.e.,

curl --location --request GET 'http://localhost:3000/api/dashboard/expression/get-expression-analytics?startTime=1648387240382&endTime=1651069240382' \
--header 'Authorization: Bearer xxxx' \
--data-raw ''

Successfully returns a response with data attached.

LeCoda
  • 538
  • 7
  • 36
  • 79
  • What was your previous version of SWR? have you read the release notes or revisited the documentation for the features you're using? – diedu Apr 30 '22 at 23:48
  • There's no problem with async functions and SWR, 400 is a bad request, not a timeout, so I think you are just doing something wrong. You did not definitely made it more readable by mixing await and .then(), you should never do that, pick one out of the two approaches. You are also not catching the firebase auth call. Inside the lambda function you can log correctly the req.query and req.user values ? – Cesare Polonara May 01 '22 at 05:37

1 Answers1

3

Based on your first code blocks, the startDate value is getting passed into the fetcher as method, and the endDate value is getting passed into the fetcher as data. This is based on the useSWR docs about passing in an array for the key argument: https://swr.vercel.app/docs/arguments#multiple-arguments

If the code you provided is correct, I'd assume the 400 is coming from trying to pass in a random value for the method option for fetch.

This should be fixed by only passing the API endpoint path into useSWR instead of an array:

const { data: expressionsData, error: expressionsError } = useSWRImmutable(
  `dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
  apiRequest
);
Chris Sandvik
  • 1,787
  • 9
  • 19
  • Thanks - If the start date and end date changes though, will useSWR immutable be called again? (that's what I would be looking for) – LeCoda May 05 '22 at 05:40
  • 1
    Yes it will! useSWRImmutable only prevents automatic revalidation, and any changes to the conditions, including the URL, will cause it to be run again. Here's an example, where the URL changes every 5 seconds and so does the returned data: https://codesandbox.io/s/useswrimmutable-example-1x33kb?file=/src/App.js – Chris Sandvik May 05 '22 at 18:18