12

I am not really sure why its showing the default route once I did a query param change. Is there a better approach for this kind of issue? maybe I shouldn't be using query param? Open to get educated!

Version "react": "^16.2.0", "react-dom": "^16.2.0", "react-router": "^4.2.0", "react-router-dom": "^4.2.2",


Test Case https://codepen.io/adamchenwei/pen/YeJBxY?editors=0011

Steps to reproduce Click on Home -> Step 1

Expected Behavior Go to Step 1 as well as Step 2 render correct dom

Actual Behavior Empty, nothing renders on the page

// For this demo, we are using the UMD build of react-router-dom
const {
  BrowserRouter,
  Switch,
  Route,
  Link
} = ReactRouterDOM

const {
  Component,
} = React;

const Router = BrowserRouter;

const Step1View = () => (
  <div>
    <h1> Step 1</h1>
  </div>
)

const Step2View = () => (
  <div>
    <h1> Step 2</h1>
  </div>
)

class Home extends Component {
  constructor(props) {
    super(props);
    console.log('-!!!')
    this.state = {
      step: 1,
    }
    this.next = this.next.bind(this);
  }

  next(stepNumber=1) {
    this.props.history.push({
      pathname: `/adamchenwei/pen/YeJBxY?editors=0011/?step=${stepNumber}`,
    });
    const query = this.props.history.location.pathname;
    console.log('---aaaaa');
    console.log(query);
    if (query === '/adamchenwei/pen/YeJBxY?editors=0011/?step=1') {
      this.setState({
        step: 1,
      })
    } else if (query === '/adamchenwei/pen/YeJBxY?editors=0011/?step=2') {
      this.setState({
        step: 2,
      })
    }
  }
  render() {
    console.log('render!!!');
    console.log(this);
    const {
      step
    } = this.state;
    console.log('---step');
    console.log(step);
    return(
      <div>
        <h1>Welcome to the Tornadoes Website!</h1>
        <button onClick={()=> this.next(1)} > Step 1</button>
        <button onClick={()=> this.next(2)} > Step 2</button>
        {
          step === 1 ? <h1>Step 1 here</h1> : null
        }
        {
          step === 2 ? <h1>Step 2 here</h1> : null
        }
      </div>

    );
  }
}

// The Main component renders one of the three provided
// Routes (provided that one matches). Both the /roster
// and /schedule routes will match any pathname that starts
// with /roster or /schedule. The / route will only match
// when the pathname is exactly the string "/"
const Main = () => (
  <main>
    <Route exact path='/' component={Home}/>
  </main>
)

// The Header creates links that can be used to navigate
// between routes.
const Header = () => (
  <header>
    <nav>
      <ul>
        <li><Link to='/'>Home</Link></li>
        <li><Link to='/roster'>Roster</Link></li>
        <li><Link to='/schedule'>Schedule</Link></li>
      </ul>
    </nav>
  </header>
)

const App = () => (
  <div>
    <Header />
    <Main />
  </div>
)

// This demo uses a HashRouter instead of BrowserRouter
// because there is no server to match URLs
ReactDOM.render((
  <Router>
    <App />
  </Router>
), document.getElementById('root'))
2
  • When you define a param within your route you can reference it within your component via this.props.match.params.[your param name] so your-url/:id would be reference by this.props.match.params.id Commented Feb 26, 2018 at 16:53
  • @FrancisLeigh - what if my param is /register?step=1 ? this cannot work with :step... Commented Aug 30, 2018 at 20:30

2 Answers 2

3

React router from v4 onwards no longer gives you the query params in its location object. The reason being

There are a number of popular packages that do query string parsing/stringifying slightly differently, and each of these differences might be the "correct" way for some users and "incorrect" for others. If React Router picked the "right" one, it would only be right for some people. Then, it would need to add a way for other users to substitute in their preferred query parsing package. There is no internal use of the search string by React Router that requires it to parse the key-value pairs, so it doesn't have a need to pick which one of these should be "right".

Having included that, It would just make more sense to just parse location.search in your view components that are expecting a query object.

You can do this generically by overriding the withRouter from react-router like

customWithRouter.js

import { compose, withPropsOnChange } from 'recompose';
import { withRouter } from 'react-router';
import queryString from 'query-string';

const propsWithQuery = withPropsOnChange(
    ['location', 'match'],
    ({ location, match }) => {
        return {
            location: {
                ...location,
                query: queryString.parse(location.search)
            },
            match
        };
    }
);

export default compose(withRouter, propsWithQuery)

and wherever you need query string, you could simply use it like

import withRouter from 'path/to/customWithRouter.js';

class Home extends Component {
  constructor(props) {
    super(props);
    console.log('-!!!')
    this.state = {
      step: 1,
    }
    this.next = this.next.bind(this);
  }

  next(stepNumber=1) {
    this.props.history.push({
      pathname: `/adamchenwei/pen/YeJBxY?editors=0011&step=${stepNumber}`,
    });
  }
  componentDidUpdate(prevProps) {    // using componentDidUpdate because componentWillReceiveProps will be renamed to UNSAFE_componentWillReceiveProps from v16.3.0 and later removed
    const {query: { step } } = this.props.history.location;
    if(!_.isEqual(this.props.history.location.query, prevProps.history.location.query)) {
         this.setState({
             step
          })
    }
  }
  render() {
    console.log('render!!!');
    console.log(this);
    const {
      step
    } = this.state;
    console.log('---step');
    console.log(step);
    return(
      <div>
        <h1>Welcome to the Tornadoes Website!</h1>
        <button onClick={()=> this.next(1)} > Step 1</button>
        <button onClick={()=> this.next(2)} > Step 2</button>
        {
          step === 1 ? <h1>Step 1 here</h1> : null
        }
        {
          step === 2 ? <h1>Step 2 here</h1> : null
        }
      </div>

    );
  }
}

const HomeWithQuery = withRouter(Home);
Sign up to request clarification or add additional context in comments.

3 Comments

really interesting solution!
@Francis Leigh alternative works perfectly, yours though, very adhere to the question itself... I guess I will leave the best solution to be the most vote by other users! I feel both of your answers are really good.
Agreed, the answer above is a like for like solution for your scenario. Maybe take the answer above and think about the alternative approach i state below and we can all learn from it :-) hope you found your solution.
0

// A lose explanation. URL = localhost:3000/myRoute/2

<Router history={history}>
  <Route path="/myRoute/:id" component={App} />
</Router>

class App extends Component {
  render() {
    const { id } = this.props.match.params
    
    return (
      <div>
        {id === 2 ? <div>id is 2</div> : <div>id not 2</div>}
      </div>
    )
  }
}

2 Comments

love it! yea seems match.params is the easiest way to do instead of wrestling with query param, in this simple case, this works perfectly
How does this detect changes in the query params?

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.