react-afc
v3.4.1
Published
Allows you to significantly simplify the optimization of functional react components
Downloads
126
Readme
React Advanced Function Component (AFC)
Allows you to simplify optimization.
Full type support.
Table of contents
About the package
- Installation
- Why
- When to use
- What gives
- Performance
- Example
- Component structure
- State management
- Working with context
- Regular hooks in constructor
- Compatible with non-afc components
- Common errors
- API
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
-componentafc
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 calluseRedux
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 callbackuseOnRender
adds a call the passed callback (performance directly depends on the actions in it)useContext
adds one React.useContext calluseDispatch
/useActions
adds one ReactRedux.useDispatch calluseMemo
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