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-to-imperative

v0.2.0

Published

extract props from React elements

Downloads

5,254

Readme

react-to-imperative

Convert React (Native) components for imperative use.

Why?

React is great for declarative UI, but React Native comes with many apis that have to be used imperatively, even though they also describe UI and declarative approach would fit them well. For example: ActionSheetIOS, react-native-menu or the old PopupMenu.

This package serves as a bridge from declarative (React) to imperative by extracting props from React Elements.

Consider an example: In order to pass the right params to the imperative api of ActionSheet, you'll have to create array of string for the button titles. That can get quite tedious, for example:

const titles: Array<string> = [];
if (user.isAdmin) {
  titles.push('Delete');
}
if (user.isModerator) {
  titles.push('Ban');
}
if (user.isAuthor) {
  titles.push('Edit');
}

showActionSheetWithOptions(
  {
    options: labels,
  },
  (selectedIndex) => {
    if (buttonIndex === 0) {
      // delete
    } else if (buttonIndex === 1) {
      // ban
    } else if (buttonIndex === 2) {
      // edit
    }
  }
);

Notice how we have a mutated array, the not-too-explicit relation between the selectedIndex and the corresponding labels, and multiple ifs. We could refactor and make the code better, but it would still be imperative.

Let's take a look at how this could look when rendered with React Native. You'll notice that the code is cleaner: no need to mutate arrays, there is no hidden relationship between the selectedIndex and the corresponding labels, and there are no if statements.

const buttonElements = (
  <>
    {user.isAdmin && (
      <Button
        title="Delete"
        onPress={() => {
          // delete
        }}
      />
    )}
    {user.isModerator && (
      <Button
        title="Ban"
        onPress={() => {
          // ban
        }}
      />
    )}
    {user.isAuthor && (
      <Button
        title="Edit"
        onPress={() => {
          // edit
        }}
      />
    )}
  </>
);

This package aims to combine the two approaches, because the second one is cleaner and easier to read but we still need to use the imperative apis of React Native.

With react-to-imperative, the code would look like this:

const extractedProps = inspectElements(buttonElements, ({ props, type }) => {
  if (type === React.Fragment) {
    // if the element is a Fragment, return true to continue traversing deeper. The children (Buttons) will be covered by the next iteration.
    return true;
  }
  if (type === Button) {
    // we're interested in the titles and onPress handlers
    return { title: props.title, onPress: props.onPress };
  }
  return false; // ignore other components
});
const titles = extractedProps.map((props) => props.title);

showActionSheetWithOptions(
  {
    options: titles,
  },
  (selectedIndex) => {
    if (selectedIndex === undefined) {
      return;
    }
    const onPress = extractedProps[selectedIndex]?.onPress;
    onPress?.();
  }
);

Installation

yarn add react-to-imperative

Examples

There are many examples in the test suite. Please do see them to understand what the package does. There is also a simple example app.

Usage

import inspectElements from 'react-to-imperative';

inspectElements

has the following signature:

type inspectElements<I extends Props, O extends Props = I> = (
  inputReactElements: ReactNode | ReactNode[],
  opts:
    | PropsExtractor<I, O>
    | {
        propsExtractor: PropsExtractor<I, O>;
        maxDepth?: number;
        elementCreator?: ElementCreator;
      }
) => O[];

The first arguments is an (array of) React elements from which you want to extract props. The second argument is either a props-extracting function or an object which allows advanced control (all options described below). It returns an array of props extracted from React elements you provide.

propsExtractor

has the following signature:

type PropsExtractor<I extends Props, O extends Props = I> = (element: {
  props: I;
  type: ReactElement<I>['type'];
  depth: number;
}) => O | boolean;

This is a function that you implement. It accepts ({ props, type, depth }). props is the props object, type is a component type (div, View, React.Fragment ...), depth is the current depth in the explored React tree. You should return:

  • an object with props you want to extract if the provided React element is one you're interested in. Search will not carry on deeper in the React tree in this case (please open an issue if you need to change this).
  • true to denote that the provided React element is of no interested to you but you want the search to carry on one level deeper in the React tree (in the element's children)
  • false to denote you're not interested in exploring the provided part of React tree

maxDepth

Controls the maximum depth of the React tree that will be explored. Defaults to 3.

elementCreator

A function that, given props and type, creates a new React element. The default implementation is here. The default implementation simply calls type(props) and swallows any errors that might come if the limitations (see next paragraph) are violated.

Limitations

The inspectElements function only functions correctly for pure function components. It does not work if you use hooks or if you use class components - those will be ignored by default.

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT


Made with create-react-native-library