Search code examples
reactjscomponentsreact-props

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>
    )}
  </>
);

Solution

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