javascriptreactjsreact-routerrelayjs

Using React, React Router, and React Relay which should I use to store and retrieve state?


I'm currently using React with React Router and React Relay. Each of these has ways of updating/passing data. React with useState, React Router with it's pushState wrapper (location.state), and Relay with it's updater() method.

I'm unsure however which I should be using to handle a global state (if any at all). There is a particular ID that all pages of my app need that is not a session. Imagine a form with let's say 5 steps. Step 1 has no ID, Step 2 gets the ID on successful submit, and now on steps 3-5 it needs that ID to be rendered into a hidden input (as an example). Let's call it formId.

Currently, I'm passing it using React Router with location.state but it feels wrong. Instead I feel like I should have a way to get this formId once it's set. If formId is null send them to Step 1 and if it's not null let the current page/route load.

Of course, I could use window.formId but that seems to most wrong.


Solution

  • I ended up using React's Context API.

    It looked something like this

    // FormIdContext.tsx
    import React, { createContext, useState, useContext } from 'react';
    
    interface FormIdContextType {
      formId: string | null;
      setFormId: (formId: string | null) => void;
    }
    
    const FormIdContext = createContext<FormIdContextType | undefined>(undefined);
    
    // A custom hook to consume the FormIdContext
    export function useFormIdContext(): FormIdContextType {
      const context = useContext(FormIdContext);
      if (!context) {
        throw new Error('useFormIdContext must be used within a FormIdContext.Provider');
      }
      return context;
    }
    
    export function FormIdProvider(props: React.PropsWithChildren<{}>) {
      const [formId, setFormId] = useState<string | null>(null);
    
      return (
        <FormIdContext.Provider value={{ formId, setFormId }}>
          {props.children}
        </FormIdContext.Provider>
      );
    }
    

    Then in my router

    // App.tsx
    import React, { useState } from 'react';
    import { BrowserRouter as Router, Route } from 'react-router-dom';
    import Step1 from './Step1';
    import Step2 from './Step2';
    import Step3 from './Step3';
    import FormIdContext from './FormIdContext';
    
    function App() {
      const [formId, setFormId] = useState<string | null>(null);
    
      return (
        <Router>
          <FormIdContext.Provider value={{ formId, setFormId }}>
            <Route path="/step1" component={Step1} />
            <Route path="/step2" component={Step2} />
            <Route path="/step3" component={Step3} />
          </FormIdContext.Provider>
        </Router>
      );
    }
    
    export default App;
    

    Then I can get and set those values like this

    // Step3.tsx
    import React from 'react';
    import { useHistory } from 'react-router-dom';
    import { useFormIdContext } from './FormIdContext';
    
    function Step3() {
      const history = useHistory();
      const { formId, setFormId } = useFormIdContext();
    
      const handleFormSubmission = () => {
        // Assuming you have the form data and you want to perform some submission logic
        // Once the submission is successful and you get the formId, you can update the state
        const newFormId = 'new_generated_form_id';
        setFormId(newFormId);
    
        // Optionally, you can navigate to another page or perform other actions
        // Here, we simply go back to Step2 as an example
        history.push('/step2');
      };
    
      return (
        <div>
          {/* Your Step 3 content that may use formId */}
          <h1>Step 3</h1>
          <p>Current formId: {formId}</p>
          <button onClick={handleFormSubmission}>Submit Form</button>
        </div>
      );
    }
    
    export default Step3;