Below is the HOC and it is connected to redux store too. The WrappedComponent function is not fetching the redux state on change of storedata. What could be wrong here?
export function withCreateHOC<ChildProps>(
ChildComponent: ComponentType,
options: WithCreateButtonHOCOptions = {
title: 'Create',
},
) {
function WrappedComponent(props: any) {
const { createComponent, title } = options;
const [isOpen, setisOpen] = useState(false);
function onCreateClick() {
setisOpen(!isOpen);
Util.prevDefault(() => setisOpen(isOpen));
}
return (
<div>
<ChildComponent {...props} />
<div>
<Component.Button
key={'add'}
big={true}
round={true}
primary={true}
onClick={Util.prevDefault(onCreateClick)}
className={'float-right'}
tooltip={title}
>
<Component.Icon material={'add'} />
</Component.Button>
</div>
<OpenDrawerWithClose
open={isOpen}
title={title}
setisOpen={setisOpen}
createComponent={createComponent}
/>
</div>
);
}
function mapStateToProps(state: any) {
console.log('HOC mapStateToProps isOpen', state.isOpen);
return {
isOpen: state.isOpen,
};
}
// Redux connected;
return connect(mapStateToProps, {})(WrappedComponent);
}
Expecting isOpen to be used from ReduxStore and update the same with WrappedComponent here. By any chance this should be changed to class component?
The above HOC is used as:
export const Page = withCreateHOC(
PageItems,
{
createComponent: <SomeOtherComponent />,
title: 'Create',
},
);
You don't want isOpen
to be a local state in WrappedComponent
. The whole point of this HOC is to access isOpen
from your redux store. Note that nowhere in this code are you changing the value of your redux state. You want to ditch the local state, access isOpen
from redux, and dispatch
an action to change isOpen
in redux.
Additionally we've got to replace some of those any
s with actual types!
It seems a little suspect to me that you are passing a resolved JSX element rather than a callable component as createComponent
( <SomeOtherComponent />
vs SomeOtherComponent
), but whether that is correct or a mistake depends on what's in your OpenDrawerWithClose
component. I'm going to assume it's correct as written here.
There's nothing technically wrong with using connect
, but it feels kinda weird to use an HOC inside of an HOC so I am going to use the hooks useSelector
and useDispatch
instead.
We want to create a function that takes a component ComponentType<ChildProps>
and some options WithCreateButtonHOCOptions
. You are providing a default value for options.title
so we can make it optional. Is options.createComponent
optional or required?
interface WithCreateButtonHOCOptions {
title: string;
createComponent: React.ReactNode;
}
function withCreateHOC<ChildProps>(
ChildComponent: ComponentType<ChildProps>,
options: Partial<WithCreateButtonHOCOptions>
) {
We return a function that takes the same props, but without isOpen
or toggleOpen
, if those were properties of ChildProps
.
return function (props: Omit<ChildProps, 'isOpen' | 'toggleOpen'>) {
We need to set defaults for the options
in the destructuring step in order to set only one property.
const { createComponent, title = 'Create' } = options;
We access isOpen
from the redux state.
const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);
We create a callback that dispatches an action to redux -- you will need to handle this in your reducer. I am dispatching a raw action object {type: 'TOGGLE_OPEN'}
, but you could make an action creator function for this.
const dispatch = useDispatch();
const toggleOpen = () => {
dispatch({type: 'TOGGLE_OPEN'});
}
We will pass these two values isOpen
and toggleOpen
as props to ChildComponent
just in case it want to use them. But more importantly, we can use them as click handlers on your button and drawer components. (Note: it looks like drawer wants a prop setIsOpen
that takes a boolean
, so you may need to tweak this a bit. If the drawer is only shown when isOpen
is true
then just toggling should be fine).
function withCreateHOC<ChildProps>(
ChildComponent: ComponentType<ChildProps>,
options: Partial<WithCreateButtonHOCOptions>
) {
return function (props: Omit<ChildProps, 'isOpen' | 'toggleOpen'>) {
const { createComponent, title = 'Create' } = options;
const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);
const dispatch = useDispatch();
const toggleOpen = () => {
dispatch({ type: 'TOGGLE_OPEN' });
}
return (
<div>
<ChildComponent
{...props as ChildProps}
toggleOpen={toggleOpen}
isOpen={isOpen}
/>
<div>
<Component.Button
key={'add'}
big={true}
round={true}
primary={true}
onClick={toggleOpen}
className={'float-right'}
tooltip={title}
>
<Component.Icon material={'add'} />
</Component.Button>
</div>
<OpenDrawerWithClose
open={isOpen}
title={title}
setisOpen={toggleOpen}
createComponent={createComponent}
/>
</div>
);
}
}
This version is slightly better because it does not have the as ChildProps
assertion. I don't want to get too sidetracked into the "why" but basically we need to insist that if ChildProps
takes an isOpen
or toggleOpen
prop, that those props must have the same types as the ones that we are providing.
interface AddedProps {
isOpen: boolean;
toggleOpen: () => void;
}
function withCreateHOC<ChildProps>(
ChildComponent: ComponentType<Omit<ChildProps, keyof AddedProps> & AddedProps>,
options: Partial<WithCreateButtonHOCOptions>
) {
return function (props: Omit<ChildProps, keyof AddedProps>) {
const { createComponent, title = 'Create' } = options;
const isOpen = useSelector((state: { isOpen: boolean }) => state.isOpen);
const dispatch = useDispatch();
const toggleOpen = () => {
dispatch({ type: 'TOGGLE_OPEN' });
}
return (
<div>
<ChildComponent
{...props}
toggleOpen={toggleOpen}
isOpen={isOpen}
/>
<div>
<Component.Button
key={'add'}
big={true}
round={true}
primary={true}
onClick={toggleOpen}
className={'float-right'}
tooltip={title}
>
<Component.Icon material={'add'} />
</Component.Button>
</div>
<OpenDrawerWithClose
open={isOpen}
title={title}
setisOpen={toggleOpen}
createComponent={createComponent}
/>
</div>
);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.