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-afc

v3.4.1

Published

Allows you to significantly simplify the optimization of functional react components

Downloads

96

Readme

React Advanced Function Component (AFC)

Allows you to simplify optimization.

Full type support.

Table of contents

About the package

Installation

npm i react-afc

When to use

When you need to optimize a component by reducing the rerender of child components.

Why

In order not to write unnecessary useMemo, useCallback and useRef.

What gives

Allows you to reduce the number of hook calls (which affects both readability and optimization), and also not to worry about an array of dependencies.

Performance

The library is optimized as much as possible.

afcMemo returns the memo-component
afc returns a regular component

Each render uses one useRef hook, and the props is updated (excluding the first render).

Calling the following methods adds logic that is used during each render:

  • useObjectState / useReactive / useState adds one React.useState call
  • useRedux adds ReactRedux.useSelector calls depending on the passed object (one key - one hook call)
  • useOnDestroy / useOnMount / useOnDraw / useEffect / useLayoutEffect adds one React.useEffect call with the passed callback
  • useOnRender adds a call the passed callback (performance directly depends on the actions in it)
  • useContext adds one React.useContext call
  • useDispatch / useActions adds one ReactRedux.useDispatch call
  • useMemo adds one React.useMemo call

Note: All of them except useRedux / useOnRender / useMemo / useContext adds one hook call regardless of the number of its calls

Each of the methods can be called an unlimited number of times, but only within the constructor and in functions called from it (exclution - methods from react-afc/compatible).

Example

See the description below.

import { useState, afcMemo, useActions, useRedux, $ } from 'react-afc'
import { selectName, actions } from './store'

function Component(props) {
  const [getMultiplier, setMultiplier] = useState(2)
  const [getNumber, setNumber] = useState(5)

  const store = useRedux({
    name: selectName
  })

  const { changeName } = useActions(actions)

  function onChangeMult(event) {
    setMultiplier(+event.currentTarget.value)
  }
  
  function onChangeName(event) {
    changeName(event.currentTarget.value)
  }

  function calcValue() {
    return getMultiplier() * getNumber()
  }

  return () => <>
    <h1>Advanced function component</h1>
    <input value={store.name} onChange={onChangeName} />
    <input value={getMultiplier()} onChange={onChangeMult} />
    <input value={getNumber()} onChange={$(e => setNumber(+e.currentTarget.value))} />
    <p>Calculated: {calcValue()}</p>
    <p>Hi, {name}!</p>
  </>
}

export default afcMemo(Component)

Component structure

import { afc } from 'react-afc'

function Component(props) {
  // The body of the "constructor".
  // Is called once (before the first render).
  // Common hooks must be wrapped or in 'useOnRender'.

  return () => {
    // Render function, as in a regular component.
    // Every render is called.
    return (
      <div>content</div>
    )
  }
}

export default afc(Component)

State management

To work with the state, use useReactive / useObjectState / useState

import { afc, useObjectState, useReactive, useState } from 'react-afc'

function Component(props) {
  // useObjectState
  const { state, setName, setSurname } = useObjectState({
    name: 'Name',
    surname: 'Surname'
  })

  function changeState1() {
    setName('New name')
    setSurname('New surname')
  }

  // useReactive
  const reactive = useReactive({
    name: 'Name',
    surname: 'Surname'
  })

  function changeState2() {
    reactive.name = 'New name'
    reactive.surname = 'New surname'
  }

  // useState
  const [getName, setName2] = useState('Name')
  const [getSurname, setSurname2] = useState('Surname')

  function changeState4() {
    setName2('New name')
    setSurname2('New surname')
  }

  return () => {/*...*/}
}

export default afc(Component)

To work with Redux use useRedux and useDispatch / useActions

import { afc, useRedux, useDispatch, useActions, $ } from 'react-afc'
import { actions } from './store'
import { changeCount, selectCount } from './countSlice'

function Component(props) {
  const store = useRedux({
    name: s => s.name.current,
    count: selectCount
  })

  function greet() {
    return `Hi, ${store.name}!`
  }

  const dispatch = useDispatch()
  
  // Alternative
  const { delCount } = useActions(actions)

  return () => <>
    <input onChange={$(e => dispatch(changeCount(+e.target.value)))}/>
    <button onClick={$(() => delCount())}>
      Delete counter
    </button>
  </>
}

export default afc(Component)

Working with Context

To use the context, import the useContext.

Returns contextGetter, not the context itself.

import { afc, useContext } from 'react-afc'
import CountContext from './CountContext'

function Component(props) {
  const getCount = useContext(CountContext)

  function calculate() {
    return getCount() * 5
  }

  return () => (
    <p>Calculated: {calculate()}</p>
  )
}

export default afc(Component)

Using regular hooks in the body of the "constructor"

import { afc, useOnRender } from 'react-afc'
import { commonHook } from './hooks'

function Component() {
  let exampleVar = null

  useOnRender(() => {
    exampleVar = commonHook()
  })

  return () => {
    // OR
    // exampleVar = commonHook()
    return (
      <p>Variable: {exampleVar}</p>
    )
  }
}

export default afc(Component)

Or use wrapStaticHook / wrapDynamicHook

import { afc, useOnRender, useForceUpdate, wrapStaticHook, wrapDynamicHook } from 'react-afc'

function commonHook(number) {
  // any React common hooks
}

// If the result of the hook does not change
const staticHook = wrapStaticHook(commonHook)
// Else
const dynamicHook = wrapDynamicHook(commonHook)

function Component() {
  let number = 5

  const staticResult = staticHook(number)
  const getDynamicResult = dynamicHook(() => [number])
  const forceUpdate = useForceUpdate()

  return () => {
    number++

    return <>
      <p>Static result: {staticResult}</p>
      <p>Dynamic result: {getDynamicResult()}</p>
      <button onClick={forceUpdate}>
        Force update
      </button>
    </>
  }
}

export default afc(Component)

useOnRender is called immediately and before each render (so as not to break hooks)

import { afc, useOnRender } from 'react-afc'

function Component(props) {
  console.log('Constructor start')
  useOnRender(() => {
    console.log('onRender')
  })
  console.log('After onRender')

  return () => (
    <p>onRender</p>
  )
}

export default afc(Component)

In this example, the console output will be:

Constructor start
onRender
After onRender

And before each next render it will be output to the console

onRender

Compatible with non-afc components

To use the same code in regular and afc components, use the methods from react-afc/compatible.

They have slightly less performance.

When called in an afc component, they work like normal methods for 'constructor'. When called in a regular component, they use adapted versions that do not cause errors and do the same job as in 'constructor'.

Note: Use compatible methods only in reused functions. In other cases, the fastest option will be the usual methods from react-afc.

import { afc, wrapDynamicHook } from 'react-afc'
import { useOnMount, useOnDestroy } from 'react-afc/compatible'
import { externalCommonHook } from './hooks'

const afcHook = wrapDynamicHook(externalCommonHook)

function handleDocumentClick(callback) {
  useOnMount(() => {
    document.addEventListener('click', callback)
  })
  useOnDestroy(() => {
    document.removeEventListener('click', callback)
  })
}

const AFCComponent = afc(props => {
  handleDocumentClick(() => {
    // any actions
  })
  afcHook(5)

  return () => (
    <p>afc component</p>
  )
})

function CommonComponent(props) {
  // Will not cause errors
  handleDocumentClick(() => {
    // any actions
  })
  externalCommonHook(5)

  return (
    <p>common component</p>
  )
}

For single hard calculations use useOnceCreated

Common errors

Unpacking at the declaration will break the updating of the props: name and age will be the same every render

import { afc } from 'react-afc'

                    // Error !!!
function Component({ name, age }) {
  // ...
}

export default afc(Component)

Unpacking state, props or reduxState directly in the constructor body will freeze these variables: name, age and surname will not change between renders.

The exclusion is the case when the received fields do not change during the life of the component
Unpacking in render function or handlers does not have such a problem

import { afc, useReactive, useRedux } from 'react-afc'

function Component(props) {
  const state = useReactive({
    name: 'Aleksandr',
    age: 20
  })

  const store = useRedux({
    count: s => s.count.value
  })

  const { name, age } = state // Error, freeze !!!
  const { count } = store
  const { surname } = props

  function onClick() {
    const { name, age } = state // Right, always relevant
    const { count } = store
    const { surname } = props
  }

  return () => (
    <button onClick={onClick}>
      Click me
    </button>
  )
}

export default afc(Component)

It is forbidden to use regular hooks in the constructor without the useOnRender wrapper.

Since the "constructor" is called once, the call of the usual hooks in it will not be repeated in the render, which will cause the hooks to break and the application to crash.

The contents of useOnRender are called every render, which ensures that the hooks work correctly.

Note: Use useOnRender only when there is no other way.

import { useEffect as reactUseEffect } from 'react'
import { afc, useOnRender } from 'react-afc'

function Component(props) {
  reactUseEffect(/*...*/) // Error !!!

  useOnRender(() => {
    reactUseEffect(/*...*/) // Right
  })

  return () => {
    reactUseEffect(/*...*/) // Right

    return (
      <p>common hooks</p>
    )
  }
}

export default afc(Component)

API

See Wiki