react-use-recursive-hoc

Pending...

Usage no npm install needed!

<script type="module">
  import reactUseRecursiveHoc from 'https://cdn.skypack.dev/react-use-recursive-hoc';
</script>

README

React – useRecursiveHOC

Introduction

The creation of React was inspired by the idea of having a JavaScript library to facilitate the user interfaces development by separating components that worked individually, but together became an application. However, does React projects still following this principle? Is it true that creating individual components and putting them together we get an application? If this was true, it would take more time to develop those components than to make them work together. Developers spend too much time developing the structure of the applications, avoiding re-renders and bad practices, instead of simply putting components together and focusing on the logic of the product. So, this is the first inspiration for this dependency. In addition, passing properties of parent components to children over and over again until they all share the same "state" can be a nuisance; that's why state managers as Redux were created, but sometimes creating a reducer can be unnecessary or extreme for your approach, so we opted using react "contexts" or similar solutions, isn't it? Having another alternative for this was the second inspiration for this dependency. Finally, developers usually iterate arrays to render multiple components (the famous array.map(() => <Component />)), but not all dynamic components are lists, sometimes they are structures. Lists increase or decrease the number of their elements, but structures don't alter the quantity of elements, just alter their properties. Having a method to declare a “structure” using a JSON and turn it into an application dynamically was the third and final inspiration for this dependency. So, let´s see how these three situations were solved in just one light dependency.

Description

  • What is this?

“useRecursiveHOC” is a JavaScript dependency for React which purpose is to try another paradigm developing user interfaces just using individual components and a JSON structure that join them together.

  • What does this react dependency do?

It allows developing interfaces in a faster and easier way thinking in future support, maintenance and additions for the code.

  • How does this react dependency work?

As we mentioned earlier, this dependency proposes a new paradigm based on a JSON structure that represents how we shaped our interface and a series of individual components that finally render our structure. Internally, the JSON is processed by a function that recursively create Higher-Order Components one inside another until finally wrapping our interface in a final component... Following this paradigm, developers will notice how development becomes faster and more understandable.

  • What does the developer and the project gain by using it?

The developer will have the code more organized and the project could be finished faster while avoiding future maintenance or additions troubles. Also, if a new developer starts working on the project, it will be very easy for him to know how the interface is structured because he will only have to review the JSON that defines it and this will give him a general idea, too much faster than debugging parent components until reach the final children.

  • And finally, but the most important thing: What´s the difference between using this dependency and just map components based on a structure?

To understand this, please read the next section.

Learn how to use it

Using a JSON as the interface structure and representing it with individual components, we get our final result: A great component which contains the whole interface inside ready to receive properties to work.

But what's the difference between simply mapping a structure and using this dependency?

  • No more exponential renders:

When a developer maps an array by rendering components, the render just starts from the first element and ends with the last one, easy, right? But what if all those elements also map more elements within them and these others also do the same? It makes an exponential render.

There is a reason why this dependency is named “useRecursiveHOC”, because this is exactly how it works. A Higher Order Component wraps other components over and over again based on the structure defined by the JSON and at the end the result is just one component… So this final component is built only once. So, if this dependency uses recursion to wrap the provided structure using Higher Order Components each element must specify its type: If it is container or component; if it is container it must handle the children property because this will be the components the structure specifies it will contain (the container of the final component which wraps the entire interface is a Fragment).

So, keeping this in mind, the conclusion is that mapping elements within other elements makes an exponential render, but the "useRecursiveHOC" dependency processes the structure only once, providing a final component that contains all the components that make up our interface.

Code example:

const Interface = useRecursiveHOC({ structure: STRUCTURE, components: COMPONENTS })

return <Interface />
  • Don’t worry about passing unnecessary properties from parents to children.

If the result of using this dependency is a component that contains our interface, how can developers pass properties to each component inside individually? The JSON structure provided must have unique IDs for each element regardless of whether they are containers or components; so, all of these IDs are properties of the final component which wraps the interface. The developers just have to specify the ID of the component they want to send properties to and send them inside an object… Also, if they want to pass the same properties to all the components with the same name (for example, passing the same properties to all TextInputs inside), all they have to do is simply write the name of the component as the property and send the properties inside an object.

Note: Properties specified by ID overwrites the ones specified by component name.

Code example:

return (
    <Interface
        TextInput={{ theme: 'dark' }}  // Passing to all "TextInputs" the same properties.
        username={{ theme: 'light' }} // Passing properties to the "TextInput" with ID "username".
    />
)

Ok, now let's do some code.

  • Installation.

Run this in a terminal located on the root of your project:

$ npm i react-use-recursive-hoc –save

Example of use.

The following example is a simple one, however, more complex interfaces could be made with this dependency; look for more complete examples at the end of this document.

  • Define the structure (this example represents a short sign up form).
const SIGNUP_STRUCTURE = [
    {
        id: 'name',
        type: 'content',
        component: 'TextInput'
    },
    {
        id: 'row',
        component: 'Row',
        type: 'container',
        children: [
            {
                id: 'id',
                type: 'content',
                component: 'TextInput'
            },
            {
                id: 'email',
                type: 'content',
                component: 'TextInput'
            },
            {
                id: 'phone',
                type: 'content',
                component: 'TextInput'
            }
        ]
    },
    {
        id: 'direction',
        type: 'content',
        component: 'TextInput'
    }
]
  • Create your components (according with the previews structure, we should define a Row and TextInput components, let’s keep them simple).
// Styles don't really matter, we can imagine how could be.

const Row = ({ children }) => (
    <div className={style.row}>
        {children}
    </div>
)

const TextInput = ({ id, label, addResult }) => {
  const [value, setValue] = useState('')

  const onChange = useCallback(({ target: { value: newValue } }) => {
    setValue(newValue)
    addResult(id, newValue)
}, [id, addResult])

  return (
    <div className={style.input}>
      <span className={style.label}>
        {label}
      </span>
      <input
        type="text"
        value={value}
        onChange={onChange}
        className={style.textInput} />
    </div>
  )
}
  • Join them using the dependency and pass them props.
const COMPONENTS = { Row, TextInput }

const Form = ({ label, structure, questions }) => {
  const result = useRef({})
  const onSend = useCallback(() => (
    console.log('Form result: ', result.current)
  ), [])
  const addResult = useCallback((key, value) => {
    result.current = { ...result.current, [key]: value }
  }, [])

  // This useMemo is VERY important: As the structure is dynamic, the result of the useRecursiveHOC must be declared inside the component, this useMemo prevents the useRecursiveHOC from being rebuilt on each render.
  const Content = useMemo(() => useRecursiveHOC({
    structure,
    components: COMPONENTS
  }), [structure])

  return (
    <div className={style.form}>
      <span>{label}</span>
      <Content
        {...questions}
        TextInput={{ addResult }} />
      <div className={style.button} onClick={onSend}>
        <span>Send! (See the result in the console)</span>
      </div>
    </div>
  )
}
  • Finally, use it as a normal component.
// This object contains the label of each TextInput according to its ID.

const SIGNUP_QUESTIONS = {
  name: { label: 'Name' },
  email: { label: 'Email' },
  phone: { label: 'Phone' },
  id: { label: 'Identification' },
  direction: { label: 'Direction' }
}

const SignUpPage = () => (
  <div className={style.page}>
    {/* The rest of the interface. */}

    <Form
      structure={SIGNUP_STRUCTURE}
      questions={SIGNUP_QUESTIONS}
      label="Create an account and join our family"
    />
  </div>
)
  • As an extra step, add “React.memo” on your components and use the “should render” functionality to avoid unnecessary renders, this will optimize exponentially the use of this dependency.

Additional information.

  • Check some more detailed examples.
    • Forms example: Pending to upload.
    • Calendar example: Pending to upload.