Next.js Discord

Discord Forum

Uncaught Error: There was an error while hydrating this Suspense boundary. Switched to client render

Answered
dank() posted this in #help-forum
Open in Discord
I'm trying to generate cards by mapping a card component with an array of data but it's causing a hydration failure. However rendering the card component on it's own doesn't seem to cause any problems.
const content = useMemo(() => {
    return vehicleList.map((vehicle) => (
      <VehicleCard
        key={vehicle.id}
        url={`/content/vehicles/${vehicle.id}`}
        vehicleImage={`/vehicleImages/${vehicle.file}`}
        loading={"lazy"}
        vehicleData={{
          description: vehicle.description,
          make: vehicle.make,
          model: vehicle.model,
          year: vehicle.year,
          price: vehicle.price,
          status: vehicle.status,
        }}
        dictionary={props.dictionary}
      />
    ));
  }, [vehicleList, props.dictionary]);

return <>{content}</>;
}


Unhandled Runtime Error

Error: Hydration failed because the initial UI does not match what was rendered on the server.

Warning: Expected server HTML to contain a matching <a> in <div>.
Answered by DirtyCajunRice | AppDir
export const useMounted = () => {
  const [mounted, setMounted] = useState<boolean>(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  return mounted;
}
View full answer

200 Replies

can you post the updated code and the contents of VehicleCard
works
return   
 <>
  <VehicleCard ...props/>
 </>;

doesn't work
const content = vehicleList.map((vehicle) => (
      <VehicleCard
      key={vehicle.id}
        url={`/content/vehicles/${vehicle.id}`}
        vehicleImage={`/vehicleImages/${vehicle.file}`}
        loading={"lazy"}
        vehicleData={{
          description: vehicle.description,
          make: vehicle.make,
          model: vehicle.model,
          year: vehicle.year,
          price: vehicle.price,
          status: vehicle.status,
        }}
        dictionary={props.dictionary}
      />
  ));

return <>{content}</>;
}
place the mapping logic directly inside the fragment, you don't need the extra variable
okay i'll try that first
the vehicle card content is too large I'll show the relevant parts
@not-milo.tsx place the mapping logic directly inside the fragment, you don't need the extra variable
that doesn't solve it, I'll send the vehicleCard contents
return (
    <React.Fragment>
      <Link href={props.url} style={{ textDecoration: "none" }}>
        <Card
          sx={{
            maxWidth: { xs: "70%", md: "90%" },
            maxHeight: { md: 200 },
            margin: 2,
            display: { md: "flex", xs: "block" },
          }}
          variant="outlined"
        >
          <CardMedia
            sx={{
              width: { xs: "100%", sm: "100%", md: "20rem" },
              height: { xs: "100%", sm: "100%", md: "10rem" },
            }}
          >
            <Suspense fallback={<Skeleton variant="rectangular" />}>
              <StyledImage
                src={props.vehicleImage}
                width={200}
                height={200}
                alt="Vehicle picture"
                loading={props.loading}
              />
            </Suspense>
          </CardMedia>
          {/* <Box sx={{ display: "flex", flexDirection: "column" }}> */}
          <CardActionArea
            sx={{
              display: "flex",
              flexDirection: "column",
              justifyContent: "flex-start",
              alignItems: "flex-start",
            }}
          >
            <CardContent>
              <Typography gutterBottom variant="h5" component="div">
                {`${props.vehicleData.make} ${props.vehicleData.model} ${props.vehicleData.year}`}
              </Typography>

              <Typography variant="body2" color="text.secondary">
                {props.vehicleData.description}
              </Typography>

              <Typography variant="h6">${props.vehicleData.price}</Typography>
            </CardContent>
          </CardActionArea>

          <div
            style={{
              display: "grid",
              // alignItems: "center",
            }}
          >
            <VehicleStatusLabel status={props.vehicleData.status} />
          </div>
          {/* </Box> */}
        </Card>
      </Link>
    </React.Fragment>
  );
Ik what you're thinking but hear me out
you keep using fragments everywhere even though you don't need them
Ik I'm wrapping the whole thing inside a link
oh that too
fragments should be used only when you need to return multiple elements at the same level and you can't wrap them in a parent element
I tried only passing <div><div> as the return of the vehicleCard, the error persists on anything I pass except React.Fragment
does the error show up even in an incognito browser window?
Dunker
<Suspense fallback={<Skeleton variant="rectangular" />}>
              <StyledImage
                src={props.vehicleImage}
                width={200}
                height={200}
                alt="Vehicle picture"
                loading={props.loading}
              />
            </Suspense>

error says problem is here, unless you have another suspense s
lemme check
I started seeing the error when I added the loading.tsx, it seems like that file picked up on something
when I remove the file the error disappears
Dunker
next has started to have weird behaviours and bugs, and i am not sure if this one of those or not
without seeing the whole picture it's hard to tell what could be wrong with your code
do you have a minimum reproduction repo that you can share? or even the repository for your actual code
Dunker
also try without that suspense wrapper
@Dunker also try without that suspense wrapper
it really doesn't matter what the vehicle card component is
@dank() it really doesn't matter what the vehicle card component is
Dunker
so is it not working {array.map(i => <VehicleCard/>)}
@Dunker so is it not working {array.map(i => <VehicleCard/>)}
yeah it's just
return (
    <>
      {vehicleList.map((vehicle) => (
        <VehicleCard key={vehicle.id} />
      ))}
    </>
  );
}
when you make it like
#Unknown Channel
// {vehicleList.map((vehicle) => (
// <VehicleCard key={vehicle.id} />
// ))}
</>
if it still persists
@not-milo.tsx does the error show up even in an incognito browser window?
have you checked this? there might be an extension messing with the contents of the page
@Dunker if it still persists
bet
Dunker
i know, just for demo
oh you mean commented out
everything
the issue persists
the problem doesn't happen when I try to render the vehicleCard component
it happens when I try to map it
Dunker
can you map something different like <div>test<div>
good idea lemme check
prob no tho
nope
Dunker
means
ayo wait a min
it's the array that's causing the issue
Dunker
💥
the array is selected from the redux store
Dunker
that complex, and
idk why that's raising a prob tho, not sure what are the restraints of using RTK in a Next.js app
Dunker
probably you are trying to use redux at server component
nope that would be a straight no no from Next.js
Dunker
also variable.map .. is not a proper way to render items
what is the proper way?
Dunker
const.map
it is a const actually
  const vehicleList = useSelector(selectSearchedVehicles);
Dunker
not const
const array = ..variable
export const selectSearchedVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool;
};
Dunker
yep it returns something like
const array = ["car1"]
const array = ["car2"]
...
so its variable
not quite
Dunker
your vehicleList shouldnt change by selections
it should be always same
like ["a", "b"]
it is
one big array
Dunker
changing when you select something
how do you suggest I get the data from the store?
Dunker
need to see full component logic
at least selectors and rendering parts
@Dunker not const
:mild_panic:
Dunker
also redux slice
I mean you seen it all except for the slice
Dunker
let me bring them together to see
that's the slice
type stateProps = {
  searchedVehicles: { vehiclesPool: IVehicle[] };
};
const initialState = {
  vehiclesPool: [],
};
const SearchedVehiclesSlice = createSlice({
  name: "searchedVehicles",
  initialState,
  reducers: {
    setSearchedVehicles: (state, action) => {
      state.vehiclesPool = action.payload;
    },
  },
});
export const selectSearchedVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool;
};
Dunker
const vehicleList = useSelector(selectSearchedVehicles);
selectSearchedVehicles = (state: stateProps) => {
return state.searchedVehicles.vehiclesPool;
};
vehicleList.map(vehicle -> ... }
yeah
Dunker
are you seeing something wrong
👀
I don't
Dunker
state.searchedVehicles dont have vehiclesPool
it's in the intital state actually
Dunker
oh yes mb
I don't think there's a prob cuz TS is pretty strict
it wouldn't let that slip yk
it's inferring the right data
Dunker
okay so what you are seeing as vehicleList at console
and is it changing when you select something from UI
lemme see
nah
not sure what you meant but it doesn't change in the console
when doing things that invoke rerendering the component
Dunker
i meant sth like
select X from UI -> console : ["X"]
" Y from UI -> console: ["Y"]
maybe problem is your inputs and select logic, (to send data to redux store)
and if [1,2,3].map(i => <VehicleCard/> not gives same error, problem is totally your vehicleList array
Dunker
import React from 'react'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'

const selectNumCompletedTodos = createSelector(
  (state) => state.todos,
  (todos) => todos.filter((todo) => todo.completed).length
)

export const CompletedTodosCounter = () => {
  const numCompletedTodos = useSelector(selectNumCompletedTodos)
  return <div>{numCompletedTodos}</div>
}

export const App = () => {
  return (
    <>
      <span>Number of completed todos:</span>
      <CompletedTodosCounter />
    </>
  )
}
When the selector does only depend on the state, simply ensure that it is declared outside of the component so that the same selector instance is used for each render:
redux has a lot edge cases tbh
Dunker
yea i meant declare it with createSelector
okay I'll try that
Dunker
When using useSelector with an inline selector as shown above, a new instance of the selector is created whenever the component is rendered. This works as long as the selector does not maintain any state. However, memoizing selectors (e.g. created via createSelector from reselect) do have internal state, and therefore care must be taken when using them. Below you can find typical usage scenarios for memoizing selectors.
actually I found what's causing it
but I will address this when I fix the issue first
Dunker
const counter = useSelector((state) => state.counter) yours looks okay also
so the problem is in a different component, the searchbar, my search feature logic is like this
// filter vehicles based on the search query
  useEffect(() => {
    if (!searchQuery) {
      // if there's no search query, just display all vehicles
      dispatch(setSearchedVehicles(allVehicles));
    } else {
      const queryWords = searchQuery?.toLowerCase().split(" ");
etc..
Dunker
i said this you also 😄
maybe problem is your inputs and select logic, (to send data to redux store)
yes that's why I checked the other file
it's because I'm storing all vehicles in the searchedVehicles when there's no query
what's wrong with that tho 🙂
Dunker
you have a thing like all parts are moving 😄
if there is no search, why dispatch tho
because the searchedVehicles is initially empty
also when user clears the search it displays all vehicles
Dunker
yea but method of that is kinda wrong
maybe you should use .filter when search
where?
Dunker
vehicleList.filter(q => q.brand == query) ?
instead of what?
Dunker
dispatch
that's not supposed to do any filtering
it just fills the searched vehicles pool with all the available vehicles from the vehicle's slice(another slice)
so I basically have two slices related to vehicles, one is for vehicles and one is for searched vehicles
Dunker
hmm, and
the searched vehicles is initially empty, but when a user enters the page it is filled with allVehicles.
all vehicles comes from this selector
export const selectAllVehicles = (state: RootState) => {
  return state.vehicle.entities;
};

selected by
  const allVehicles = useSelector(selectAllVehicles);
Dunker
okay go on
it is filled whenever the search query is empty
when there's no query in the url params
Dunker
yep
then
that's all
idk how to go about fixing it
Dunker
i think problem is you are using dispatch in useEffect instead a action function
like handleSearch, onSubmit ..
thats because your search logic kinda working wrong
yeah actually I could listen to the search input change and do the thing
Dunker
okay at first all vehicles rendering at page, (this dont need a dispatch tho) then you are searching them
lemme try that
Dunker
when someone writes something as query, your list should rerender as updated
filtered
@Dunker okay at first all vehicles rendering at page, (this dont need a dispatch tho) then you are searching them
there's more stuff to account for, I can't directly use the allVehicles
Dunker
so your selector probably will like (state => state.allVehicles.filter(v => v.brand == query))
there's also these selectors that the search filter gonna be affecting based on current page
export const selectSavedVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.SAVED
  );
};
export const selectDeletedVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.DELETED
  );
};
export const selectRequestedVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.REQUESTED
  );
};
export const selectBidlostVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.BID_LOST
  );
};
export const selectBidwonVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.BID_SUCCESS
  );
};
export const selectImportingVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.IMPORTING
  );
};
export const selectReceivedVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.RECEIVED
  );
};
export const selectForsaleVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.FOR_SALE
  );
};
export const selectSoldVehicles = (state: stateProps) => {
  return state.searchedVehicles.vehiclesPool.filter(
    (vehicle: IVehicle) => vehicle.status === VehicleProgressStatus.SOLD
  );
};
export default SearchedVehiclesSlice.reducer;
if the problem is with the usage of useEffect then I could probably fix it with an onChange handler for the search input and move my logic there
Dunker
selectSearchedVehicles = () => state.searchedVehicles.vehiclesPool.filter((v => v.name == query) kinda
@dank() if the problem is with the usage of useEffect then I could probably fix it with an onChange handler for the search input and move my logic there
Dunker
onSubmit handler is better, when you submit query it ll send to store and render it back, if you want it dynamic use onChange
but how do I fill the searched vehicles pool on first render
without useEffect
Dunker
at first there is no searchedVehicles
input is empty
query is null
yes but when there is no searchedVehicles I gotta show all vehicles
Dunker
yea just fetch all vehicles from state and render it directly
when there is no query i mean
then the filtering won't apply to it
I mean
wait
Dunker
no you ll filter your data according to your query
as expected
first render all data, then filter
not even related with state
the way I'm doing it now is, I render the searchedVehicles in the page and then it's filled
and the filters apply to it
that works for all the different selectors
Dunker
yea but you are trying to write something like [].map(i -> <VehicleCard/>
cuz vehicleList is [] at first load
I see
Dunker
you are connecting your search logic to data
i meant you need to connect your data to search logic
I don't wanna apply any filters to the actual vehicles slice, just the searchedVehicles
that's the thing
Dunker
like
if (query) {return state.vehicles.filter(v => v.name = query)
else {return state.vehicles.all ..
also look at RTK-Query
this is all i have so far, hope you can fix it somehow if not there s a channel in reactiflux dc server named redux
yeah I'll try to figure out a way without changing my whole process
Dunker
only you need that actual vehicles slice, not more
yeah maybe no need for searchedVehicles slice, it's just I'm trying to keep all the selectors in sync
Dunker
with rtk-query, not even need selectors
interesting
Dunker
good luck, if nothing helps tomorrow i can look at live if you want
alr deal thanks a lot I appreciate it
I might just keep it as is and call it a feature, the user has to search before all the vehicles appear
doesn't seem too bad
export const useMounted = () => {
  const [mounted, setMounted] = useState<boolean>(false);
  useEffect(() => {
    setMounted(true);
  }, []);
  return mounted;
}
Answer