useState hook being called inside map function causing infinite loop - reactjs

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>

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

Calling props.myFunction() inside a function in react functional component

I am passing a function to a child component in React and it works perfectly when I call it directly in onClick of a button. But then when I move it into a separate function and call the function from onClick, it no longer works. I can verfity the function is being called with a log statement, but props.myFunction() never gets called. I've encountered this a few times now in React and it always confuses me.
I've looked at some other questions like this one, but its still not helping.
React: Can't call prop function when it is inside of another function?
This code works - it sets loggedIn to true in the parent when the button is clicked
export default function SignupModal(props) {
return (
<div class="main-block">
<button
className="create-account-button"
href="/"
onClick={props.setIsLoggedIn(true)}
>
Create Account
</button>
</div>
);
}
this code doesn't set loggedIn to true - but the function still gets called
export default function SignupModal(props) {
const createAccount = () => {
console.log("this gets logged");
//but this doesn't get called
props.setIsLoggedIn(true);
};
return (
<div class="main-block">
<button
className="create-account-button"
href="/"
onClick={createAccount}
>
Create Account
</button>
</div>
);
}
can anyone tell me why?
here is what I'm trying to do in the parent, maybe a little unorthodox to render routs like this but it's for a splash page - also as mentioned it works perfectly in onClick()
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<>
{isLoggedIn ? (
<>
<SearchBar onLocationChange={onLocationChange}></SearchBar>
<NavBar></NavBar>
<Switch>
<Route exact path="/quik">
<Pins
mapRef={mapRef}
pinnedLocationIds={pinnedLocationIds}
pinsRemaining={pinsRemaining}
pushPinnedLocation={pushPinnedLocation}
usePin={usePin}
mapCenter={mapCenter}
setMapCenter={setMapCenter}
matches={matches}
setMapZoom={setMapZoom}
mapZoom={mapZoom}
changeStatus={changeStatus}
/>
</Route>
<Route path="/potentials">
{/* dont forget,
the props for 'Potentials' must also pass
through 'potentials in the 'Pins' component! */}
<Potentials
pinsRemaining={pinsRemaining}
matches={matches}
changeStatus={changeStatus}
/>
</Route>
<Route path="/connects">
<Connects matches={matches} recentMatchId={recentMatchId} />
</Route>
<Route path="/profile" component={Profile} />
</Switch>
</>
) : (
<Route exact path="/quik">
<Landing setIsLoggedIn={() => setIsLoggedIn}></Landing>
</Route>
)}
</>
);
Just to demonstrate the difference between your two cases, take this example. When I call a function immediately in the returned JSX code, it fires as soon as the element mounts. However, in the second button, I have to click it before the logging will happen.
function App() {
return (
<div>
<button onClick={console.log("foo")}>Immediate log</button>
<button onClick={() => console.log("bar")}>Must click to log</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="main"></div>
Edit: In the case of your code, you're actually setting setIsLoggedIn to a function that returns a function when you pass it to your Landing component:
<Landing setIsLoggedIn={() => setIsLoggedIn} ></Landing>
What you want to do instead is this:
<Landing setIsLoggedIn={setIsLoggedIn} ></Landing>
Now setIsLoggedIn is just a function and your second example will work (your first example will fire immediately and not work how you intend).

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.

Can't redirect to search result page

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.

"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
>

Resources