4
useEffect(() => {
    return history.listen(_ => {
        console.log(history.action);
        if (history.action === 'POP' && (location.pathname === Settings_NewPersonPath || location.pathname === Settings_NewBusinessPath) && entityState.data) {
            setEntityState({
                ...entityState,
                submitted: false
            });
        }
    })
}, [])

I'm emulating having multiple pages using a state. However, when someone is on the second "page", and they press the back button, I just want to update the state and not actually go back.

How would I do this?

Ali Bdeir
  • 4,151
  • 10
  • 57
  • 117
  • 1
    Check out [this](https://stackoverflow.com/a/66659233/1751640) answer of mine. – Mordechai Jun 15 '21 at 16:04
  • @AliBdeir do you want to stop the transition to Settings_NewPersonPath and Settings_NewBusinessPath routes, o do you want to save the data first and then apply the route change? – lissettdm Jun 18 '21 at 19:25

2 Answers2

3

You can use the history.block API to prevent page transition. Example:

useEffect(() => {
  const unblock = history.block(({ action, location, retry }) => {
    // Conditional state manipulation
    // Call unblock and retry to actually perform transition
  });

  return () => {
    unblock();
  };
}, []);
kausko
  • 930
  • 3
  • 10
2

You can create a Route Context to handle the routes changes and create listeners for those components where you need to save the data before changing the route or prevent the route changes:

RouteProvider.js

Add this component to the top of your component structure, but inside the Router component.

const RouteContext = React.createContext();

const RouteProvider = ({ children }) => {
  const locationKey = useRef({ /* keep tracking the route history */
    to: null,
    from: null,
  });
  const listeners = useRef({});
  const history = useHistory();

  useEffect(() => {
    /* set the locationKey value during the first render*/
    locationKey.current = {
      from: history.location.pathname,
      to: history.location.pathname
    }
    return history.block(({ pathname }, action) => {
      if(locationKey.current.to === pathname) return;
      if (locationKey.current.from === pathname) {
        /** you are going to the last route visited */
        const listenerValues = Object.values(listeners.current);
        listenerValues.forEach(({callback, prevent}) => {
          callback();
        });
        /** if one listener needs to prevent the route changes */
        if(listenerValues.find(({prevent})=> prevent)) return false;
      }
      locationKey.current = { from: locationKey.current.to, to: pathname };    
    });
  }, [history]);

  /* add listeners*/
  const onBackListener = (callback, prevent = false) => {
    const ID = Date.now;
    listeners.current[ID] = {
      callback,
      prevent,
    };
    return ID;
  };

  /* remove listeners*/
  const removeListener = (ID) => delete listeners.current[ID] ;

  return <RouteContext.Provider value={{onBackListener, removeListener}}>{children}</RouteContext.Provider>;
};

SecondComponent.js

This component will add a listener to the RouteContext and will prevent the route changes.

function Second() {
  const {onBackListener, removeListener} = React.useContext(RouteContext);
  const [state, setState] = useState();

  useEffect(()=> {
     const listener = onBackListener(()=> {
       /**save data */
       console.log("saving data");
       setState("Data")
     }, true);


     return () => removeListener(listener);
  }, []);

  return <h2>Second Page</h2>;
}

I didn't test all possibles escenarios but it will give you an idea of how to transform this to accomplish your requirements.

Working example If you try to go from Second component to the Home component the data is saved and the route doesn't changes.

lissettdm
  • 12,267
  • 1
  • 18
  • 39