Multiple Modals with React Hooks - reactjs

I´m building some modals for fun but I can´t make it work for multiple modals.
useModal hook
import { useState } from 'react';
const useModal = () => {
const [isShowing, setIsShowing] = useState(false);
const toggle = () => {
setIsShowing(!isShowing);
}
return {
isShowing,
toggle,
}
};
export default useModal;
Modal component
import React, { useEffect } from 'react';
const Modal = (props) => {
const { toggle, isShowing, children } = props;
useEffect(() => {
const handleEsc = (event) => {
if (event.key === 'Escape') {
toggle()
}
};
if (isShowing) {
window.addEventListener('keydown', handleEsc);
}
return () => window.removeEventListener('keydown', handleEsc);
}, [isShowing, toggle]);
if (!isShowing) {
return null;
}
return (
<div className="modal">
<button onClick={ toggle } >close</button>
{ children }
</div>
)
}
export default Modal
in my Main component
If I do this the only modal in the page works fine
const { isShowing, toggle } = useModal();
...
<Modal isShowing={ isShowing } toggle={ toggle }>first modal</Modal>
but when I try to add another one it doesn´t work. it doesn´t open any modal
const { isShowingModal1, toggleModal1 } = useModal();
const { isShowingModal2, toggleModal2 } = useModal();
...
<Modal isShowing={ isShowingModal1 } toggle={ toggleModal1 }>first modal</Modal>
<Modal isShowing={ isShowingModal2 } toggle={ toggleModal2 }>second modal</Modal>
what I´m doing wrong? thank you
if you want to check it out please go to https://codesandbox.io/s/hopeful-cannon-guptw?fontsize=14&hidenavigation=1&theme=dark

Try that:
const useModal = () => {
const [isShowing, setIsShowing] = useState(false);
const toggle = () => {
setIsShowing(!isShowing);
};
return [isShowing, toggle];
};
then:
export default function App() {
const [isShowing, toggle] = useModal();
const [isShowingModal1, toggleModal1] = useModal();
const [isShowingModal2, toggleModal2] = useModal();

if you have multiple modal then dynamically inject <div> inside the page body and map modal to it.
to create div use,
var divTag = document.createElement("div");
divTag.setAttribute('id', 'modal');
document.getElementsByTagName('body')[0].appendChild(divTag);
document.getElementById('modal').innerHTML = modalHtML;
This should work.

Related

How to mock custom hook which returns scrolled element's data with jest and enzyme

In my react component I am adding a css class to a div when another div get scrolled :
import useOnScroll from '../utils';
const MyComponent = (props) => {
const scrollableContainerRef = useRef();
const { scrollTop: containerScrollTop } = useOnScroll(scrollableContainerRef);
...
return (
<div className="commonBtmSheet">
<div data-test="btmSheet" className="OverlayWrap">
<div data-test="btmSheet-header" className={`header ${containerScrollTop > 0 ? 'scrolled' : '' }`}>
...
<div data-test="btmSheet-body" ref={scrollableContainerRef} className="OverlayOuter">
...
useOnScroll Hook:
import { useEffect, useRef, useState } from "react";
function useOnScroll(ElRef) {
const prevScrollTop = useRef();
const [scrollData, setScrollData] = useState({});
const handleScroll = (e) => {
const el = e.target;
const { scrollTop } = el;
let direction = "down";
if (prevScrollTop.current > scrollTop) {
direction = "up";
}
setScrollData((prev) => ({
...prev,
scrollTop,
direction,
}));
};
useEffect(() => {
const elm = ElRef.current;
if (elm) {
elm.addEventListener("scroll", handleScroll);
}
return () => {
if (elm) {
elm.removeEventListener("scroll", handleScroll);
}
};
}, [ElRef.current]);
return scrollData;
}
export default useOnScroll;

React: Is there a way to access component state from function in another file?

I've a react component which includes a large function that updates the component state, the function is large so I want to move it to a separate file and export it in the react component. But I don't find anyway to access the component state if I move the function to its own file.
Is there anyway to do this ?
example:
component.tsx
import { myFunction } from './function.ts'
const [toggle, setToggle] = useState(false)
const my_component = () => {
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = () => {
// do something that updates `toggle`
}
you can do the logic apart from the component and return the result to the component. have a look at the code below.
https://codesandbox.io/s/hopeful-dubinsky-930p7?file=/src/App.js
This is just a raw example of what you can do with custom state hooks (reference: https://dev.to/spukas/react-hooks-creating-custom-state-hook-300c)
import React from 'react';
export function useMyFunction(value) {
const [toggle, setToggle] = React.useState(value || false);
const myFunction = () => {
// do something that updates `toggle` with setToggle(...)
}
return { toggle, myFunction };
}
import { useMyFunction } from './function.ts'
const my_component = () => {
const [toggle, myFunction] = useMyFunction(false)
return (
<div>
<button onClick={myFunction}>Run function</button>
</div>
)
}
export default my_component
This can be achieved by 2 different ways one using HOC components and another just by using functions.
Approach 1: Using HOC
handler.js
const withHandlers = (WrappedComponent) => {
class HandlerComponent extends Component {
state = {toggle:false};
myFunction = () => {
//Do your update here
}
render() {
return <WrappedComponent
toggle={this.state.toggle
myFunction={this.myFunction}
/>
}
};
my_component.js
const my_component = (props) => {
return (
<div>
<button onClick={props.myFunction}>Run function</button>
</div>
}
export default withHandlers(my_component);
Approach 2: Using Functions
handler.js
export const myFunction(toggle) => {
return !toggle; //return the changed value
}
my_component.js
const my_component = () => {
const [toggle, setToggle] = useState(false);
const myFunction = () => {
setToggle(handler.myFunction); //the state will be passed as a parameter by default
};
return(
<div>
<button onClick={myFunction}>Run function</button>
</div>
);
};
For the toggle to work, it must be passed to the function as a props then for update it used state management (redux or react context).
The best solution is to define the toggle in the function itself and pass it a Boolean props to control it.
import { myFunction } from './function.ts'
const my_component = () => {
return (
<div>
<button onClick={myFunction(false)}>Run function</button>
</div>
)
}
export default my_component
function.ts
export const myFunction = (props) => {
const [toggle, setToggle] = useState(props || false);
// your codes
};

Bind a function in parent to a mouse event in child in react (hooks)

I am trying to learn how to bind functions in reactjs to events set in child. The one canvas (canvas2) has a mouse move event and the other canvas (canvas1) shall receive data from that event when there is any (=mouse moves).
But none of the functions are called and console.log doesn't show up.
Parent App.js
const [move, setMove] = useState();
const handleMove = (e) => {
console.log(e); //shows nothing
setMove(e.target);
}
return(
<>
<Canvas1 move={move} />
<Canvas2 handleMove={handleMove} />
</>
);
Canvas1:
useEffect(() => {
console.log(props.move); //shows nothing
}, [props.move]); //when props.move has new data, I wand this to trigger
Canvas2:
const [canvas, setCanvas] = useState();
useEffect(() => {
if(!canvas) {
setCanvas(initCanvas());
return;
}
canvas.on("mouse:move", props.handleMove); //bind mouse event to parent function
}, [canvas]);
return(
<canvas></canvas>
);
Canvas1
Could you try to change move and then check the message. (Anything has to be shown up in the console in this way)
useEffect(() => {
console.log(props.move); //shows nothing
}, [props.move]); //when props.move has new data, I wand this to trigger
Canvas2
Could you try this below? and then let me know what message comes up.
const [canvas, setCanvas] = useState();
useEffect(() => {
console.log(canvas);
if(!canvas) {
setCanvas(initCanvas()); // This set up a size of the canvas, doesn't this?
return;
}
// canvas.on("mouse:move", props.handleMove); //bind mouse event to parent function
canvas.addEventListener("mousemove", props.handleMove);
}, [canvas]);
return(
<canvas></canvas>
);
I'm not sure about this... If you get messages from the code, let me know. I'm trying to find out what problems are
=========================
-EDITED-
[App.js]
import React, { useState } from "react";
import Canvas1 from "./components/Canvas1";
import Canvas2 from "./components/Canvas2";
function App() {
const [move, setMove] = useState();
const handleMove = e => {
setMove(e.target);
};
return (
<>
<Canvas1 move={move} />
<Canvas2 handleMove={handleMove} />
</>
);
}
export default App;
[Canvas1]
import React, { useEffect } from "react";
const Canvas1 = props => {
useEffect(() => {
console.log(`props.move:${props.move}`);
}, [props.move]);
return <></>;
};
export default Canvas1;
[Canvas2]
import React, { useEffect, useState, useRef } from "react";
const initCanvas = () => {
const newCanvas = document.createElement("canvas");
newCanvas.setAttribute("width", "500px");
newCanvas.setAttribute("height", "500px");
return newCanvas;
};
const Canvas2 = props => {
const [canvas, setCanvas] = useState();
const canvasRef = useRef();
useEffect(() => {
if (!canvas) {
setCanvas(initCanvas());
return;
}
}, [canvas]);
useEffect(() => {
canvasRef.current.addEventListener("mousemove", props.handleMove); // It doens't work
}, [props.handleMove]);
return <canvas ref={canvasRef} />;
};
export default Canvas2;

OutSider click event using React Hook

I am trying to develop a click event handler function for the DOM element so that when I click to the outside of the div the corresponding dom element close. I have been trying the following code but I am getting the error of TypeError: node.contains is not a function. Not sure if I am doing it correctly with the react hook. Any kinds of help would be really appreciated.
import React, { useState, useEffect, useRef } from 'react';
const OutSiderClickComponent = () => {
const [visible, setVisible] = useState(false);
const node = useRef();
const handleClick = () => {
if (!visible) {
document.addEventListener('click', handleOutsideClick, false);
} else {
document.removeEventListener('click', handleOutsideClick, false);
}
setVisible(prevState => ({
visible: !prevState.visible,
}));
}
const handleOutsideClick = (e) => {
if (node.contains(e.target)) {
return;
}
handleClick();
}
return(
<div ref={node}>
<button onClick={handleClick}>Click to See</button>
{visible && <div>You Clicked the Button</div>}
</div>
);
};
export default OutSiderClickComponent;
When you use useRef you need to remember that the value is in current attribute of ref.
Try node.current.contains().
The rest should look more like that, using React.useEffect:
const handleOutsideClick = (e) => {
if (node.current.contains(e.target)) {
console.log('clicked inside');
// this.setVisible(true);
} else {
this.setVisible(false);
}
}
React.useEffect(() => {
document.addEventListener('click', handleOutsideClick, false);
return () => void document.removeEventListener('click', handleOutsideClick, false);
}, []);
and
<button onClick={() => void setVisible(true)}>Click to See</button>
There are two changes. First, you need to use node.current to check for the ref,
node.current.contains(e.target) . Also the ref must be attached to the node to which you need to detect outside click
var { useState, useEffect, useRef } = React;
const OutSiderClickComponent = () => {
const [visible, setVisible] = useState(false);
const node = useRef();
const handleClick = () => {
if (!visible) {
document.addEventListener('click', handleOutsideClick, false);
} else {
document.removeEventListener('click', handleOutsideClick, false);
}
setVisible(prevState => ({
visible: !prevState.visible,
}));
}
const handleOutsideClick = (e) => {
if (node.current.contains(e.target)) {
return;
}
setVisible(prev => !prev.visible)
}
return(
<div>
<button onClick={handleClick}>Click to See</button>
{visible && <div ref={node}>You Clicked the Button</div>}
</div>
);
};
ReactDOM.render(<OutSiderClickComponent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />

Strange React hooks behavior, can't access new state from a function

I use the library react-use-modal, and
I'm trying to read the updated value of confirmLoading when inside the handleClick function.
handleClick does read the first value of confirmLoading defined when doing const [ confirmLoading, setConfirmLoading ] = useState(false), but never updates when I setConfirmLoading inside handleOk.
I don't understand what I'm doing wrong
import { Button, Modal as ModalAntd } from 'antd'
import { useModal } from 'react-use-modal'
export interface ModalFormProps {
form: React.ReactElement
}
export const ModalForm: React.FC = () => {
const [ confirmLoading, setConfirmLoading ] = useState(false)
const { showModal, closeModal } = useModal()
const handleOk = () => {
setConfirmLoading(true)
setTimeout(() => {
setConfirmLoading(false)
closeModal()
}, 1000)
}
const handleCancel = () => {
closeModal()
}
const handleClick = () => {
showModal(({ show }) => (
<ModalAntd
onCancel={handleCancel}
onOk={handleOk}
title='Title'
visible={show}
>
// the value of confirmLoading is always the one defined
// with useState at the beginning of the file.
<p>{confirmLoading ? 'yes' : 'no'}</p>
</ModalAntd>
))
}
return (
<div>
<Button onClick={handleClick}>
Open Modal
</Button>
</div>
)
}
This is happening because of closures. The component that you pass to showModal remembers confirmLoading and when you call function setConfirmLoading your component renders again and function handleClick is recreated. 'Old' handleClick and 'old' component in showModal know nothing about the new value in confirmLoading.
Try to do this:
export const ModalForm: React.FC = () => {
const { showModal, closeModal } = useModal();
const handleClick = () => {
showModal(({ show }) => {
const [ confirmLoading, setConfirmLoading ] = useState(false);
const handleOk = () => {
setConfirmLoading(true)
setTimeout(() => {
setConfirmLoading(false)
closeModal()
}, 1000)
};
const handleCancel = () => {
closeModal()
};
return (
<ModalAntd
onCancel={handleCancel}
onOk={handleOk}
title='Title'
visible={show}
>
// the value of confirmLoading is always the one defined
// with useState at the beginning of the file.
<p>{confirmLoading ? 'yes' : 'no'}</p>
</ModalAntd>
);
})
};
return (
<div>
<Button onClick={handleClick}>
Open Modal
</Button>
</div>
)
}

Resources