skip to Main Content

I’m used to using htm, a JSX-like syntax via tagged templates, which allows me to write anonymous components like this:

let loc;
render(
    html`
        <div>
            <${() => {
                loc = useLocation();
            }} />
        </div>
    `,
    scratch
);

// test value of `loc`

However, I can’t figure out what the equivalent JSX syntax is. I use this a lot in tests, cuts down on the number of irrelevant components I need to write just to extract a hook value out of a test render.

2

Answers


  1. JSX doesn’t have that feature — presumably because it’s a bad idea outside of test code (you’d get a new component-type on every render, which means that every render has to completely unmount the old component and create a new one) — but you can use createElement directly:

    let loc;
    
    render(
        <div>{
           createElement(() => { loc = useLocation() }, null)
        }</div>,
        scratch
    );
    
    // test value of `loc`
    
    Login or Signup to reply.
  2. // How to define an inline component with JSX?

    I will answer your question in two parts:

    1. How to render an "anonymous" component

    In React/JSX pretty much any function that returns JSX can be rendered as a component (note that the function name will usually start with a capital letter).

    And since you can write functions pretty much anywhere, you can define a functional component directly inside each test, much like you have in your example.

    So the direct translation of your example into JSX would be:

    it('test useLocation()', () => {
        let loc;
        function MyComponent(){
            loc = useLocation();
            return (<div>
                {loc}
            </div>);
        }
        render(<MyComponent />, scratch);
    
        // test value of `loc`
        expect(loc).toEqual(/* your expected value here */);
    });
    

    The use of the loc variable is probably not the recommended way of testing hooks, but it works…

    You could then split out the rendering part to its own function if you wanted truly anonymous components for your test:

    function doRender(Component){
        return render(<Component />, scratch);
    }
    
    it('test useLocation()', () => {
        let loc;
        doRender(() => {
            loc = useLocation();
            return (<div>
                {loc}
            </div>);
        });
    
        // test value of `loc`
        expect(loc).toEqual(/* your expected value here */);
    });
    

    2. How to test hooks

    I would recommend looking at a dedicated library like @testing-library/react (formerly react-testing-library).

    This has a bunch of helper functions to cut down on boilerplate code.

    Specifically, the renderHook() function would be worth looking at – it allows you to run hooks without having to render them inside a component.

    Here’s an example of how you could convert your example to use renderHook() instead:

    import * as React from 'react';
    import { renderHook } from '@testing-library/react';
    import { useLocation } from 'react-router-dom';
    
    it('renders hook', () => {
        const { result } = renderHook(() => useLocation());
        expect(result.current).toEqual(/* your expected value here */);
    });
    

    Final note – the doRender() example above is a really dumb version of what the @testing-library/react render() function lets you do, so I would recommend using that.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search