0

I'm busy creating a navigation flow on the startup page in a SPA, and I'm experimenting with different methods of going about navigating between components. Here's an image to give you an idea of the flow. enter image description here

I'm experimenting in a sandbox trying to come up with a neat method, but I'm not entirely sure if it's particularly elegant.

The idea is that there will be a container, a panel, that renders different components depending on where in the flow the user is. The buttons have event handlers that navigate the user to different parts of the flow. I'm using a reducer to handle the actions of the user which is stored in the parent container (called 'Panel'). The payload is just the name of the component that should be rendered within the panel. It looks like this:

import './Panel.css';
import { useReducer } from 'react';
import {Start, Middle, End} from './';

const initialState = {
   // activePanel can be: Start, Middle, End
   activePanel: "Start",
}

function reducer(state, action) {
   switch (action.type) {
      case "changePanel": {
         return {
            ...state, 
            activePanel: action.payload
         }
      }
      default: {
         return {
            ...state
         }
      }
   }
}

function renderPanel(activePanel, dispatch, setShowPanel) {
   switch (activePanel) {
      case "Start": {
         return (<Start dispatch={dispatch}/>);
      }
      case "Middle": {
         return (<Middle dispatch={dispatch}/>);
      }
      case "End": {
         return (<End dispatch={dispatch}/>);
      }
      default:{
         return null;
      }
   }
}

export default function Panel({children}) {
   const [state, dispatch] = useReducer(reducer, initialState);

   return (
      <div className="Panel">
         {renderPanel(state.activePanel, dispatch)}
      </div>
   )
}

But here's the deal: the whole page stores the state of the visibility of the panel. But I want the continue button of the final panel to destroy the panel. That means that the final continue button needs to access the state of the visibility of the panel, which is stored two ancestors up (button -> Panel -> App). So I wrapped the panel in a context provider whose value is a function that is used to hide the panel. The App component looks like this:

import Panel from "./Navigation/Panel";
import { useState, createContext } from "react";
import { Start, Middle, End } from "./Navigation";
import ImageDropZone from "./ImageDropZone/ImageDropZone";


export const FlowContext = createContext();

export default function App() {
  const [showPanel, setShowPanel] = useState(false);
  const [inProgress, setInProgress] = useState(false);

  const startFlow = () => {
    setShowPanel(true);
    setInProgress(true);
  };

  const goToStart = () => {
    setShowPanel(false);
    setInProgress(false);
  };

  const value = {
    showPanel: showPanel,
    setShowPanel: setShowPanel,
  };

  return (
    <div className="App">
      <FlowContext.Provider value={value}>
        {showPanel && <Panel />}
      </FlowContext.Provider>
      {inProgress && !showPanel && <ImageDropZone />}
      {!inProgress && <button onClick={startFlow}>Start flow</button>}
      {inProgress && <button onClick={goToStart}>Go to start</button>}
    </div>
  );
}

And the End component which is contained within the panel uses the context to access the function that sets the visibility of the panel:

import Panel from '../Panel';
import { useContext } from 'react';
import {FlowContext} from '../../App';

export default function End({dispatch}) {
   const {setShowPanel} = useContext(FlowContext);
   const onContinue = () => {
      setShowPanel(false);
   }

   const onGoBack = () => {
      dispatch({type: "changePanel", payload:"Middle"});
   }

   return (
      <div>
         End content
         <div>
            <button onClick={onContinue}>
               Continue
            </button>
            <button onClick={onGoBack}>
               Go Back
            </button>
         </div>
      </div>
   )
}

Is this method fine? It may be an ambiguous question that has no direct answer, but I'm uncertain as to whether there's a best practice that I might be unaware of here.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.