Search code examples
javascriptreactjshigher-order-componentsrecomposereact-context

Use recompose to map props consumed from context


I have a functional component that required props height and width to be rendered. Let's call it PureSizableDiv:

const PureSizableDiv = ({ height, width }) =>
    <div style={{ height, width }}>I'm a div!</div>

I also have a React context called Size:

import React from 'react';
import { compose, fromRenderProps } from 'recompose';

export const { SizeProvider, SizeConsumer } = React.createContext({
  height: null,
  width: null,
});

Instead of manually creating a new HoC like this:

export const withSize = Component => (props) =>
  <SizeConsumer>
    {
      ({ height, width }) =>
        <Component
          height={height}
          width={width}
          {...props}
        />
    }
  </SizeConsumer>; 

I'd like to know if there's a shorter a cleaner way using recompose to do this. I tried

export const withSize = compose(
  fromRenderProps(SizeConsumer, ({ height, width }) => ({ height, width })),
);

But got the error Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.


Solution

  • I had connected my component, but I wasn't using properly the Context API. I fixed the code and it worked flawlessly using fromRenderProps from recompose. Working code:

    import React from 'react';
    import { compose, fromRenderProps } from 'recompose';
    
    const PureSizableDiv = ({ height, width }) =>
      <div style={{ height, width }}>I am a div!</div>;
    
    const SizeContext = React.createContext({
      height: null,
      width: null,
    });
    
    const withSize = compose(
      fromRenderProps(SizeContext.Consumer, ({ height, width }) => ({ height, width })),
    );
    
    // Usage
    const Wrapper = ({ children, height, width }) => 
      <SizeContext.Provider
        value={{
          height: height,
          width: width,
        }}
      >
        {children}
      </SizeContext.Provider>;
    
    const SizedDiv = withSize(PureSizableDiv);
    
    // Render components
    <Wrapper>
      <SizedDiv/>
    </Wrapper>