Search code examples
javascriptreactjsreduxaxiosreact-hooks

Empty redux store, useSelector returns nothing


so I've got an really simple react-app. It renders some cars on the first render, when you click details, it takes you to another router and shows only that vehicle based on its ID.

It's all okay when you follow the right order, open up the page, redux gets filled with data, car cards render up, then you click 'details' button, react-router steps in and routes us to some particular car's id, based on the ID we see the car.

BUT... at that point, when you try to click re-render the page, I get nothing from my redux store, what am I need to do? Do I need to inplement in my every component that needs redux store to fetch items if there's not?

This is my redux slicer

This is my component that renders all the cars

Aand this is where I get nothing...


This is my slice from redux

import { createSlice, createEntityAdapter, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'

const carAdapter = createEntityAdapter();

// async action
export const fetchCars = createAsyncThunk('cars', async () =>{
  const car = await axios.get('http://localhost:5000/api/cars');
  return car.data.data
});

const carSlice = createSlice({
  name: 'cars',
  initialState: carAdapter.getInitialState({
    status: 'idle',
    error: null
  }),
  reducers:{
  },
  extraReducers :{
    [fetchCars.pending]: (state, action) => {
      state.status = 'loading'
    },
    [fetchCars.fulfilled]: (state, action) => {
      state.status = 'fulfilled'
      carAdapter.setAll(state, action.payload)
    },
    [fetchCars.rejected]: (state, action) => {
      state.status = 'failed'
      state.error = action.error.message
    }
  }
})

export const {
  selectAll: selectAllCars,
  selectById : selectCarById,
} = carAdapter.getSelectors(state => state.cars)

export default carSlice.reducer

This is my first page, where I render all the vehicles from my api

import React, { useEffect } from 'react'
import { Link } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { fetchCars, selectAllCars } from '../features/car/carSlice'
import './car.css'

export default function Car() {
  const dispatch = useDispatch();
  const carStatus = useSelector(state => state.cars.status)
  const cars = useSelector(selectAllCars)

  useEffect(() => {
    if(carStatus === 'idle') {

      dispatch(fetchCars());
    }
  }, [dispatch, carStatus])

  return (
    <>
      {
        cars.map(car => {
          return (
            <div key={car.id} className="card">
              <img src={car.vehiclePhoto} alt="vehicle" className="vehicle-img" />
              <div className="card-container">
              <h4>{car.vehicleName}</h4>
              <p>{car.price}</p>
              <Link to={car.id}>Details</Link>
              </div>
            </div>
          )
        })
      }
    </>
  )
}

This is where the issue begins when you try to re-load the page

export default function Car({ match: { params: { id } } }) {
    const state =  useSelector(state => state)
    const car = selectCarById(state, id);

  return (
    <div className="card">
    { car ? 
      <>
      <img src={car.vehiclePhoto} alt="vehicle" class="vehicle-img" />
      <div className="card-container">
        <h4>{car.vehicleName}</h4>
        <p>{car.price}</p>
      </div>
      </> : 'loading...'
    }
    </div>
    )
}

Solution

  • Every page of the app needs to be able to load its own data. On a page which displays details for a single car, you want it to look in the state, select the data if it's already loaded, and dispatch a request for the data if it hasn't been loaded (like when you go to that page directly).

    You'll want to use a different API endpoint on the single car page than the one that you use on the home page because you want to load a single car's details from the id. It's probably something like 'http://localhost:5000/api/cars/123' for id #123.