0

I am new to react and I'm working on react fetch using useEffect. Im using miragejs to mimic an api server. I am testing the sad path and have made the miragejs API to return a 400 status code.

im using miragejs to mimic an api server.

here is the server.js

import { createServer, Model } from "miragejs"


createServer({
models: {
    vans: Model,
},

seeds(server) {
    server.create("van", { id: "1", name: "Modest Explorer", price: 60, description: "The Modest Explorer is a van designed to get you out of the house and into nature. This beauty is equipped with solar panels, a composting toilet, a water tank and kitchenette. The idea is that you can pack up your home and escape for a weekend or even longer!", imageUrl: "https://assets.scrimba.com/advanced-react/react-router/modest-explorer.png", type: "simple", hostId: "123" })
    server.create("van", { id: "2", name: "Beach Bum", price: 80, description: "Beach Bum is a van inspired by surfers and travelers. It was created to be a portable home away from home, but with some cool features in it you won't find in an ordinary camper.", imageUrl: "https://assets.scrimba.com/advanced-react/react-router/beach-bum.png", type: "rugged", hostId: "123" })
    server.create("van", { id: "3", name: "Reliable Red", price: 100, description: "Reliable Red is a van that was made for travelling. The inside is comfortable and cozy, with plenty of space to stretch out in. There's a small kitchen, so you can cook if you need to. You'll feel like home as soon as you step out of it.", imageUrl: "https://assets.scrimba.com/advanced-react/react-router/reliable-red.png", type: "luxury", hostId: "456" })
    server.create("van", { id: "4", name: "Dreamfinder", price: 65, description: "Dreamfinder is the perfect van to travel in and experience. With a ceiling height of 2.1m, you can stand up in this van and there is great head room. The floor is a beautiful glass-reinforced plastic (GRP) which is easy to clean and very hard wearing. A large rear window and large side windows make it really light inside and keep it well ventilated.", imageUrl: "https://assets.scrimba.com/advanced-react/react-router/dreamfinder.png", type: "simple", hostId: "789" })
    server.create("van", { id: "5", name: "The Cruiser", price: 120, description: "The Cruiser is a van for those who love to travel in comfort and luxury. With its many windows, spacious interior and ample storage space, the Cruiser offers a beautiful view wherever you go.", imageUrl: "https://assets.scrimba.com/advanced-react/react-router/the-cruiser.png", type: "luxury", hostId: "789" })
    server.create("van", { id: "6", name: "Green Wonder", price: 70, description: "With this van, you can take your travel life to the next level. The Green Wonder is a sustainable vehicle that's perfect for people who are looking for a stylish, eco-friendly mode of transport that can go anywhere.", imageUrl: "https://assets.scrimba.com/advanced-react/react-router/green-wonder.png", type: "rugged", hostId: "123" })
},

routes() {
    this.namespace = "api"
    this.logging = false
    this.get("/vans", (schema, request) => {
        return new Response(400, {}, {error: "Error fetching data"})
        //return schema.vans.all()
    })
    
    this.get("/vans/:id", (schema, request) => {
        const id = request.params.id
        return schema.vans.find(id)
    })

    this.get("/host/vans", (schema, request) => {
        // Hard-code the hostId for now
        return schema.vans.where({ hostId: "123" })
    })

    this.get("/host/vans/:id", (schema, request) => {
        // Hard-code the hostId for now
        const id = request.params.id
        return schema.vans.findBy({ id, hostId: "123" })
    })
}
})

The api.js is :

export async function getVans() {
  const res = await fetch("/api/vans")
  if (!res.ok) {
  throw Object.assign(new Error("Failed to fetch vans"),{
      message: "Failed to fetch vans", 
      statusText: res.statusText,
      status: res.status
  });
  }
  const data = await res.json()
  return data.vans
}

below is my fetch code:

export async function getVans() {
  const res = await fetch("/api/vans")
  if (!res.ok) {
  throw Object.assign(new Error("Failed to fetch vans"),{
      message: "Failed to fetch vans", 
      statusText: res.statusText,
      status: res.status
  });
 }
  const data = await res.json()
  return data.vans
}

I'm using this in my component to set some states.

I'm using a try-catch block while calling this API inside the use effect. But I see that the error is not caught.

useEffect(() => {
async function loadVans() {
  setLoading(true)
  try {
      const data = await getVans()
      console.log("fetched data: "+data);
      setVans(data)
  } catch (err) {
    console.log("error :"+err)
      setError(err)
  } finally {
      setLoading(false)
  }
}
loadVans()
}, [])

I see it proceeds and breaks with runtime errors.

Somehow the response is 200 even I'm returning 400 in server.js. enter image description here

I get the error on /vans path. the file is Vans.jsx

enter image description here

Vans.jsx is :

import React from "react";
import { useState, useEffect } from "react";
import { Link, useSearchParams } from "react-router-dom";
import { getVans } from "../../api";

export default function Vans() {
  const [vans, setVans] = useState([])
  const [searchParams, setSearchParams] = useSearchParams();
  const [loading,setLoading] = useState(false);
  const [error, setError] = useState(null);


  const typeFilter = searchParams.get("type");
  useEffect(() => {
    async function loadVans() {
      setLoading(true)
      try {
          const data = await getVans()
          console.log("fetched data: "+data);
          setVans(data)
      } catch (err) {
        console.log("error :"+err)
          setError(err)
      } finally {
          setLoading(false)
      }
    }
    loadVans()
}, [])

  const displayedVans = typeFilter
        ? vans.filter(van => van.type === typeFilter)
        : vans
  console.log("typeFilter :" + typeFilter);
  const vanElements = displayedVans.map(van => {
    return (
      <div key={van.id} className="van-tile">
        <Link to={van.id} state={{ search: `? 
   ${searchParams.toString()}`, type: typeFilter }}>
          <img src={van.imageUrl} alt={van.name} />
          <div className="van-info">
            <h3>{van.name}</h3>
            <p>${van.price}<span>/day</span></p>
          </div>
          <i className={`van-type ${van.type} selected`}>{van.type}</i>
        </Link>
      </div>
    )
  })
  function handleFilterChange(key, value) {

    setSearchParams(prevParams => {
      if (value === null) {
        prevParams.delete(key)
      } else {
        prevParams.set(key, value)
      }
  return prevParams
    })
  }
  if(loading){
    return(<h1>Loading</h1>)
  }
  if (error) {
    return <h1>There was an error: {error.message}</h1>
}
  return (
    <div className="van-list-container">
      <h1>Explore our van options</h1>
      <div className="van-list-filter-buttons">
        <button
          onClick={() => handleFilterChange("type", "simple")}
          className={
            `van-type simple ${typeFilter === "simple" ? "selected" : ""}`
          }
        >Simple</button>
        <button
          onClick={() => handleFilterChange("type", "luxury")}
          className={
            `van-type luxury ${typeFilter === "luxury" ? "selected" : ""}`
      }
    >Luxury</button>
    <button
      onClick={() => handleFilterChange("type", "rugged")}
      className={
        `van-type rugged ${typeFilter === "rugged" ? "selected" : ""}`
      }
    >Rugged</button>
    {typeFilter ? (<button onClick={() => setSearchParams({})} className="van-type clear-filters">Clear Filters</button>) : null}
  </div>
  <div className="van-list">
    {vanElements}
  </div>
</div>
  )
}

Please advise.

manjosh
  • 438
  • 6
  • 28
  • This should work fine. What is the runtime error? Where are you seeing it? – Bergi Jul 30 '23 at 17:17
  • setVans(data) will be set null to the state. Further in the code this data is iterated using map function. since the state object vans is null the map function breaks. Im also check setError(err) state. if its set, it should render an error page. – manjosh Jul 31 '23 at 09:19
  • So this is happening not when you get a 400 response, but rather when you get a 200 response with `data.vans` being `null`! There is no exception thrown by `await getVans()` then, and subsequently it's not caught by the `try`/`catch`. The error actually happens when your component is re-rendered in the invalid state - `setVans(null)` is setting that invalid state without throwing an exception. – Bergi Jul 31 '23 at 17:02
  • Either check `vans != null` in `loadVans`, or check whether `vans` is `null` in the component itself before `.map()`ping over it and render the error page instead. Or maybe this actually is happening while you are loading the data? – Bergi Jul 31 '23 at 17:03
  • Ok, so this is happening during re-rendering? Not sure why it should re-render. If you see my project, I'm deliberately returning 400 for getAllVans call in the server.js file. Ideally, the above code snipped should work. Not able to pinpoint the actual issue. – manjosh Jul 31 '23 at 17:07
  • You say that `setVans(data)` is called with `null`, that means a) you're changing the state so it re-renders b) your 400 response did not work. Please [edit] your question to include more code, as well as the request and response as seen in the devtools network panel. – Bergi Jul 31 '23 at 17:10
  • Where is `setVans` declared and where are you doing the `.map()` that causes the error? – Bergi Jul 31 '23 at 17:17
  • updated the question with Vans.jsx – manjosh Jul 31 '23 at 17:23
  • That seems fine. Did you make sure that `res.ok` is actually false by putting a debugger breakpoint in there? – Bergi Jul 31 '23 at 17:26
  • 1
    oh, the response is actually 200. Not sure why. Is there anything wrong in server.js? – manjosh Jul 31 '23 at 17:33
  • Apparently so, but I don't know miragejs – Bergi Jul 31 '23 at 17:36
  • 1
    Thanks @Bergi for your help. After debugging I found that the Response was not imported in the server.js – manjosh Aug 01 '23 at 14:30

4 Answers4

0

Check out this CodeSandbox that catches the error for your getVans function.

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

async function getVans() {
  throw Object.assign(new Error("Failed to fetch vans"), {
    message: "Failed to fetch vans"
  });
}

export default function App() {
  const [counter, setCounter] = useState(1);

  useEffect(() => {
    async function loadVans() {
      try {
        const data = await getVans();
        console.log("fetched data: " + data);
      } catch (err) {
        console.log("error :" + err);
      } finally {
      }
    }
    loadVans();
  }, [counter]);

  const onClick = () => {
    setCounter(counter + 1);
  };
  return (
    <div className="App">
      <button onClick={onClick}>click me trigger caught error</button>
    </div>
  );
}

I wish I could pinpoint what the error is, but it's hard without a "working" example from your end.

Audun Hilden
  • 330
  • 3
  • 13
  • So you didn't really change anything, you're claiming the OPs code should work? If yes, you might want to make that more clear in your answer – Bergi Jul 30 '23 at 17:18
  • My code is pretty much the same. Wonder why it doesn’t work or am I missing something?I have shared the project link. – manjosh Jul 30 '23 at 17:29
0

The issue was with the import in server.js. Response was not imported

with import { createServer, Model, Response } from "miragejs" its working fine.

manjosh
  • 438
  • 6
  • 28
-1

AJAX and APIs - React:

Note: it's important to handle errors here

instead of a catch() block so that we don't swallow

exceptions from actual bugs in components.

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 03 '23 at 14:03
-2

import { useEffect } from "react";

// simulate api call
export async function fetchRandomUser() {
  const response = await fetch("https://randomuser.me/api/");
  // If response is ok
  if (response.ok) {
    return response.json();
  } else {
    // else throw your custom error message
    throw new Error("error");
  }
}


export default function App() {
  useEffect(() => {
    async function fetchUsers() {
      try {
       // call user's api
        const users = await fetchRandomUser();
        console.log(users.results);
      } catch (error) {
        // handle error
        console.log("Error", error.message);
      }
    }
    fetchUsers();
  }, []);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
    </div>
  );
}