1

I am using this article to help with using the useReducer hook.

I have created this context component because I am using the react-leaflet routing machine to create markers for the route and I'd like to save the latitude and longitude in local storage; I am able to save them initially but I can't update them!

Essentially in my UserContext component, I have created this reducer function:

function userReducer(state, { type, payload }) {
    switch (type) {
      case 'isLengthOfMarkersLessThanTwoFalse': {
        return {
          ...state,
          isLengthOfMarkersLessThanTwo: payload.isLengthOfMarkersLessThanTwo
        };
      }
      case 'updateMarkers': {
        console.log('type, payload ', type, payload);
        return {
          ...state,

          ...state.markers.map(element => {
            console.log('element.alt === payload.alt ', element.alt === payload.alt);
            return element.alt === payload.alt ? { ...element, ...payload } : element;
          })
        };
      }
      default: {
        throw new Error(`Unhandled action type: ${type}`);
      }
    }
  }

So you can see there is a console.log in the case clause,

  case 'updateMarkers': {
            console.log('type, payload ', type, payload);
            return {
              ...state,

type, payload  updateMarkers LatLng {lat: 40.74154270838886, lng: -73.76230053137989, alt: "current location"} alt: "current location"lat: 40.74154270838886lng: -73.76230053137989

So it actually IS getting passed in the reducer, but the state is not being updated!

This is the complete component :


import React, { useState, useEffect, useReducer } from 'react';
import { getUserAvatar } from '../../utils/index';

import { parse, stringify } from 'flatted';

var initialState = {
  avatar: '/static/uploads/profile-avatars/placeholder.jpg',
  isRoutingVisible: false,
  removeRoutingMachine: false,
  isLengthOfMarkersLessThanTwo: true,
  markers: [],
  currentMap: {}
};

var UserContext = React.createContext();

function setLocalStorage(key, value) {
  function isJson(item) {
    item = typeof item !== 'string' ? JSON.stringify(item) : item;

    try {
      item = JSON.parse(item);
    } catch (e) {
      return false;
    }

    if (typeof item === 'object' && item !== null) {
      return true;
    }

    return false;
  }

  try {
    window.localStorage.setItem(key, JSON.stringify(value));
  } catch (errors) {
    // catch possible errors:
    console.log(errors);
  }
}

function getLocalStorage(key, initialValue) {
  try {
    const value = window.localStorage.getItem(key);
    return value ? JSON.parse(value) : initialValue;
  } catch (e) {
    return initialValue;
  }
}

function UserProvider({ children }) {
  const [user, setUser] = useState(() => getLocalStorage('user', initialState));
  const [isAvatarUploading, setIsAvatarUploading] = useState(true);

  function userReducer(state, { type, payload }) {
    switch (type) {
      case 'isLengthOfMarkersLessThanTwoFalse': {
        return {
          ...state,
          isLengthOfMarkersLessThanTwo: payload.isLengthOfMarkersLessThanTwo
        };
      }
      case 'updateMarkers': {
        console.log('type, payload ', type, payload);
        return {
          ...state,

          ...state.markers.map(element => {
            console.log('element.alt === payload.alt ', element.alt === payload.alt);
            return element.alt === payload.alt ? { ...element, ...payload } : element;
          })
        };
      }
      default: {
        throw new Error(`Unhandled action type: ${type}`);
      }
    }
  }

  const [state, dispatch] = useReducer(userReducer, initialState);

  // console.log('user ', user);
  useEffect(() => {
    setLocalStorage('user', user);
  }, [user]);

  useEffect(() => {
    console.log('user.isRoutingVisibile ', user.isRoutingVisibile);
  }, [user.isRoutingVisibile]);

  useEffect(() => {
    console.log('state', state);
    if (user.markers.length === 2) {
      dispatch({
        type: 'isLengthOfMarkersLessThanTwoFalse',
        payload: { isLengthOfMarkersLessThanTwo: false }
      });
    }
  }, [JSON.stringify(user.markers)]);

  useEffect(() => {
    if (user.id) {
      getUserAvatar()
        .then(userAvatar => {
          setIsAvatarUploading(false);
          setUser(user => ({ ...user, avatar: userAvatar }));
        })
        .catch(err => console.log('error thrown from getUserAvatar', err));
    } else {
      console.log('No user yet!');
    }
  }, [user.id]);

  return (
    <UserContext.Provider
      value={{
        userId: user.id,

        setUserId: id => setUser({ ...user, id }),

        userAvatar: user.avatar,

        setUserAvatar: avatar => setUser({ ...user, avatar }),

        isAvatarUploading: isAvatarUploading,

        userImages: user.images,

        setUserImages: images => setUser({ ...user, images }),

        userMarkers: user.markers,

        setUserMarkers: marker => {
          state.isLengthOfMarkersLessThanTwo
            ? setUser(user => ({
                ...user,
                markers: [...user.markers, marker]
              }))
            : () => null;
        },

        setUpdateUserMarker: dispatch,

        deleteUserMarkers: () => {
          setUser({
            ...user,
            markers: [
              ...user.markers.filter(function(e, i, a) {
                return e !== a[a.length - 1];
              })
            ]
          });
        },

        setUserMarkersToNull: () =>
          setUser({
            ...user,
            markers: null
          }),

        userMap: user.currentMap,

        setUserCurrentMap: map =>
          setUser({ ...user, currentMap: { ...user.currentMap, map } }),

        removeRoutingMachine: user.removeRoutingMachine,

        resetUserMarkers: () => {
          console.log('fired setIsRoutingVisibileToTrue');
          setUser({
            ...user,
            removeRoutingMachine: true,
            isRoutingVisible: false,

            markers: []
          });
        },

        isRoutingVisibile: user.isRoutingVisible,

        setIsRoutingVisibileToTrue: () => {
          console.log('fired setIsRoutingVisibileToTrue');
          setUser({
            ...user,
            isRoutingVisible: true
          });
        },

        setIsRoutingVisibileToFalse: () => {
          console.log('fired setIsRoutingVisibileToFalse');
          setUser({
            ...user,
            isRoutingVisible: false
          });
        }
      }}
    >
      {children}
    </UserContext.Provider>
  );
}

export default UserContext;

export { UserProvider };

And in the Routing Machine I am calling it like so:

  setUpdateUserMarker({
    type: 'updateMarkers',
    payload: e.latlng
  });
zhulien
  • 5,145
  • 3
  • 22
  • 36
Antonio Pavicevac-Ortiz
  • 7,239
  • 17
  • 68
  • 141
  • It would be nice if you embed all this code in a demo so we could reproduce your issue. – kboul Jan 09 '21 at 12:34

1 Answers1

1

In your case 'updateMarkers' you are mapping the state.markers array but you are spreading it as top-level properties of the state. This is actually going to create properties 0, 1, etc. on your state and won't update the markers property at all.

Run this very simplified version to see what it does.

const state = {markers: [1,2,3]};
console.log({...state, ...state.markers});

What you want to do is to replace the markers property with the mapped array.

case 'updateMarkers': {
  return {
    ...state,
    markers: state.markers.map(element => {
      return element.alt === payload.alt ? { ...element, ...payload } : element;
    })
  };
}
Linda Paiste
  • 38,446
  • 6
  • 64
  • 102