Home > Net >  How to share data between two components without using higher order functions in React Router and ou
How to share data between two components without using higher order functions in React Router and ou

Time:12-17

I have two components, one called <Flights/> and the other called <FlightResults/>.

Flights renders a context provider for the rest of the application.

const Flights = () => {
  return (
    <FlightSearchContext.Provider
      value={{
        typeOfTrip,
        fromAirport,
        departureDate,
        returnDate,
        toAirport,
        outGoingFlights,
        searchAirports,
        setSearchAirports,
      }}
    >
     <h1>Some UI</h1>     
    </FlightSearchContext.Provider>
  );
};

export default Flights;
const FlightResults = () => {
  const { toAirport, outGoingFlights } = useContext(FlightSearchContext);

  return (
    <div>Flight results</div>
  )
}

export default FlightResults

Right now I'm declaring both routes in my index.tsx file like this.

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { Routes, Route } from "react-router";
import { BrowserRouter } from "react-router-dom";

import Flights from "./Components/Flights/Flights";
import FlightResults from "./Components/Flights/FlightResults";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<App />}>
          <Route path="/flights" element={<Flights />} index />
          <Route path="/flights/flight-results" element={<FlightResults />} />
        </Route>
      </Routes>
    </BrowserRouter>
  </React.StrictMode>
);

FlightResults is a child route of Flights and it needs to access the context data that is declared in the Flights component.

Currently everything works fine but FlightResults cant access the data in the Flights component.

After a lot of searching around i found out that I should wrap my child components in the index.tsx file as below.

        <Route path="/" element={<App />}>
          <Route path="/flights" element={<Flights />} index>
            <Route path="/flight-results" element={<FlightResults />} />
          </Route>
          <Route path="/hotels" element={<Hotel />} />
          <Route path="/taxi" element={<Taxi />} />
        </Route>

and whilst this works the FlightResults UI doesn't show under /flights/flightresults, a possible fix was to render an Outlet component on the Flights component like below

const Flights = () => {
  return (
    <FlightSearchContext.Provider
      value={{
        typeOfTrip,
        fromAirport,
        departureDate,
        returnDate,
        toAirport,
        outGoingFlights,
        searchAirports,
        setSearchAirports,
      }}
    >
     <h1>Some UI</h1>
     <Outlet/>     
    </FlightSearchContext.Provider>
  );
};

export default Flights;

and the above does work but now both UIs show up on /flights/flightresults/ .as in both Flights and FlightResults show up on the same URL.

How can I make the right component render on the right URL but also still access the context data?

CodePudding user response:

Short answer: you should not declare FlightSearchContext.Provider in Flights, but instead above both Flights and FlightResults.

Adapting this example from the docs, you can declare the context above both Route, and if you need to update data in Flights, be sure to include setWhateverData in the context so you can update it from any children of the context

CodePudding user response:

Issue

The issue is that you're mixing what you want exclusively to be rendered on "/flights" with data you want provided to multiple routes.

Solution

The FlightSearchContext.Provider component of the current Flights component should refactored to a Layout Route leaving the UI portion in Flights to be rendered on its own route.

Example:

const FlightsProvider = () => (
  <FlightSearchContext.Provider
    value={{
      typeOfTrip,
      fromAirport,
      departureDate,
      returnDate,
      toAirport,
      outGoingFlights,
      searchAirports,
      setSearchAirports,
    }}
  >
    <Outlet/>     
  </FlightSearchContext.Provider>
);
<Route path="/flights" element={<FlightsProvider />}>
  <Route
    index                 // <-- "/flights"
    element={<Flights />}
  />
  <Route
    path="flight-results" // <-- "/flights/flight-results"
    element={<FlightResults />}
  />
</Route>

If you are really wanting to not use an Outlet component then you can simply lift the FlightSearchContext.Provider higher in the ReactTree. In this case the FlightsProvider becomes a normal Wrapper component.

Example:

const FlightsProvider = ({ children }) => (
  <FlightSearchContext.Provider
    value={{
      typeOfTrip,
      fromAirport,
      departureDate,
      returnDate,
      toAirport,
      outGoingFlights,
      searchAirports,
      setSearchAirports,
    }}
  >
    {children}     
  </FlightSearchContext.Provider>
);
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <FlightsProvider>
        <Routes>
          <Route path="/" element={<App />}>
            <Route path="/flights" >
              <Route index element={<Flights />} />
              <Route path="flight-results" element={<FlightResults />} />
            </Route>
          </Route>
        </Routes>
      </FlightsProvider>
    </BrowserRouter>
  </React.StrictMode>
);
  • Related