npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-curry-component

v1.1.1

Published

A react utility allowing easy creation of specialised components from existing components

Downloads

19

Readme

npm package npm downloads licence codecov CircleCI bundlephobia LGTM alerts LGTM grade

A small utility for easily creating specialised versions of existing React components, based on the functional programming concept of currying.

Motivation

Frequently in React we want to take an out-of-the-box component, or a generic one we've created ourselves, and use it in a specialised way. Let's say, for example, that our app has Call To Action buttons that use the Material UI Button component, but with the variant="raised" prop to give it more visual emphasis:

function MyAppForm({ formData, onSubmit }) {
    return (
        <Form>
            // form fields go here

            <Button variant="raised" onClick={ onSubmit }>Submit Form</Button>
        </Form>
    );
}

If we use these everywhere, it's a good idea to create a React component to represent this sort of use case, so that if we want to change the apperance or behaviour of all the buttons, we can just update our component. So we do

function CallToAction(props) {
    return <Button variant="raised" { ...props } />;
}

This is fine, but a little more boilerplatey than we need. We only need to supply three things to make this declaration:

  1. The component we want to specialise (Button in this case)
  2. The props we want to use to specialise it ({ variant: "raised" } here)
  3. The displayname of the component (CallToAction) - this is optional and could be autogenerated.

This suggests a possible simpler syntax for this sort of operation:

const CallToAction = curry(Button, { variant: "raised"}, "CallToAction");

…but we can be even more concise once we notice that React Component Type + props = React Element. So we can instead write simply

 const CallToAction = curry(<Button variant="raised" />, "CallToAction");

This is much more readable and allows lots of cool tricks, like dynamically creating your curry based on the output of other components.

Usage

Install with npm install react-curry-component, then use like this:

import React from 'react';
import { Button } from '@material-ui/core';
import curry from 'react-curry-component';

const CallToAction = curry(<Button variant="raised" />, "CallToAction");

React Displayname

The second argument is optional and defaults to a generated displayname of the form Curried(ComponentType):

// this will have the name `Curried(Button)`
const CallToAction = curry(<Button variant="raised" />);

Advanced techniques

Soft and Hard currying

Unlike currying in Functional Programming, there's a possibility that your curried component could get props that contradicts the ones you provide in your curry. In most cases we probably want this more "recent" value to take precedence - we call this a soft curry, and it's a lot like providing default props. However, in some cases, we may want our curried props to take precendence, in which case we call it a hard curry.

import React from 'react';
import { Button } from '@material-ui/core';
import { curryHard, currySoft } from 'react-curry-component';

const DefaultRaisedButton = currySoft(<Button variant="raised" />);
const buttonElement2 = <DefaultRaisedButton variant="outlined" />; // variant will be "outlined"

const AlwaysRaisedButton = curryHard(<Button variant="raised" />);
const buttonElement1 = <AlwaysRaisedButton variant="outlined" />; // variant will be "raised"

The default export for this package is currySoft.

Smart currying

For some React props, it makes more sense to do some sort of clever merge rather than just use either prop. For example, with className or style, it makes more sense to use both the curried value and the supplied value. You can use currySmart to do this:

import React from 'react';
import { currySmart } from 'react-curry-component';

const MyButton = currySmart(<button className="btn" />);
const buttonElement = <MyButton className="submit-btn" />; // className will be "btn submit-btn"

const MyTitle = currySmart(<h2 style={ { fontFamily: "Comic Sans" } } />);
const titleElement = <MyTitle style={ { padding: "8px" } }>Hurray for Curry!</MyTitle>; //will have the font family and the padding applied

In the case of event handlers, currySmart will ensure that both handlers are triggered:

import React from 'react';
import { currySmart } from 'react-curry-component';

const MyButton = currySmart(<button onClick={ sendButtonAnalytics } />);
const buttonElement = <MyButton onClick={ handleSubmit } />; // clicking on this will trigger analytics and submit behaviour

All other props will be handled using the normal currySoft behaviour. You can switch to curryHard default behaviour like this:

import React from 'react';
import { currySmart } from 'react-curry-component';

const MyButton = currySmart(<button onClick={ sendButtonAnalytics } />, true); // will prefer curried props

This also gives preference to curried style instructions if they conflict with the "normal" props.

Custom prop behaviour

In some specialised circumstances you may want to customised the currying behaviour for certain props, so you can supply a propsReducer that determines exactly how the curried props are combined with the element props.

import React from 'react';
import { Button } from '@material-ui/core';
import { curry } from 'react-curry-component';

function linkReducer(curriedProps, props) {
    const { href: curryHref, ...otherCurriedProps } = curriedProps;
    const { href, ...otherProps } = props;

    //only allow overwrite by HTTPS links
    const combinedHref = href.startsWith("https")) ? href : curryHref;

    //do a default soft curry on other props
    return { ...otherCurriedProps, ...otherProps, className: combinedClassName };
}

const Btn = curry(<Button className="btn" />, "Btn", classNameReducer);

// className will be "big btn"
const buttonElement = <Btn className="big" />; 

Bear in mind that your propsReducer function will be called on every render of every instance of your curried function, so avoid making it too intensive if you're planning to do lots of frequent updates on a large number of curried elements.