Home > database >  redux state is undefined in useEffect and useSelector
redux state is undefined in useEffect and useSelector

Time:02-23

I am new to redux. Currently I am working on a project that shows nearby bike stations on map by API call. So that means most of the redux actions are async and returns some data for next api call in another action. When I dispatch all my actions in useEffect and taking states with useSelector it is showing them at first render and returning undefined after a refresh. This is the common problem I will face while I work with redux, redux-thunk, useEffect, useSelector.

I have tried async function inside useEffect hook to wait for each action call to finish

Actions :

export const fetchIPLocation = () => {
    return async (dispatch) => {
        try {
            let response = await fetch(`https://ip.nf/me.json`)
            if(response.ok) {
                let data = await response.json()
                dispatch({
                    type: FETCH_IP_LOCATION,
                    payload: data.ip.ip
                })
            }
        } catch (error) {
            
        }
    }
}

export const fetchUserData = (IPadress) => {
    return async (dispatch) => {
        try {
            let response = await fetch(`http://api.ipstack.com/${IPadress}?access_key=15633b167163620b5cd84ef60db62414`)
            if(response.ok) {
                let data = await response.json()
                dispatch({
                    type: FETCH_USER_DATA,
                    payload: data
                })
            }
        } catch (error) {
            console.log(error)
        }
    }
}

Reducer :

import { initialState } from "../store";
import { FETCH_IP_LOCATION, FETCH_USER_DATA, FETCH_NETWORKS } from "../action";

export const rootReducer = (state = initialState, action) => {
    switch (action.type) {
        case FETCH_IP_LOCATION:
            return {
                ...state,
                ipLocation: action.payload,
                isLoading: false,
            }
        case FETCH_USER_DATA:
            return {
                ...state,
                userData: action.payload,
                isLoading: false,
            }
        case FETCH_NETWORKS: 
            return {
                ...state,
                isLoading: false,
                bikeNetworks: action.payload,
            }
    }
}

MapComponent.jsx which renders leaflet map and uses all states

import React, { useState } from 'react';
import '../styles/MapComponent.css';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import { useEffect } from 'react';
import { fetchIPLocation, fetchUserData, getUserData } from '../redux/action/';
import { useDispatch, useSelector } from 'react-redux'
 
const MapComponent = () => {

    const dispatch = useDispatch()

    const [latitude, setLatitude] = useState(0)
    const [longitude, setLongitude] = useState(0)
    const [checkCords, setCheckCords] = useState(false)

    const ipLocation = useSelector((state) => state.ipLocation)
    const userCountry = useSelector((state) => state.userData.country_code)
    
    console.log(userCountry)

 
    useEffect(() => {
      if(navigator.geolocation) {
          navigator.geolocation.watchPosition((position) => {
              setLatitude(position.coords.latitude)
              setLongitude(position.coords.longitude)
              setCheckCords(true)
          })
      }
    }, [])

    useEffect(async () => {
      await dispatch(fetchIPLocation())
      await dispatch(fetchUserData(ipLocation))
    }, [])

  return (
      !checkCords ? <h1>Loading...</h1> :
    <MapContainer center={[latitude, longitude]} zoom={11}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[latitude, longitude]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  
  )
}

export default MapComponent

redux store:

import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import persistStore from 'redux-persist/es/persistStore';
import storage from 'redux-persist/lib/storage';
import persistReducer from 'redux-persist/es/persistReducer';
import { rootReducer } from '../reducer';

export const initialState = {
    ipLocation: [],
    userData: [],
    bikeNetworks: [],
    bikeStations: [],
    isLoading: true,
}

const persistConfig = {
    key: 'root',
    storage
}

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const persistedReducer = persistReducer(persistConfig, rootReducer)

const configureStore = createStore(
    persistedReducer,
    initialState,
    composeEnhancers(applyMiddleware(thunk))
)

const persistor = persistStore(configureStore)

export { configureStore, persistor }

app.js and Provider :

import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import MapComponent from './components/MapComponent'
import { configureStore, persistor } from './redux/store';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { BrowserRouter,  Routes, Route } from 'react-router-dom';
import {Container, Row, Col} from 'react-bootstrap';


const App = () => {
  return (
    <Provider store={configureStore}>
      <PersistGate persistor={persistor} loading={null}>
        <Container fluid={true} className='px-0 overflow-hidden'>
          <BrowserRouter>
           <Row>
             <Col md={12}>
               <Routes>
                 <Route exact path='/' element={<MapComponent />} />
               </Routes>
             </Col>
           </Row>
          </BrowserRouter>
        </Container>
      </PersistGate>
    </Provider>
  );
}

export default App;

CodePudding user response:

In MapComponent.jsx ipLocation from the useSelector wouldnt update after the first function (ie. fetchIPLocation()) and before the second function (ie. fetchUserData()).

What you have to do is to dispatch the action fetchUserData(ipLocation) after getting the response from the action fetchIPLocation in Actions file.

// Action
export const fetchIPLocation = () => {
    return async (dispatch) => {
        try {
            let response = await fetch(`https://ip.nf/me.json`)
            if(response.ok) {
                let data = await response.json()
                dispatch({
                    type: FETCH_IP_LOCATION,
                    payload: data.ip.ip
                })
                dispatch(fetchUserData(data.ip.ip)
            }
        } catch (error) {
            
        }
    }
}

and in useEffect of MapComponent.jsx just call dispatch(fetchIPLocation())

  • Related