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

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).

Related

Handling events across different exports with React

So I wanted to create a popup div that would slide from the side when an object has been selected and then exit when the object is re selected. I also want to create an exit button that would also close the div. I can pretty much understand how to do this except that I want to reuse this div component which is why I have kept it as an export in a different javascript file. This is where the issue is as I am having trouble handling the events across the files.
Here is my code:
/*Popup div export*/
export default () => {
const [toggle, set] = useState(true);
const { xyz } = useSpring({
from: { xyz: [-1000, 0, 0] },
xyz: toggle ? [0, 0, 0] : [-1000, 0, 0]
});
return (
<a.div
style={{
transform: xyz.interpolate(
(x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`
)
}}
className="expand"
>
<Link to={link}>
<a.button>Next</a.button>
</Link>
<button onClick={() => set(!toggle)}>Exit</button>
</a.div>
);
};
/*This is where I am implementing the export*/
<Canvas>
<Suspense fallback={<Fallback />}>
<Cube position={[-1.2, 0, 0]} onClick={e => <Toggle />} /> <---/*Here is the click event where I call the div*/-->
<Cube position={[1.2, 0, 0]} />
</Suspense>
</Canvas>
);
}
I have tried changing the styling to make the display 'hidden' and 'block' but this doesn't work as it doesn't show the slide in animation it just pops up. Furthermore, If I try to manipulate this separately, for example, create a click event within the canvas to make the div appear with react-spring, if I try to use the exit button, the click event doesn't work anymore.
Here is my sandbox to show what is happening. : (p.s sorry if this all seems confusing)
The codes are within Page1.js and toggle.js
https://codesandbox.io/s/sad-goldberg-pmb2y?file=/src/toggle.js:250-326
Edit:
simpler sandbox visual:
https://codesandbox.io/s/happy-chatelet-vkzjq?file=/src/page2.js
Your example is a bit confusing to follow, a simpler reproduction would be nice. That said, if I understand the overall goal, I think you want to store some global state (perhaps in your App.js component) that has some sort of state about the sidebar being visible.
For example:
function App() {
const [sidebarVisible, setSidebarVisible] = React.useState(false)
const toggleSidebar = () => setSidebarVisible(!sidebarVisible)
return (
<Router>
<Switch>
<Route path="/page1">
<Page1 toggleSidebar={toggleSidebar} />
</Route>
<Route path="/page2">
<Page2 toggleSidebar={toggleSidebar} />
</Route>
<Route path="/">
<Start toggleSidebar={toggleSidebar} />
</Route>
</Switch>
</Router>
)
}
function Page1({ toggleSidebar }) {
return <Toggle toggleSidebar={toggleSidebar} />
}
function Toggle({ toggleSidebar }) {
return <button onClick={toggleSidebar}>Toggle</button>
}
This is just to give you ideas, you could of course pass the setSidebarVisible function or make another function that stores some sort of state about what should show on the sidebar.
You could also use something like Redux or React Context to pass down state/actions into your components.
Hope this helps somewhat 👍🏻

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>

How to redirect link from list of posts to valid post?

Problem is with routing on my page. I fetched some posts from my API and map them in React component. I get a list of posts (with title, description, etc.) Now I have a problem with routing from list to valid post.
I've tried to nest component in <BrowserRouter>, and used <Link to = {'/'+post.id}. However it applied proper url of page but I can't go to valid post. Next, I tried to Route into component, but it doesn't work.
In App.js
return (
<BrowserRouter>
<div className="App">
<Posts posts={currentPosts} foo={foo} />
</div>
</BrowserRouter>
);
};
In Posts.js
const Posts = ({ posts, foo}) => {
return (
<div>
{posts.map(post => (
<div key={post.id}>
<Link to={"/" + post.id}>
<Button>Read more</Button>
</Link>
</div>
</div>
))}
</div>
);
};
You have to include the postId in path parameter while defining a route. Try the following. Observe path value.
In your App.js define a Route,
<Route path="/:postId" component={Post} />
Hope it helps!

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

Trying to make a sub page (channel) in reactjs

I am trying to make the user able to create channels in my web app.
If you the see image below, there is a modal that take the input and use
that as the id of the page that will newly created.
My route is specified as:
require('./styles/main.scss');
render(
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route exact path="/RecTest" component={RecTest}/>
<Route exact path="/Example" component={Example}/>
<Route path="/channels/:id" component={ (props) => (<Channel {...props}/>)} />
</div>
</Router>,
// Define your router and replace <Home /> with it!
document.getElementById('app')
);
And the modal is coded as:
class MakeChannelModal extends Component {
constructor(props){
super(props);
console.log("constructor");
this.state={
channelName: ''
}
this.clickHandler = this.clickHandler.bind(this);
this.inputChangeHandler = this.inputChangeHandler.bind(this);
}
inputChangeHandler(event) {
this.setState({
channelName: event.target.value
})
}
clickHandler() {
<Link to={{ pathname: '/channels/' + this.state.channelName }}></Link>
}
render(){
return(
<div className="ui active modal">
<div className="header">Create a New Channel</div>
<div className="loginbody">
<div className="fillout">
<div className="channelName">
<span>Channel Name: </span>
<Input
onChange = { this.inputChangeHandler }
placeholder="Channel Name"
label = "Channel Name"
value = { this.state.channelName } />
</div>
</div>
<Button onClick = { this.clickHandler }>
SUBMIT
</Button>
</div>
</div>
)
}
}
export default MakeChannelModal
I was assuming this to take the channel name input and direct the page to a Channel component with the id of df.
My Channel component is just blank right now.
The thing is, if I click SUBMIT in the modal, it does not do anything.
Doesn't even show an error.
What am I doing wrong?
clickHandler() should be a function that changes pathname, making Router to draw another component, but it tries to draw React component by itself instead (not quite successfully though, because this function doesn't actually returns anything).
I believe you should either use props.history.push() in onClick(), or make <Button /> a <Link />.
Solution 1: (preferable, if you want a <Button>)
Replace your clickHandler() with this:
clickHandler() {
this.props.history.push('/channels/' + this.state.channelName);
}
Solution 2: (still would work)
Replace your <Button ... /> component with this:
<Link to={{ pathname: '/channels/' + this.state.channelName }}>
SUBMIT
</Link>
SO answer about history.push(): https://stackoverflow.com/a/43298961/6376451

Resources