MakeStateful Its an "HOC" allows you to write functional components with ALL the functionalities of class Components without using hooks. *
npm install --save react-makestateful
or
yarn add react-makestateful
import makeStatefu from 'react-makestateful'
const increaseByOneOnClick = ({makeState, getProps, addContext, setHoc, on, setGetDerivedState, statefulProvider}) => {
// this needs to go first!
addContext('style', ContextProvider, shouldUpdateifContextChange1)
addContext('style2', ContextProvider2, shouldUpdateifContextChange2)
//=> ATTENTION, props from HOC are not available in the construct phase!
setHoc((comp) => myGoodOldHoc(comp))
// Behaves similar to use state
const [getNumber, setNumber] = makeState('number')(0)
// getProps returns the props at the give stage of the execution, here you will print the initial props
console.log('initial props =>' getProps() )
// on.<lifeCycleMethod> is a setter that adds the function to the lifecycle
on.didUpdate = (prevProps, scopedPrevState, snapshot) => {
console.log('did Something at every update')
}
// --
const onClick = () => {
console.log('current number:', getNumber())
setNumber(getNumber() + 1)
}
// add static getDerivedStateFrom(Props/Errors)
setGetDerivedState.fromProps = (props, state) => console.log('this function will be set as the static getDerivedStateFromProps')
setGetDerivedState.fromErrors = (error) => {console.log('this function will be set as the static getDerivedStateFromProps')}
// add some extra functionality like you would do with hooks
// stateful provider provides a scope to the state and lifecycle access of logic providers
const [getVal, setVal, onWhatever] = myLogicProvider(statefulProvider('logic1'))
const [getStuff, setStuff, onWhateverElse] = otherLogicProvider(statefulProvider('greatLogic'))
// this is the function you will actually render no props will be provided, use getProps instead
return () => {
return(
<div>
<div style={getProps().style2.propShowStyle}> {JSON.stringify(getProps())}<div>
<button onClick={onClick}> increase by 1 </button>
<div> current Count : {getNumber()}</div>
</div>
)
}
}
The basic component that should be passed to makeStateful
is a function that returns the component you actually want to render. Everything else before that will be executed only once (like the class constructor) when the component is instantiated.
Mostly because hooks are bad because:
- Black magic
- not easy to read
- lack the very clear lifecycle functionalities of class components
- you need to use stuff life useRef or useMemo to avoid costly function calls that are totally avoidable if using classed
Good because
- Let you write less code
- Make Isolating logic easier
This package tries to fix that, to bring all the functionalities of class components to functional ones. The goal of this package is to allow people to write functional components with less black magic that have all the advantages of classes, and the compactness of hooks.
You can create a state member by using makeState. Make state will return a getter and setter function for your state entry. Make function requires a name and a value. If no name is passed the name will be set as "default"
.
Notes:
- The name of the state needs to be unique
[stateGetter, stateSetter] = makeState(name)(value)
- name (String): a unique name within your component. If not defined it will default to 'default'
- value: the initial state value
- stateGetter (function): a function that returns the current value of your state
- stateSetter (function): a function to set the state
The state setter calls can be used in the same way as react's setState
stateSetter(updater, [callback])
- updater (any): if the update is a function it will be called as
updater(stae)
The only difference with react setState is that the updater will be already wrapped in a function call when calling react's setState
The props are accessible through the getProps()
method. When called within the component setup it will return the initial props. Calls within the render component will return the props at the specific render time.
There are 2 main caveats when calling getProps()
during set up:
- getProps will have access to context props ONLY AFTER addContext has been called
- getProps will NOT return props from HOC's during setup
You can add a context consumer by calling addContext
addContext(name, contextProvider, shouldUpdateForContext)
- name (string): a require and unique string name
- contextProvider (React.createContext)
- shouldUpdate (function): a function that will be called ONLY the context value of this specific context has changed. This function will not be called if the props have changed.
the signature of this function is:
(nextContext, currentContext) => boolean
Caveats:
- If nothing is returned we will try to re render the component will try to render by default
You can add further logic written as HOCs
setHoc((comp) => yourFavoriteHOC(comp))
Caveats:
- The HOC functionality will not be called until AFTER the component setup face, so don't expect to have access to any props that might come from HOC. EX: If your hoc pass a prop called banana, calling
getProps().banana
during the component setup will returnundefined
.
Makes stateful allows you to access ALL the lifecycle methods of React Class Components.
To add any of the available lifecycle methods in react you need to call on.<lifecyclemethod>
. Attention, I removed the word 'component' from lifecycle methods since it's superfluous.
on.shouldUpdate = Your_shouldUpdate_function
on.getSnapshotBeforeUpdate = Your_getSnapshotBeforeUpdate_function
on.didMount = Your_didMount_function
on.didUpdate = Your_didUpdate_function
on.willUnmount = Your_willUnmount_function
on.didCatch = Your_didCatch_function
To improve readability and reduce parentesys, the behavior of on.<lifecyclemethod>
does not set your method, but rather adds it to a list of methods to be executed during the specific lifecycle method. For example:
on.didMount = () => console.log('this will execute first on did mount')
on.didMount = () => console.log('this will execute second on did mount')
on.didMount = () => console.log('this will execute third on did mount')
The call other is guaranteed to be the same as the other in which the function were added.
By default shouldComponentUpdate
returns true. If you add several callbacks to be called on shouldComponentUpdate
, the first one to return a boolean will the return value of shouldComponentUpdate
. Example:
// nextProps = {num: 1}
on.shouldUpdate = (nextProps, nextState, snapshot) => {
console.log('You will see this log')
if nextProps.num ===100 return false
}
on.shouldUpdate = (nextProps, nextState, snapshot) => {
console.log('You will see this log')
if nextProps.num > 0 return nextProps.num > 5
}
on.shouldUpdate = (nextProps, nextState, snapshot) => {
console.log('You will not see this log unless num becomes smaller than 0')
if nextProps.num > 0 return nextProps.num > 5
}
To set static getDerivedStateFromProps
and static getDerivedStateFromError
you can call the setters: setGetDerivedState.fromProps
and setGetDerivedState.fromError
respectively.
setGetDerivedState.fromProps = yourGetDerivedStateFromProps
setGetDerivedState.fromErrors = yourGetDerivedStateFromError
Just like hooks it is super easy to add external logic. Moreover, in order to isolate the access to state, you can (but you don't have to) scope lifecycle methods and makeState. This will not only remove possible conflicts when setting state, but will also limit the access to the full state from logic providers during the calls to getSnapshotBeforeUpdate
, componentDidUpdate
, and shouldComponentUpdate
. Within your logic provider you can use makeState
, and the on
setters just like you would have done if you had a regular component.
{makeState, getProps, addContext, on} = statefulProvider(name)
if you set a ref on the component you will simply not receive it. If you want to pass a ref just use a props name other than "ref". For example
<MyMakeStatefullComponet ref={myRef}>
will not pass the reff sot that getPros().ref
will return undefined
, instead
<MyMakeStatefullComponet toPassRef={myRef}>
will give you access to toPassRef
so that getProps().toPassRef
will return myRef
getFullState
will return the full state
The only thing missing is the implementation of the static method displayName and defaultProps:
defaultProps
can be trivially implemented in your componentdisplayName
was just not worth implementing for my need
YES PLEASE! :)
MIT © pensarfeo