3

I've seen several of these questions on Stack Overflow, but none of the fixes seem to work. this.props.location.state always returns undefined. Here is my AppContainer, ExamplePageParent, and ExamplePage.

import {Router, Route, Switch} from "react-router-dom";

class AppContainer extends Component {
  render() {
    return (
      <Router>
        <Switch>   
          <Route exact path="/page" component={Page}
          <Route exact path="/examplePage" component={ExamplePage}/>
        </Switch>
      </Router>
    )
  }
}
//has mapStateToProps and mapDispatch to Props here

export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);

--

class Page extends Component {

  render() {
   return(
     <AnotherPage key=this.props.id>
   );
  }
    }

// has mapStateToProps here (this.props.id comes from here)

export default connect(mapStateToProps, mapDispatchToProps)(Page);

--

class AnotherPage extends Component {

  render() {
   return(
     <ExamplePageParent key=this.props.id>
   );
  }
    }

// has mapStateToProps here (this.props.id comes from here)

export default connect(mapStateToProps, null)(AnotherPage);

--

class ExamplePageParent extends Component {

  //pathName and dataPassed content filled, not relevant to question

  render() {
   return(
     <Link
       class={'link-styling'}
       target="_blank"
       rel="noreferrer"
       to={{pathname: this.state.pathName, state: {dataPassed: true} }}
     >
       Click Me
     </Link>
   );
  }
    }

//has mapStateToProps here

export default connect(mapStateToProps, null)(ExamplePageParent);

--

import {withRouter} from "react-router";

class ExamplePage extends Component {

  constructor(props) {
    super(props);

    //this has content:
    console.log(this.props.location);

    //undefined:
    console.log(this.props.location.state);
  }

  render() {
   return(
     // do stuff
   );
  }
}
export default withRouter(ExamplePage);
penguin
  • 143
  • 3
  • 11
  • Where ***specifically*** is `props.location.state` undefined? `ExamplePage` component is rendered on the `component` prop, so route props are passed to it, and it's also wrapped in the `withRouter` HOC, so again, route props are passed. Where is this `ExamplePageParent` rendered that is passing route state? – Drew Reese Feb 10 '22 at 18:54
  • Good question. `ExamplePageParent` is the third child component of the Route `Page`, which I've added above. – penguin Feb 10 '22 at 18:59
  • Meaning it would be `Page` > `AnotherPage1` > `AnotherPage2` > `ExamplePageParent`. But only `Page` has Route. – penguin Feb 10 '22 at 19:00
  • Can you update your question to include all relevant code? The complete `App` component, and all the page components between `Page` through to `ExamplePageParent`? What `Router` are you using? Is `this.state.pathName` the path value you expect? – Drew Reese Feb 10 '22 at 19:08
  • I have added the page components between Page through to `ExamplePageParent`. Router is from "react-router-dom", while withRouter is from "react-router". I've added the appropriate imports for you to see. And yes, `this.state.pathName` is the correct path value. – penguin Feb 10 '22 at 19:36
  • Also, I meant to say that `ExamplePageParent` is actually the second child component of `Page`. So, `Page` > `AnotherPage` > `ExamplePageParent`. – penguin Feb 10 '22 at 19:37

1 Answers1

1

Issues

  • You are using the low-level Router which needs a history object to have a defined location object to work with.
  • The code is linking to a new window, so the app is loaded/mounted from scratch and the passed route state isn't transferred.

To address the undefined location you have a couple options:

  1. Import a custom history creator from the history package:

    import { createBrowserHistory } from 'history';
    
    const history = createBrowserHistory();
    
    ...
    
    <Router history={history}>
      ...
    </Router>
    
  2. Use one of the higer-level routers, i.e. BrowserRouter, HashRouter, MemoryRouter, etc...:

    import { BrowserRouter as Router } from 'react-router-dom';
    
    ...
    
    <Router>
      ...
    </Router>
    

To address accessing the "state" in the receiving component you'll need to add a bit of logic. When the app is started in a new window/tab the state isn't transferred, so to combat this you can send the route state serialized as a queryString search param.

<Link
  className={"link-styling"}
  target="_blank"
  rel="noreferrer"
  to={{
    pathname: this.state.pathName,
    search:
      "stuff=content&moreStuff=moreContent" + // existing search params
      "&dataPassed=This is the passed data.", // additional for state
    state: { dataPassed: "This is the passed data." }
  }}
>
  Click Me
</Link>

And then in ExamplePage process the queryString to extract and delete the added "dataPassed" query param, and redirect with populated route state previously existing queryString.

class ExamplePage extends Component {
  componentDidMount() {
    const { history, location } = this.props;
    const { pathname, state, search } = location;

    console.log({ location });

    const searchParams = new URLSearchParams(search);
    let dataPassed;

    if (searchParams.has("dataPassed")) {
      dataPassed = searchParams.get("dataPassed");
      searchParams.delete("dataPassed");
    }
    history.replace({
      pathname,
      search: searchParams.toString(),
      state: { dataPassed }
    });
  }

  render() {
    return (
      ...
    );
  }
}

Edit this-props-location-state-is-undefined-in-react-link-router

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • I am not allowed to add anything to parameter to the `this.state.pathName`, as part of the requirements. Correct me if I'm wrong, but the `search` param in the `Link` component is appended to the `pathname`, which is not what I want, as it changes the output of the page. – penguin Feb 11 '22 at 19:43
  • @penguin This solution doesn't modify `this.state.pathName` at all. The fully qualified URL is `pathname` + `search` + `hash`, yes, but this is what the second half of the solution resolves by processing the `search` on the receiving routed component and redirecting to just `pathname` with actual route state again. – Drew Reese Feb 11 '22 at 19:55
  • I see. However, the `this.state.pathName` includes some query params in that state. Basically, `this.state.pathName` looks something like this: `/path/?stuff=content&moreStuff=moreContent`. This is without including the `dataPassed`, ofcourse. I think the `history.replace` line you've included would only keep the `/path/` and not the `?stuff=content&moreStuff=moreContent`. Again, please correct me if I'm wrong. – penguin Feb 11 '22 at 20:25
  • @penguin Ok, I see. You would need to inject this additional query param and then remove it on the other side, preserving any other query params that may've been sent. I'll update my answer and example in a moment. – Drew Reese Feb 11 '22 at 22:10
  • @penguin Please see updated answer and linked codesandbox. – Drew Reese Feb 11 '22 at 22:37
  • 1
    Hi Drew, I really appreciate the time and effort you put into this answer. I learned a lot about routers and UrlSearchParams, thanks to you. Thank you! :) – penguin Feb 16 '22 at 18:30
  • 1
    @penguin Welcome. Always glad to help out. Cheers and good luck! – Drew Reese Feb 16 '22 at 18:30