bignose
bignose

Reputation: 32347

How to write ReactJS function component without using JSX?

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?

Upvotes: 0

Views: 1548

Answers (2)

nzjoel
nzjoel

Reputation: 1206

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)

Upvotes: 1

Alex Wayne
Alex Wayne

Reputation: 187144

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

Upvotes: 2

Related Questions