30

I'm trying here on my application to do some tests with the new context API from React 16.3 but I can't understand why my redirect never works.

<ContextA>
  <Switch>
    <Route exact path='/route1' component={ Component1 } />
    <ContextB>
      <Route exact path='/route2' component={ Component2 } />
      <Route exact path='/route3' component={ Component3 } />
    </ContextB>
    <Redirect from='/' to='/route1' />
  </Switch>
</ContextA>

I don't want to have my ContextB available for all the routes, just 2 and 3. How can I do this?

9 Answers 9

27

It looks like that <Switch> should only have <Route> and <Redirect > components as direct children. (source)

I suppose that's why your Redirect doesn't work as you use ContextB as a Switch child.

The simplest but repetitive solution could be to pass your ContextB as a child of each <Route> you want:

Note: These solutions suppose that you assigned the default value of your Context component like this: const MyContext = React.createContext(defaultValue);

<Route exact path='/route2'>
  <ContextB.Provider>
    <Component1 />
  </ContextB.Provider>
</Route>

You can even create a ContextRoute component for this:

import React from 'react';
import { Route } from 'react-router-dom';

const ContextRoute = ({ contextComponent, component, ...rest }) => {
  const { Provider } = contextComponent;
  const Component = component;

  return (
    <Route {...rest}>
      <Provider>
        <Component />
      </Provider>
    </Route>
  );
};

export default ContextRoute;

And then use it as a Route:

<ContextA>
  <Switch>
    <Route exact path='/route1' component={ Component1 } />
    <ContextRoute exact path='/route2' contextComponent={ContextB} component={ Component2 } />
    <ContextRoute exact path='/route3' contextComponent={ContextB} component={ Component3 } />
    <Redirect from='/' to='/route1' />
  </Switch>
</ContextA>

With this solution, you then use your context with render props in your nested Components:

return (
  <ContextB.Consumer>
    {value => <div>{value}</div>}
  </ContextB.Consumer>
);

But we can imagine much more solutions to this like HOC, passing context value directly to the route component props, etc...

Sign up to request clarification or add additional context in comments.

5 Comments

It worked, thank so much! Now I could understand better the architecture.
Seems like the state of Context B will be lost however if you go to /route1 from either /route2 or /route3. Seems like it's usually better to just put at the root somewhere and then grab it in those components where it's needed?
@jones I would agree with you as a good practice, but I guess this will depends on the goals of each app.
Read answer below to understand why this approach wouldn't really work as intended: stackoverflow.com/a/61824692/4470169
bro I've been stuck on this for 6 hours.. you saved me. I owe you my life.
21

As a warning to others, accepted answer doesn't really work as you would expect from the original (non-working) concept:

// This comes from the original question (doesn't work as-is!)
<ContextA>
  <Switch>
    <Route exact path='/route1' component={ Component1 } />
      <ContextB>
        <Route exact path='/route2' component={ Component2 } />
        <Route exact path='/route3' component={ Component3 } />
      </ContextB>
      <Redirect from='/' to='/route1' />
   </Switch>
</ContextA>

In there, /route2 and /route3 are sharing a context and the expectation should be that:

  1. State is kept between routes transitions.
  2. If either Component2 or Component3 update the context, changes should be reflected back to the other.

None of the above is true for the accepted solution.

4 Comments

You are right, good catch. Even if your answer is not really one (maybe a comment or edit in the accepted answer would have been more appropriate?). One way to make it works would be to lift the ContextB provider upper the Switch component, and use the consumers on route 2 and 3. I'll try to update the answer when I got time
I'm still looking for a solution to this and haven't been able to figure it out yet. @Nenu I'm interested in your theory about lifting the context provider – did you ever solve this?
Looking for an answer for this in 2022, did anyone ever solve this?? Anytime we switch between the routes the context resets to the initial state. Anyone else facing similar issue?
@A.K.47 Working Solution where sharing contexts between routes is done properly here: stackoverflow.com/questions/50780311/…
8

use render method in Route. this will solve your problem as i did this in my App.

<Route
  path="/"
  render={(props) => (
    <ContextB>
      <Component2 {...props} />
    </ContextB>
  )}
/>

1 Comment

Saved my ass. Was getting very weird behavior, with Context just breaking my Switch. Turns out that, indeed, <Switch /> only works right with <Route /> components as its immediate children.
2

thanks for your help, i used this in store context and works!. (createContext and useReducer)

import React, {createContext, useReducer} from "react";
import Reducer from './reducer.js'

const initialState = {
    /* */
};
const Store = ({children}) => {
    const [state, dispatch] = useReducer(Reducer, initialState);

    return (
        <Context.Provider value={[state, dispatch]}>
            {children}
        </Context.Provider>
    )
};

export const Context = createContext(initialState);
export default Store;

/* in App.js*/

const App = () => {

    return (
    <BrowserRouter>
            <Switch>
            <Route exact path="/" render={ (props)=> <Store> <Home {...props} /> </Store> } />
            <Route exact path="/about" render={ (props)=> <Store> <About {...props} /> </Store> } />
            <Route exact path="/login" render={ (props)=> <Store> <Login {...props} /> </Store> } />
            </Switch>

    </BrowserRouter>
    );
};

1 Comment

Doesn't that just reset the context state when navigating between components, same as all the other examples?
1

Hi I found this solution using react-router-dom 6.3 (Don't know if it works in previus versions). It uses Layout. I think is quite simple. The idea is import the context provider into the component layout, and use the layout to wrap the routes that i want to share with that context. Somthing like this.

<BrowserRouter>
  <Routes>
    <Route element={<ComponentContextLayout />}>
      <Route path="/component1" element={<Component1 />} />
      <Route path="/component2" element={<Component2 />} />
    </Route>
  </Routes>
</BrowserRouter>

And then in the ComponentContextLayout :

import { Outlet } from "react-router-dom";
import { ComponentProvider } from "./ComponentContext";

export default function MiPlanContextLayout() {
  return (
    <ComponentProvider>
      <Outlet />
    </ComponentProvider>
  );
};

Comments

0

context that specific to the element.

 <Route
     exact
     path="/route-1"
     element={
        <TheContextProvider>
     <TheRoutElement/>
  </TheContextProvider>
}/>
<Route
  exact
  path="/route-2"
  element={
    <TheContextProvider>
      <TheRoutElement2/>
    </TheContextProvider>
  }/>

Comments

0

Context around the router.
Routes share data.
Rejoice.

Index/App.js

<YourContextProvider>  

   <Router>  
     <Switch>
       <Route>
       </Route>
     </Switch>
   <Router>

</YourContextProvider>

YourContext.js

YourContextProvider = (props)=>{

  yourContext = React.createContext(null);

  value = { shared, state, and, functions }
    return (
       <YourContext.Provider value={value}>
         {props.children}
       </YourContext.Provider>
    )
}
export {YourContext, YourContextProvider}

Comments

0

You can also directly provide the context within the 'element' part of the route.

<Routes>
<Routes path="/endpoint" element ={<ContextProvider><Component/></ContextProvider>}/>
</Routes>

Comments

-2

Simple solution:

<ContextA>
    <Switch>
        <Route exact path='/' render={ () => <Redirect to='/route1' /> } />
        <Route path='/route1' component={ Component1 } />
    </Switch>
    <ContextB>
        <Switch>
            <Route path='/route2' component={ Component2 } />
            <Route path='/route3' component={ Component3 } />
        </Switch>        
    </ContextB>        
</ContextA>

5 Comments

@impulsgraw It will. You just have to declare your error route handler inside the switch wrapper at the bottom of your route declarations.
no, it won't. If having multiple switches, in which one should I include the error route so the router didn't display it all the time?
Since Switch wrappers are nested, you will have to conditionally render your error route handler. You could define a custom function that loops through all your static paths and returns a boolean. Use it to conditionally render your error route handler if no match is found. i.e ``` { !isMatch() && <Route component={NotFound}} /> ```
The approach you're suggesting is obviously a smelly workaround. In fact, what you're saying means making up own Switch implementation instead of the built-in one, which, I think, is the only available option here.
Restricting yourself to strictly follow an api implementation for every use case is not practical and ideal. That's why you have to create a logical work around that will achieve your expected result.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.