history.push is doing an extra redirect, and ignoring the search parameters, if the user is already on the redirect page

Viewed 1218

I have a simple Navbar with a Form within it:

const NavBar = () => {
   let history = useHistory()
   ...
   ...

   return (
     ...
     <Form inline onSubmit={handleSubmit}>
         <InputGroup style={{width: "90%"}}>
             <Form.Control id="navbar-search" placeholder="Pesquise" size="sm"/>
             <Form.Control as="select" size="sm">
                 <option>Ações</option>
                 <option>Declarações</option>
             </Form.Control>
             <InputGroup.Append  size="sm">
                 <Button size="sm" type="submit" variant="outline-secondary">
                     <SearchIcon fontSize="small" />
                 </Button>
             </InputGroup.Append>
         </InputGroup>
     </Form>
     ...
   )

handleSubit is suppose to redirect (using history.push) to a path that I'm going to use the input value as a search parameter. I'm using react-router-dom.

const handleSubmit = (e) => {
    let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes"
    history.push({
        pathname: `/${baseEndpoint}/query`,
        search: `?description=${e.target[0].value}`, 
    })
}

Everything looks good except if the user is in the page the handleSubmit is going to redirect, ie, at /${baseEndpoint}/query.

If the user is in this page, history goes to /${baseEndpoint}/query?description=${e.target[0].value} and re-renders automatically to /${baseEndpoint}/query?.

I've also tried using history.replace, but it didn't work.

const handleSubmit = (e) => {
    let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes"
    let url = `/${baseEndpoint}/query`
    if(history.location.pathname === url) {
        history.replace({
            pathname: url,
            search: `?description=${e.target[0].value}`, 
        })
        return
    }
    history.push({
        pathname: url,
        search: `?description=${e.target[0].value}`, 
    })
}

What is causing this behaviour? What am I doing wrong?

Thanks a lot!

(EDIT) My Switch and Routes:

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


class Wrapper extends Component {
  render() {
    return (
      <>
        <Container className="main">
          <MetaData />
          <Navbar />
          <this.props.component {...this.props} />
          <br />
          <br />
          <Footer />
        </Container>
      </>
    )
  }
}


export default function App() {
  return (
    <Router>
      <Switch>
        <Route exact path="/"
          render={props => (
            <Wrapper {...props} component={Home} />
          )}
        />
        <Route
          exact path="/actions"
          key="action-home"
          render={props => (
            <Wrapper
              {...props}
              image={bozoAction}
              baseEndpoint="actions"
              component={EntityHome}
            />
          )}
        />
        <Route
          path="/actions/query"
          key="action-query"
          render={props => (
            <Wrapper
              {...props}
              image={bozoAction}
              baseEndpoint="actions"
              component={EntityQuery}
            />
          )}
        />
        <Route
          path="/actions/:id(\d+)"
          key="action-entity"
          render={props => (
            <Wrapper
              {...props}
              image={bozoAction}
              baseEndpoint="actions"
              component={Entity}
            />
          )}
        />
        <Route
          exact path="/quotes"
          key="quote-home"
          render={props => (
            <Wrapper
              {...props}
              image={bozoQuote}
              baseEndpoint="quotes"
              component={EntityHome}
            />
          )}
        />
        <Route
          path="/quotes/query"
          key="quote-query"
          render={props => (
            <Wrapper
              {...props}
              image={bozoQuote}
              baseEndpoint="quotes"
              component={EntityQuery}
            />
          )}
        />
        <Route
          path="/quotes/:id(\d+)"
          key="quote-entity"
          render={props => (
            <Wrapper
              {...props}
              image={bozoQuote}
              baseEndpoint="quotes"
              component={Entity}
            />
          )}
        />
        ...
      </Switch>
    </Router>
  )
}

Sandbox: https://codesandbox.io/s/0y8cm

Form some reason the sandbox is not redirecting and ignoring the search parameters, but we can evaluate the error doing the following: use the navbar to search one of the entities. Check the form (clicking in "Mais filtros") and see the provided query param at the input. Do another search in the navbar to the same entity. Check the form and see no initial values.

3 Answers

What's causing the behavior is the form submission event, it refreshes the page resulting in the loss of the query params, the solution to this is e.preventDefault() when you submit the form :

const handleSubmit = (e) => {

  e.preventDefault(); // prevent the page refresh

  let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes";
  history.push({
    pathname: `/${baseEndpoint}/query`,
    search: `?description=${e.target[0].value}`
  });
};

You can add history.go() if you absolutely have to refresh the page, but i wouldn't recommend that, the goal is to let the child components know that the search has changed,

The way your EntityQuery and QueryForm is set up makes it impossible for the child components to know if there is a change, so you need to fix that,

You shouldn't set the initial state value from the props in the constructor as it's anti-pattern , instead, have the initial values empty and use life cycle methods to update them ( componentDidMount and componentDidUpdate )

EntityQuery

constructor(props) {
    super(props);
    this.state = {
      error: null,
      isLoaded: true,
      hasMore: false,
      searchParams: {
        page: 1,
        tags: "",
        description: ""
      },
      entities: []
    }
  }

  updateStateValues = () => {
    const _initialTag = this.props.location.search.includes("tags")
      ? this.props.location.search.split("?tags=")[1]
      : "";
    const _initialText = this.props.location.search.includes("description")
      ? this.props.location.search.split("?description=")[1]
      : "";

    this.setState({
      searchParams: {
        page: 1,
        tags: _initialTag,
        description: _initialText
      }
    })
  }

  componentDidMount() {
    this.updateStateValues()
  }

  componentDidUpdate(prevProps) {
    // add checks for other potential props that you need to update if they change
    if (prevProps.location.search.split("?description=")[1] !== this.props.location.search.split("?description=")[1])
      this.updateStateValues()
  }

And pass the values from the state to the child :

<QueryForm
    baseEndpoint={this.props.baseEndpoint}
    tagsInitialValues={this.state.searchParams.tags}
    textInitialValue={this.state.searchParams.description}
    setSearchParams={this.setSearchParams}
 />

Updated CodePen

EDIT:

Fllowing up the OP's comment and the updated SandBox, there's another issue regarding Formik where the initialValues do not update when the props change, see : https://github.com/formium/formik/issues/811

The suggested solution of adding enableReinitialize didn't work, so, to force the component to update, you can use a key that changes when the url changes, in this case, use this.props.textInitialValue :

QueryForm :

<Formik
  key={this.props.textInitialValue}
  ...

Try adding e.prevenDefault() at the start of the handleSubmit function.

Maybe <form> submission is causing a page reload and hence, removing the search parameters.

Try to prevent the default behavior of the form on submission by changing your handleSubmit() handler to:

const handleSubmit = (e) => {
   e.preventDefault()
    let baseEndpoint = e.target[1].value === "Ações" ? "actions" : "quotes"
    history.push({
        pathname: `/${baseEndpoint}/query`,
        search: `?description=${e.target[0].value}`, 
    })
}

Let me if it works *

Related