HOC React and Redux : Cannot access 'mapStateToProps' before initialization - javascript

I want make higher-order component / HOC wrapped with redux
I tried something like that :
export const Button = connect(mapStateToProps,mapDispatchToProps)(
() => {
return(
<Link
to="#"
className="btn-enter"
onClick={() => this.props.handleShare()}>
Undang Teman
</Link>
);
});
const mapStateToProps = (state) => {
}
const mapDispatchToProps = (dispatch) => {
return {
handleShare: () => dispatch({type: ActionType.HANDLE_SHARE_MODAL})
}
}
But when i run this code i got message error :
ReferenceError: Cannot access 'mapStateToProps' before initialization

Also if you don't need mapStateToProps you can leave it as null
// Better to declare component above and then pass it to connect HOC
const ButtonComponent = (props) => {
return(
<Link
to="#"
className="btn-enter"
// ButtonComponent is a functional component
// and to you props you get them from params
onClick={() => props.handleShare()}>
Undang Teman
</Link>
);
}
const mapDispatchToProps = (dispatch) => {
return {
handleShare: () => dispatch({type: ActionType.HANDLE_SHARE_MODAL})
}
}
export const Button = connect(null, mapDispatchToProps)(ButtonComponent);

as it says you should first initialize function to access it later so it must be
const mapStateToProps = (state) => {
}
const mapDispatchToProps = (dispatch) => {
return {
handleShare: () => dispatch({type: ActionType.HANDLE_SHARE_MODAL})
}
}
export const Button = connect(mapStateToProps,mapDispatchToProps)(
() => {
return(
<Link
to="#"
className="btn-enter"
onClick={() => this.props.handleShare()}>
Undang Teman
</Link>
);
});

Related

Passing the callback function

I have a Modelmenu that is nested in the parent component, it implements the function of opening a modal window by click. Also in this parent component there is a child component to which you need to bind the function of opening a modal window by one more element. I can do this if all the logic is in the parent component, but this is a bad practice, since I will have to duplicate the code on each page in this way. How can I do this? I'm sorry, I'm quite new, I can't understand the callback.
Parent:
const Home: NextPage = () => {
const handleCallback = (handleOpen: any) => {
}
return (
<>
<ModalMenu parentCallback={handleCallback}/>
<Slider handleCallback={handleCallback}/>
</>
)
}
Modal:
export const ModalMenu: FC = (props) => {
const [play, setPlay] = useState<boolean>(false)
const handleOpen = () => {
props.parentCallback(setPlay(!play))
};
const handleClose = () => {
setPlay(false)
setPlay(!play)
};
return
}
Child:
export const Slider: FC= (props) => {
return (
<>
<Image nClick={props.handleCallback}/>
</>
I did as advised in the comments using hook, it works fine, maybe it will be useful to someone. Custom hook is really convenient
export const useModal = () => {
const [play, setPlay] = useState<boolean>(false)
const handleOpen = () => {
setPlay(!play)
};
const handleClose = () => {
setPlay(false)
setPlay(!play)
};
return {
play, handleOpen, handleClose
}
}
If you're looking to pass down values and/or functions from a parent to two or more children, I think it's better to just have the values and functions in the parent
Parent :
const Home: NextPage = () => {
const [play, setPlay] = useState<boolean>(false)
const handleOpen = () => {
setPlay(prev => !prev)
};
const handleClose = () => {
setPlay(false)
setPlay(!play)
};
return (
<>
<ModalMenu handleOpen={handleOpen} handleClose={handleClose} play={play}/>
<MainSlider <ModalMenu handleOpen={handleOpen} handleClose={handleClose} play={play}/>
</>
)
}
if you want to pass an interface to the props in the children for typescript your interface will look something like this
interface iProps {
play : boolean;
handleOpen : () => void;
handleClose : () => void;
}
export const ModalMenu: FC = (props):iProps => {
// you can easily access all you want
const {handleClose, handleOpen, play} = props
return
}
export const Slider: FC= (props): iProps => {
const {handleClose, handleOpen, play} = props
return (
<>
<Image onClick={handleOpen}/>
</>

Is it possible to expose a function defined within a React function component to be called in other components?

I'm refactoring some old code for an alert widget and am abstracting it into its own component that uses DOM portals and conditional rendering. I want to keep as much of the work inside of this component as I possibly can, so ideally I'd love to be able to expose the Alert component itself as well as a function defined inside of that component triggers the render state and style animations so that no outside state management is required. Something like this is what I'm looking to do:
import Alert, { renderAlert } from '../Alert'
const CopyButton = () => (
<>
<Alert text="Text copied!" />
<button onClick={() => renderAlert()}>Copy Your Text</button>
</>
)
Here's what I currently have for the Alert component - right now it takes in a state variable from outside that just flips when the button is clicked and triggers the useEffect inside of the Alert to trigger the renderAlert function. I'd love to just expose renderAlert directly from the component so I can call it without the additional state variable like above.
const Alert = ({ label, color, stateTrigger }) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger])
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
};
export default Alert;
Though it's not common to "reach" into other components and invoke functions, React does allow a "backdoor" to do so.
useImperativeHandle
React.forwardRef
The idea is to expose out the renderAlert function imperatively via the React ref system.
Example:
import { forwardRef, useImperativeHandle } from 'react';
const Alert = forwardRef(({ label, color, stateTrigger }, ref) => {
const { Alert__Container, Alert, open } = styles;
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
const portalElement = document.getElementById('portal');
const renderAlert = (): void => {
setAlertRendered(false);
setAlertVisible(false);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
useEffect(() => {
renderAlert();
}, [stateTrigger]);
useImperativeHandle(ref, () => ({
renderAlert,
}));
const ele = (
<div className={Alert__Container}>
{ alertRendered && (
<div className={`${Alert} ${alertVisible ? open : ''}`}>
<DesignLibAlert label={label} color={color}/>
</div>
)}
</div>
);
return portalElement
? ReactDOM.createPortal(ele, portalElement) : null;
});
export default Alert;
...
import { useRef } from 'react';
import Alert from '../Alert'
const CopyButton = () => {
const ref = useRef();
const clickHandler = () => {
ref.current?.renderAlert();
};
return (
<>
<Alert ref={ref} text="Text copied!" />
<button onClick={clickHandler}>Copy Your Text</button>
</>
)
};
A more React-way to accomplish this might be to abstract the Alert state into an AlertProvider that renders the portal and handles the rendering of the alert and provides the renderAlert function via the context.
Example:
import { createContext, useContext, useState } from "react";
interface I_Alert {
renderAlert: (text: string) => void;
}
const AlertContext = createContext<I_Alert>({
renderAlert: () => {}
});
const useAlert = () => useContext(AlertContext);
const AlertProvider = ({ children }: { children: React.ReactElement }) => {
const [text, setText] = useState<string>("");
const [alertVisible, setAlertVisible] = useState<boolean>(false);
const [alertRendered, setAlertRendered] = useState<boolean>(false);
...
const renderAlert = (text: string): void => {
setAlertRendered(false);
setAlertVisible(false);
setText(text);
setTimeout(() => {
setAlertVisible(true);
}, 5);
setAlertRendered(true);
setTimeout(() => {
setTimeout(() => {
setAlertRendered(false);
}, 251);
setAlertVisible(false);
}, 3000);
};
const ele = <div>{alertRendered && <div> ..... </div>}</div>;
return (
<AlertContext.Provider value={{ renderAlert }}>
{children}
// ... portal ...
</AlertContext.Provider>
);
};
...
const CopyButton = () => {
const { renderAlert } = useAlert();
const clickHandler = () => {
renderAlert("Text copied!");
};
return (
<>
<button onClick={clickHandler}>Copy Your Text</button>
</>
);
};
...
function App() {
return (
<AlertProvider>
...
<div className="App">
...
<CopyButton />
...
</div>
...
</AlertProvider>
);
}

How to access nodes when your component is passed to connect?

I'm trying to access the MetaDetails component in the test case but unable to do so since its wrapped with the Provider. The goal is to access the PrintLabelButton component on click of which I need to mock 'handlePrintLabelButtonClick' function.
Utils.js
export const handlePrintLabelButtonClick = (
e,
rmaNumber,
labelUrl,
getReturnLabel
) => {
const rmaList = [];
e.preventDefault();
if (!labelUrl) {
const container = { rmaNo: "" };
container.rmaNo = rmaNumber;
rmaList.push(container);
getReturnLabel(rmaList);
} else {
window.open(labelUrl, "_blank");
}
};
Page.js
import {
filterReturnLabelResponse,
handlePrintLabelButtonClick
} from "../../utils/common-util";
import { PrintLabelButton } from "./printLabel";
Const MetaDetails = () => {
//a lot of code
{showPrintLabelButton && (
<PrintLabelButton
onClickHandle={e =>
handlePrintLabelButtonClick(
e,
rmaNumber,
labelUrl,
getReturnLabel
)
}
url={labelUrl}
target="_blank"
type="printLabel"
text={intl.formatMessage(messages.printLabelText)}
/>
)}
// again a lot of code
}
const mapStateToProps = state => ({
// some code
});
const mapDispatchToProps = dispatch => ({
// some code
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(injectIntl(MetaDetails));
PrintLabelButton.js
export const PrintLabelButton = ({
target,
url,
type,
text,
onClickHandle
}: PrintLabelButtonProps) => {
return (
<ButtonWrapper>
<AnchorWrapper
href={url}
target={target}
type={type}
>
<DefaultButton
tabIndex="0"
onClick={onClickHandle}
data-test="print-label-button"
>
{text}
</DefaultButton>
</AnchorWrapper>
</ButtonWrapper>
);
};
What I've tried so far
MetaDetails.test.js
test("Returns Component Whats next button click", () => {
const wrapper = mount(
<Provider store={store}>
<MetaDetails/>
</Provider>
);
const middle = wrapper.find(MetaDetails);
const component = wrapper.find(`[data-test="print-label-button"]`).hostNodes();
component.simulate("click");
expect(mockSetModal).toHaveBeenCalled();
});
Can someone please tell me how can I access nodes when your component is surrounded by the Provider.
Note: I am using react/#emotion

Create HOC for outsideClick function, using Redux

I have a React HOC that hides a flyover/popup/dropdown, whenever I click outside the referenced component. It works perfectly fine when using local state. My HOC looks like this:
export default function withClickOutside(WrappedComponent) {
const Component = props => {
const [open, setOpen] = useState(false);
const ref = useRef();
useEffect(() => {
const handleClickOutside = event => {
if (ref?.current && !ref.current.contains(event.target)) {
setOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => setOpen(false);
}, [ref]);
return <WrappedComponent open={open} setOpen={setOpen} ref={ref} {...props} />;
};
return Component;
}
When in use I just wrap up the required component with the HOC function
const TestComponent = () => {
const ref = useRef();
return <Wrapper ref={ref} />;
}
export default withClickOutside(TestComponent);
But I have some flyover containers that are managed from Redux when they are shown, or when they are hidden. When the flyover is shown, I want to have the same behavior, by clicking outside the referenced component to hide it right away. Here's a example of a flyover:
const { leftFlyoverOpen } = useSelector(({ toggles }) => toggles);
return (
<div>
<Wrapper>
<LeftFlyoverToggle
onClick={() => dispatch({ type: 'LEFT_FLYOVER_OPEN' })}
>
...
</Wrapper>
{leftFlyoverOpen && <LeftFlyover />}
{rightFlyoverOpen && <RightFlyover />}
</div>
);
Flyover component looks pretty straightforward:
const LefFlyover = () => {
return <div>...</div>;
};
export default LefFlyover;
Question: How can I modify the above HOC to handle Redux based flyovers/popup/dropdown?
Ideally I would like to handle both ways in one HOC, but it's fine if the examples will be only for Redux solution
You have a few options here. Personally, I don't like to use HOC's anymore. Especially in combination with functional components.
One possible solution would be to create a generic useOnClickOutside hook which accepts a callback. This enables you to dispatch an action by using the useDispatch hook inside the component.
export default function useOnClickOutside(callback) {
const [element, setElement] = useState(null);
useEffect(() => {
const handleClickOutside = event => {
if (element && !element.contains(event.target)) {
callback();
}
};
if (element) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [element, callback]);
return setElement;
}
function LeftFlyOver() {
const { leftFlyoverOpen } = useSelector(({ toggles }) => toggles);
const dispatch = useDispatch();
const setElement = useOnClickOutside(() => {
dispatch({ type: 'LEFT_FLYOVER_CLOSE' });
});
return (
<Dialog open={leftFlyoverOpen} ref={ref => setElement(ref)}>
...
</Dialog>
)
}

Dispatch prop as argument to store from React Redux Container

I want to dispatch a component prop as payload to my store from the click event of a sub-component:
const Aircraft = ({ ident, type, base, handleOnClick }) => (
<div className="item" onClick={handleOnClick}>
<i className="large plane middle aligned icon"></i>
<div className="content">
<div className="header">{ident}</div>
<div className="description">{type}</div>
<div className="description">{base}</div>
</div>
</div>
);
So I want to dispatch ident to handleOnClick.
The click event is passed as a prop from the parent component and mapped from a redux container.
const AircraftList = ({ aircraftList, setCurrentAircraft }) => (
<div className="ui relaxed celled list">
{aircraftList.map((el, index) => (
<Aircraft key={index} {...el} handleOnClick={setCurrentAircraft} />
))}
</div>
);
Redux container:
import { connect } from 'react-redux';
import AircraftList from '../../components/AircraftList/AircraftList';
import { setCurrentAircraft } from '../../actions/actions';
const mapStateToProps = state => {
return {
aircraftList: state.aircraft,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setCurrentAircraft: (e) => {
dispatch(setCurrentAircraft(ident));
}
};
};
const AircraftListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(AircraftList);
export default AircraftListContainer;
I am not sure how to pass the ident from the sub component to the dispatch and subsequently update the store?
If you need the event object inside setCurrentAircraft add the ident as a second argument, else keep ident as the only argument.
const mapDispatchToProps = (dispatch) => {
return {
setCurrentAircraft: (e, ident) => { // If `event` needed, else `(ident)`
dispatch(setCurrentAircraft(ident));
}
};
};
Then wrap the handleOnClick on Aircraft inside another function to give it ident as an argument.
const Aircraft = ({ ident, type, base, handleOnClick }) => (
<div className="item" onClick={(e) => handleOnClick(e, ident)}> // or just handleOnClick(ident)
{/* ... */}
</div>
);
Try this. You need to bind your click handler and dispatch the action from that. You also need to assign the ident value somewhere in you child component, so you can access it.
Redux Container
import { connect } from 'react-redux';
import AircraftList from '../../components/AircraftList/AircraftList';
import { setCurrentAircraft } from '../../actions/actions';
class AircraftListContainer extends React.Component {
constructor(props) {
super(props)
}
handleClick = (e) => {
const ident = e.currentTarget.getAttribute("data-ident")
this.props.setCurrentAircraft(ident)
}
render () {
return (<AircraftList aircraftlist={this.props.aircraftlist} handleClick=
{this.handleClick} />)
}
}
const mapStateToProps = state => {
return {
aircraftList: state.aircraft,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setCurrentAircraft: (ident) => {
dispatch(setCurrentAircraft(ident));
}
};
};
export default connect(mapStateToProps,
mapDispatchToProps)(AircraftListContainer);
AircraftList
const AircraftList = ({ aircraftList, handleClick }) => (
<div className="ui relaxed celled list">
{aircraftList.map((el, index) => (
<Aircraft key={index} {...el} handleOnClick={handleClick} />
))}
</div>
);
Aircraft
const Aircraft = ({ ident, type, base, handleOnClick }) => (
<div className="item" onClick={handleOnClick} data-ident={ident}>
<i className="large plane middle aligned icon"></i>
<div className="content">
<div className="header">{ident}</div>
<div className="description">{type}</div>
<div className="description">{base}</div>
</div>
</div>
);

Categories