0

How do I write a React component as a function correctly for React Hooks?

When I attempt to write my component as a function:

"use strict"

function App() {
    const [foo, setFoo] = React.useState()

    const element = React.createElement()
    return element
}

function appRender() {
    const appContainerElement = document.getElementById('app-container')
    const appComponent = App()
    ReactDOM.render(appComponent, appContainerElement)
}

The React library emits an error:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

That component is pretty simple, it's hard to see how it fails to be a function component. The index document directly loads the React and React DOM libraries with the same version. I believe the component is “following the Rules of Hooks”.

Note that this is using React without JSX, as the ReactJS documentation describes. The React component creates an element and returns it.


The index document loads the React libraries and invokes the ‘appRender’ function after all modules are loaded:

<!DOCTYPE html>
<html lang="en" class="no-js">
    <head>
        <meta charset="UTF-8" />
        <script src="https://unpkg.com/react@17/umd/react.development.js"
                crossorigin></script>
        <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
                crossorigin></script>
        <script src="/js/app.js"></script>
        <script defer>
            window.addEventListener('load', ((event) => {
                appRender()
            }))
        </script>
    </head>
    <body>
        <div id="app-container">
            <div id="app" class="initial-placeholder">
                <p class="loading-status">Preparing the app…</p>
            </div><!-- #app -->
        </div><!-- #app-container -->
    </body>
</html>

This correctly invokes the ‘appRender’ function.

What is the error? How do I correct this component so I can use React Hooks in it?

2
  • 2
    React components return JSX. What are you returning from App? You also don't invoke your component (e.g. <App /> instead of App()), you pass the JSX to the React framework and it handles invoking and computing the renderable content. JSX is syntactic sugar around createElement. reactjs.org/docs/react-without-jsx.html Commented Jul 14, 2021 at 6:29
  • As you correctly note, JSX is syntactic sugar and is entirely optional: as described at that documentation article a React component does not return JSX, but an element. I've described this more in the question, thank you. Commented Jul 14, 2021 at 22:34

2 Answers 2

2

You are breaking this rule of hooks:

Only Call Hooks from React Functions

What makes a function a "react" function is that it's called synchronously as react is rendering. So directly executing your function like App() executes your functional component, but react is not currently rendering. So you get an error because there is no current react context for the hooks to "hook" into.

To solve this you need to either execute your component as JSX, which does the magic for you:

ReactDOM.render(<App />, appContainerElement)

Or if you really want to avoid JSX at all costs, then do it manually:

const appComponent = React.createElement(App)
ReactDOM.render(appComponent, appContainerElement)

Either way you have to tell React to render your component. The component is not smart enough to completely render itself in this way.

Working example on stack blitz

Sign up to request clarification or add additional context in comments.

Comments

1

Instead of manually calling your App function you need to pass your component to React and let react invoke your function.

Instead of:

const appComponent = App()
ReactDOM.render(appComponent, appContainerElement)

Try:

const reactElement = React.createElement(App, {}, null);
ReactDOM.render(reactElement, appContainerElement)

2 Comments

This doesn't appear to answer the question; you're advising to entirely remove the React component? Can you update this answer to describe the purpose?
@bignose My answer is the same as the accepted - with less detail. The key point is that you need to let React.createElement call your component instead of manually calling it yourself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.