Can't redirect to search result page - reactjs

I'm not good at English, so it might be hard to explain my intention.
I'm using React, React router, Apollo client,
In production build, When I click the search button, I can't redirect to render a result component because of error with error message
error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
First, I tried without useEffect. It occur error as described above.
Second, I tried with useEffect hook, that change redirect state to false. and it changes url. but doesn't render result component.
useEffect(() => {setRedirect(false)}, [redirect])
Finally I tried to make another react app to test this situation without apollo, and some custom components that are for clean code in the production build. I tried in development build. And it perfectly works without error
// search-bar.js
function SearchBar(props) {
// keywords for searching books
const [keywords, setKeywords] = useState('');
// search option
const [option, setOption] = useState('All');
// determine to redirect or not
const [redirect, setRedirect] = useState(false);
return (
<>
<div className="nav-search">
<form
role="search"
onSubmit={ e => {
e.preventDefault();
setRedirect(true);
}}
className="form-search"
>
<NotRequiredInput
type="search"
label="Search keywords: "
id="keywords"
value={keywords}
onChange={e => {
setKeywords(e.target.value);
}}
/>
// it map the list's items to option
<SelectBoxWithoutNone
label="Search option: "
id="search-option"
value={option}
onChange={e => {
setOption(e.target.value);
}}
list={['All', 'Title', 'Author']}
/>
<SubmitButton label="Search" />
</form>
</div>
{ redirect && <Redirect to={`/search?o=${option}&k=${keywords}`} /> }
</>
);
}
// app.js
<Query>
{({loading, data}) => {
if (loading)
return (
<Header>
<NavBar>
<main>
<Suspense>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/search" render={props => <SearchResult {...props}/>}
)
}}
// app.js looks like this with many route component.
// also using React.lazy for code splitting
// search-result.js
function SearchResult (props) {
const parsed = queryString.parse(props.location.search);
const option = parsed.o;
const keywords = parsed.k;
return (
<div className="div-search-result">
<p>{option}</p>
<p>{keywords}</p>
</div>
);
}
I expected that it renders result component (with or without hooks)
but as I described above, It occurred error
Update: When I tried to type some query parameter on url path directly, it works.

Related

React hooks : 'Cannot read property 'push' of undefined'

I'm trying to redirect my homepage to "/call" page based on a redux state. I can go to that component by typing the url manually but cant do it with a function. I tried "Redirect to", "history.push" but none of them worked for me. I cant solve the problem. Here is my code;
const Phone = ({ hidden, photoOpened, inCall }) => {
const dispatch = useDispatch(getContacts());
let history = useHistory();
useEffect(() => {
if (inCall.inCall) {
history.push('/call')
}
}, [inCall]);
useEffect(() => {
dispatch(getContacts());
}, [])
return (
<div hidden={process.env.NODE_ENV === 'development' ? !hidden : hidden} className={photoOpened ? "phone-container-rotate" : "phone-container"}>
<div className="coque" />
<Suspense fallback={<div className="animated fadeIn pt-1 text-center">Loading...</div>}>
<HashRouter basename="/phone">
<div
className="phone-content"
style={{ backgroundImage: `url(${background})` }}
>
<HeaderBar />
<BannerNotifications />
<Switch>
{routes.map((route, idx) => {
return route.component ? (
<Route
key={idx}
path={route.path}
exact={route.exact}
render={props => <route.component {...props} />}
/>
) : null;
})}
</Switch>
</div>
<Route component={BottomPhoneNavigator} />
</HashRouter>
</Suspense>
</div>
);
};
You could try and test for history existence of the history in your effect, also add it to dependency list
useEffect(() => {
if (history && inCall.inCall) {
history.push('/call')
}
}, [inCall, history]);
And important thing, your component using this hook must be within the Router, I see you'\re using HashRouter but as child of component using the hook.
Also if you're stuck to this structure, why wont you try to use Redirect within the Switch? This could work with some smart test so you wont end up in a loop:)
To use history your Phone component should be inside router component

React-Router Redirect only rendering part of the component

I am trying to implement a React search page that uses URL params for the search so users can share or save links to search results. I'm doing this by redirecting users who submit searches to the same page but with the search parameters in the URL. My problem is that the parent component isn't rendering the Search component when I redirect to it and I can't figure out why. I am not using Redux.
const Main = props => {
const [page, setPage] = useState(0)
return (
<React.Fragment>
<HeaderTabs page={page} setPage={setPage} />
{page === 0 ? <Search ip={props.ip} /> : null}
{page === 1 ? <Management ip={props.ip} /> : null}
</React.Fragment>
)
}
const Search = props => {
const [query, setQuery] = useState('')
const [field, setField] = useState('')
const [activeSearch, setActiveSearch] = useState(false)
if (activeSearch) return <Redirect push to={`/main?query=${query}&field=${field}`} />
else return (
// forms for inputting query and field, submission changes activeSearch to true
)
}
And to show where the routes are being declared:
class App extends React.Component {
constructor(props) {
super(props)
this.ip = // my domain
this.cookies = props.cookies
}
render() {
return (
<HashRouter>
<Route path='/' exact render={() => (<Splash cookies={this.cookies} ip={this.ip} />)} />
<Route path='/login' render={() => (<Login cookies={this.cookies} ip={this.ip} />)} />
<Route path='/signup' render={() => (<Signup ip={this.ip} />)} />
<Route path='/main' render={() => (<Main cookies={this.cookies} ip={this.ip} />)} />
</HashRouter>
)
}
}
The resulting behavior is strange: The params appear in the url as intended, HeaderTabs is rendered properly but nothing else, not Search nor Management. It should render Search since I checked the "page" state and it is 0 as expected. If I use the HeaderTabs interface to switch to Management and then back to Search, it does render. I simplified the code for this question a lot and if I left out something relevant please let me know and I will add it.
Why is Main not rendering Search on redirect?
I misunderstood how React-Router works. Search was maintaining its state after the redirect (I thought it would reset to its useState defaults when rerendering), so the activeSearch prop was always true and Search was constantly returning a redirect. I moved activeSearch to Main, passed it down as a prop, and used a setTimeout function in a useEffect hook in Main to change activeSearch back to false one second after it changes (enough time for the redirect to kick in):
useEffect(() => {
if (!activeSearch) return
window.setTimeout(() => setActiveSearch(false), 1000)
}, [activeSearch])
There's a one second delay in rendering Search but now it works!
Update: I've abandoned the use of the activeSearch hook and React-Router Redirects in favor of plain HTML links. Adding a useEffect with the dependency of window.location.href to trigger the search is a much better way of doing this whole thing. No more delay, no more occasional failures to render.

useState hook being called inside map function causing infinite loop

I'm currently building a dynamic form engine and I want to display results from the redux store when the Answer Summary component is rendered. The way I figured would be best to do this would be to having a 'complete' status and set it to true once the answerSummary component is loaded, but doing this within the map function does not work and throws the infinite loop react error.
Code is here:
function App() {
let [complete, setComplete] = useState(false);
return (
<div>
<h1>Form App Prototype</h1>
<Router>
<Switch>
{Object.values(Database.steps).map(step => {
step.name === 'answerSummary' ? setComplete(true) : setComplete(false);
return (
<Route exact path={`/${step.name}`} render={() =>
<Step step={step} />
}
/>
)
})}
</Switch>
</Router>
<br></br>
<div style={{display: complete? 'block' : 'none'}}><StoreVisual/></div>
</div>
);
}
export default App;
EDIT: I know you aren't able to setState inside the render - I've written it this way as a way to try and convey what I want to be able to do
My understanding of your problem is that you are trying to display results after the answer summary component is mounted.
You can achieve this by using the useEffect hook which runs when the component mounts. https://reactjs.org/docs/hooks-effect.html
If you only want to render <StoreVisual/> when they are on the last step it might be easier to set up a state hook for the index of the step people are on.
const [index, setIndex] = useState(0);
Every time someone progresses a step increment this value.
You would have to pass setIndex, or whatever you call your setter, into the Step component to do this.
Then you can render <StoreVisual/> with a conditional, like so...
<div>
<h1>Claimer Form App Prototype</h1>
<Router>
<Switch>
{Object.values(Database.steps).map(step =>
<Route exact
path={`/${step.name}`}
render={() => <Step step={step} /> }/> )}
</Switch>
</Router>
<br></br>
{Database.steps[index] === 'answerSummary' && <StoreVisual/>}
</div>
This approach also affords you a simple way to let people start in the middle of the form. Say you want to let people save half-finished forms in the future, you just change/update the default value of index hook.
Instead of running that code inline in your return, build the array in your function logic:
function App() {
let [complete, setComplete] = useState(false);
// build an array of Route components before rendering
// you should also add a unique key prop to each Route element
const routes = Object.values(Database.steps).map(step => {
step.name === 'answerSummary' ? setComplete(true) : setComplete(false);
return <Route exact path={`/${step.name}`} render={() => <Step step={step} />} />
})
return (
<div>
<h1>Claimer Form App Prototype</h1>
<Router>
<Switch>
// render the array
{[routes]}
</Switch>
</Router>
<br></br>
<div style={{display: complete? 'block' : 'none'}}><StoreVisual/></div>
</div>
);
}
export default App;
I don't think you need to call setComplete(false) based on this logic, so you should probably replace your ternary with an if statement:
if (step.name === 'answerSummary') {
setComplete(true)
}
and avoid making unnecessary function calls
You cannot set state inside render which cause infinite loop.[Whenever state is changed, component will re-render(calls render function)]
render()=>setState()=>render()=>setState().......infinite
WorkAround:
<div style={{display: this.props.location.pathname=='answerSummary'? 'block' : 'none'}}><StoreVisual/></div>

"Maximum update depth exceeded" in React functional component

I have a problem with a react functional component. When the react-apollo query is "on completed" executes a code to handle a token, and when finish send to another page (using history.push).
When run the code I get a infinite loop with this message: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops
I've tried comment history.push and the infinite loop does not happen again.
I comment all the logic in list-session component to avoid a recursive call, but it does not work (I checked thoroughly the code to confirm is not a recursive call).
I thing the problem is related with the lifecycle or with the history.push behavior
Ptt: I'm learning react on this moment
const { setUserInfo, setUserId } = useContext(userContext);
const { token } = match.params;
const onCompleted = data => {
if (data.validateUser.status) {
setUserId(token);
setUserInfo(data.validateUser.infoUser);
localStorage.setItem("token", token);
// history.push("/list-session");
} else {
history.push("/rare-page");
}
};
return (
<div className="Auth" data-testid="AuthPage">
<Query
query={VALIDATE_USER}
variables={{ userId: token }}
onCompleted={onCompleted}
>
{({ error }) => {
if (error) return <Error />;
return (
<>
<h2 data-testid="AuthState">Authenticating...</h2>
<div className="spinner-border text-info" role="status">
<span className="sr-only">Loading...</span>
</div>
</>
);
}}
</Query>
</div>
);
}
EDIT 1:
Added relevant code in the problem. I checked the component and no one of this make a recursive call, and no make a infinite setState call. The setState problem is the consecuence to another problem.
<AnimatedSwitch
atEnter={bounceTransition.atEnter}
atLeave={bounceTransition.atLeave}
atActive={bounceTransition.atActive}
mapStyles={mapStyles}
className="route-wrapper"
>
<Route exact path="/" component={NoCredentials} />
<Route exact path="/token/:token" component={Auth} />
<Route path="/list-session" component={Home} />
</AnimatedSwitch>
and this is the code in Home component:
const Home = () => {
const [activeTab, setActiveTab] = useState("activeSessions");
return (
<ValidateToken>
<Container className="bg-light">
<div className="Home">
<CreateSessionComponent />
<Nav tabs>
<NavItem>
<NavLink
className={classnames({
active: activeTab === "activeSessions"
})}
onClick={() => setActiveTab("activeSessions")}
>
Active Sessions
</NavLink>
</NavItem>
</Nav>
<TabContent activeTab={activeTab}>
<TabPane tabId="activeSessions">
{activeTab === "activeSessions" && (
<div>
<SessionsListComponent status="ACTIVE" />
</div>
)}
</TabPane>
</TabPane>
</TabContent>
</div>
</Container>
</ValidateToken>
);
};
export default Home;
The "maximum update depth exceeded" error occurs when you are trying to update a variable multiple times until the stack size exceeds. This usually happens when u are trying to set a state inside render() or calling a method inside render which in turn sets the state. So check if there is a setState() call inside render method of your code.
Also this might happen because of setting the localStorage variable inside render. Try to move it out of render. But if you anyways need it you can use the componentDidMount() hook. That way all your variables will be available to you when your component is rendered.
try this it may work i think.
<Query
query={VALIDATE_USER}
variables={{ userId: token }}
onCompleted={(e)=>onCompleted(e)} //onCompleted throws an event
>

When react perform componentDidMount and componentWillUnmount

I played with React for several years, still confused with mount/unmount mechanism in some case.
Since mount/unmount is the place to perform side effect, I do not want them to be invoked randomly. So I need to figure out how they work. As far as I can understand currently, when the virtual dom do not present in real dom, it tend to be unmounted. However, it seems not the whole story, and I can not reason it about
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
function App() {
const [count, setCount] = useState(0);
const Component = name => <TestMount name={name} />;
return (
<div className="App">
<h1>{count}</h1>
<Component name="one" />
{Component("two")}
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Component One is remount overtime the app render while Component two not?Why this happen?
Component is a new function each time App is rendered, so <Component name="one" /> is remounted each time too, they are considered different components.
The result of Component("two") call is <TestMount name={"two"} />, TestMount stays the same each time App is rendered, so it's not remounted.
Component is invalid component for what it's used for, to pass name string as name prop to TestMount component because name parameter is not a string but props object when Component is used like <Component name="one" />. name => <TestMount name={name} /> is render function, it's preferable to name it accordingly like renderTestMount for clarity because components aren't supposed to be called directly like Component("two").
In case a function is supposed be used as component or render function interchangeably, the signature should be changed to ({ name }) => <TestMount name={name} />.
The expected behaviour could be achieved for <Component name="one" /> by memoizing Component:
const Component = useCallback(({ name }) => <TestMount name={name} />, []);
But since Component doesn't depend on App scope, a correct way is to define it outside:
const Component = ({ name }) => <TestMount name={name} />;
function App() {...}
For instance, this is the reason React Router Route has separate component and render props for a component and render function. This allows to prevent unnecessary remounts for route components that need to be defined dynamically in current scope.
The key to such issue is the difference between the React Component and React element, put shortly React is smart with element not Component
Component vs element
Component is the template used to create element using <> operation. In my prospective, <> is pretty much like new operator in OOP world.
How React perform update between renders
Every time the render method(or functional component) is invoked. The new element is created using <>, however, React is smart enough to tell the element created between renders are actually the same one, i.e. it had been created before and can be reused as long as the element is created by the same Component
How about different Component
However when the identity of the Component using to generate element changes(Even if the Components look same), React believes something new come though, so it remove(unmount) the previous element and add(mount) the new one. Thus componentDidMount or componentWillUnmount is invoked.
How is confusing
Think we got a Component and when we generate element using <Component /> react can tell the same elements because they are generated by the same Component
However, HOCComponent=()=><Component />; element= <HOCComponent />, every time element is generated, it used a different Component. it is actually a HOC constructed dynamically. Because the HOC is created dynamically inside render function, it can be confusing on the first glance.
Is that true
I never found any offical document about the idea above.However the code below is enough to prove
function TestMount(props) {
useEffect(() => {
console.log("componentDidMount", props.name);
return () => {
console.log("componentWillUnount", props.name);
};
}, []);
return <h1>Test content {" " + JSON.stringify(props.name)}</h1>;
}
function Update({ click }) {
return <button onClick={click}>Update</button>;
}
let _Component;
function cacheComponent(C) {
if (C && !_Component) {
_Component = C;
}
return _Component || null;
}
const CacheComponent2 = once(({ name }) => <TestMount name={name} />, []);
function App() {
const [count, setCount] = useState(0);
// can be used as a HOC of TestMount or a plain function returnnung a react element
const Component = name => <TestMount name={name} />;
const CacheComponent1 = cacheComponent(Component);
const CacheComponent3 = useCallback(
({ name }) => <TestMount name={name} />,
[]
);
return (
<div className="App">
<h1>{count}</h1>
{/* used as HOC */}
<Component name="one" />
{/* used as function returnning the element */}
{Component("two")}
<CacheComponent1 name="three" />
<CacheComponent2 name="four" />
<CacheComponent3 name="five" />
<Update click={() => setCount(x => x + 1)} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Also the code above provide three different ways to avoid the undesired mount/unmount. All the solutions are cache the identity of the HOC somehow

Resources