My hooks aren't opening and closing my modals - javascript

I'm new to react and redux, I'm trying to do things the newish hooks way and running into issues opening and closing a Modal using a redux state.
Basically, as soon as my page loads, the modal opens, even though the initial state in the slice is set to false and the close button in the modal footer doesn't close it.
I'm trying to learn from the example that compiles from npx create-react-app redux-demo --template redux but I'm clearly missing something.
Thanks!
AffinityModal.js
import React from 'react';
import { Button, Form, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader, Row, } from 'reactstrap';
import { affinOpen, toggleAffinAsync } from '../modalSlice'
import { useDispatch } from 'react-redux';
function AffinityModal() {
const dispatch = useDispatch();
return (
<Modal isOpen={affinOpen} toggle={() => dispatch(toggleAffinAsync())}>
<ModalHeader>
<h5 className="modal-title" id="exampleModalLabel">New Ingredient Affinity</h5>
<Button data-dismiss="modal" aria-label="Close" className="close">
<span aria-hidden="true">×</span>
</Button>
</ModalHeader>
<ModalBody>
<div className="container-fluid">
<Form>
<FormGroup>
<Row>
<div className="col-12">
<Label for="mainIngName" className="col-form-label">Main Ingredient:</Label>
</div>
</Row>
<Row>
<div className="col-12">
<Input readOnly type="text" id="mainIngName"></Input>
</div>
</Row>
</FormGroup>
<FormGroup>
<Row>
<div className="col-12">
<Label for="added-ing-text" className="col-form-label">Combines Well With:</Label>
</div>
</Row>
<Row id="secondaryIngs">
<div className="col-10">
<Input type="text" id="added-ing-text" className="secIngInputs"></Input>
</div>
<div className="col-2">
<Button id="ingPlusButton">+</Button>
</div>
</Row>
</FormGroup>
</Form>
</div>
</ModalBody><ModalFooter>
<Button data-dismiss="modal" onClick={() => dispatch(toggleAffinAsync())} color="secondary">Close</Button>
<Button id="submitNewIngButton" color="primary" className="submitButton">Submit</Button>
</ModalFooter>
</Modal >
)
}
export default AffinityModal
modalSlice.js
import { createSlice } from '#reduxjs/toolkit';
export const modalSlice = createSlice({
name: 'openAffinityModal',
initialState: {
isAffinityModalOpen: false,
isRecipeModalOpen: false
},
reducers: {
toggleAffinityModal: state => {
state.isAffinityModalOpen = !state.isAffinityModalOpen
},
toggleRecipeModal: state => {
state.isRecipeModalOpen = !state.isRecipeModalOpen
}
}
})
export const { toggleAffinityModal, toggleRecipeModal } = modalSlice.actions;
export const toggleAffinAsync = isAffinityModalOpen => dispatch => {
dispatch(toggleAffinityModal);
};
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state) => state.counter.value)`
//useSelector((state) => state.openAffinityModal.isAffinityModalOpen)
export const affinOpen = state => state.openAffinityModal.isAffinityModalOpen;
export default modalSlice.reducer;

You don't need toggleAffinAsync at all. Just use the regular action creator toggleAffinityModal.
affinOpen is a selector function. It is not a value. Right now your Modal is always open because you are passing this function to the isOpen prop and a function is truthy when cast to a boolean.
In order to get the boolean value from the state, you need to call affinOpen with useSelector.
function AffinityModal() {
const dispatch = useDispatch();
const isOpen = useSelector(affinOpen);
return (
<Modal isOpen={isOpen} toggle={() => dispatch(toggleAffinityModal())}>
...
Code Sandbox Demo

Did you try the original version from the reactstrap docs?
const toggle = () => dispatch(toggleAffinAsync());
<Modal isOpen={affinOpen} toggle={toggle} >
...
</Modal>

Related

Passing Variables to/from Modal

I implemented a Modal which uses a custom Hook in my App that should function as a login mask for a WiFi network. So far the Modal is working and I also already managed to pass the name of the selected SSID to it. However I'm struggling with getting back a variable into my other component from where I launched the modal.
Modal.js:
import React from 'react';
import { createPortal } from 'react-dom';
const Modal = ({ isShowing, connect, ssid, hide }) => isShowing ? createPortal(
<React.Fragment>
<div className="modal-overlay" />
<div className="modal-wrapper" aria-modal aria-hidden tabIndex={-1} role="dialog">
<div className="modal">
<div className="modal-header">
<button type="button" className="modal-close-button" data-dismiss="modal" aria-label="Close" onClick={hide}>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body">
<p>Connect to: {ssid}</p>
<label>
<form onSubmit={connect}>
<input
id="password"
name="Password"
type="password"
value={"password"}
/>
<button type="submit">Connect</button>
</form>
</label>
</div>
</div>
</div>
</React.Fragment>, document.getElementById("modal")
) : null;
export default Modal;
useModal.js:
import { useState } from 'react';
const useModal = () => {
const [isShowing, setIsShowing] = useState(false);
const [password, setPassword] = useState("password");
function toggle() {
setPassword("HOW");
setIsShowing(!isShowing);
}
return {
password,
isShowing,
}
};
export default useModal;
Settings.js:
import React from "react";
import { useState, useEffect } from "react";
import Modal from "../../components/Modal";
import useModal from "../../components/useModal";
const electron = window.require('electron');
const { ipcRenderer } = electron;
const Settings = ({ reqReload }) => {
const [wifiList, setWifiList] = useState([{ id: "", ssid: 'No Networks available' }]);
const [ssidSelected, setSsidSelected] = useState("127.0.0.1");
const { isShowing, toggle } = useModal();
const updateWifi = (event, args) => {
setWifiList(args);
};
function openDialogue(ssid) {
setSsidSelected(ssid);
toggle();
};
/*
function connectWifi(password) {
console.log("Aktuelles Passwort: ", password);
toggle();
}
*/
useEffect(() => {
ipcRenderer.send('updateWifi');
ipcRenderer.on('wifi_list', updateWifi);
return function cleanup() {
ipcRenderer.removeListener('wifi_list', updateWifi);
};
}, []);
return (
<div className="settings">
<Modal
isShowing={isShowing}
connect={connectWifi}
ssid={ssidSelected}
hide={toggle}
/>
<div className="settings__header">
<h2></h2>
</div>
<div className="settings__body">
<div className="settings__connections">
<div className="settings__connections__wifi">
<p><i>Available Wifi-Networks:</i></p>
<div className="settings__connections__wifi__list">
{wifiList.map((item, i) => (
<div className="settings__connections__wifi__list__item" key={i}>
<button className="selectNetworkButton" type="button" onClick={() => openDialogue(item.ssid)}>{item.ssid}</button>
</div>
))}
</div>
</div>
</div>
</div>
</div>
)
};
export default Settings;
The code above already contains some things I tried. As stated before, passing the SSID to the modal worked, but I dont have a clue how to get the password back to Settings.js to handle the data there.
I'd be happy if someone can point me in the right direction!
in order to get back a variable from a child component, you can have the variable you would like to get back as a state in your parent component:
const [yourVariable, setYourVariable] = useState('')
then pass setYourVariable as a props to your modal.
this way you can set youVariable from inside the modal component, and get back its value this way :
// inside modal component
setYoutVariable(...)
I stumbled over another article just now and it seems like using a stateless component as my model leads to a dead end. Will have to convert it to a stateful component in order to extract the data from the form.

Render a new component from the current component after click the button

I am building a login/signup/reset form. I am encountering a problem which is when on the modal of reset password, I would like to click the button to submit email for reset password. After click the button, I want a new success message modal replace the current modal. But the success message always appear below the current reset password modal. How could solve it? Thank you.
Here is the modal component of reset password
import { useState } from "react";
import { updatePasswordFields } from "../constants/formFields";
import FormAction from "./FormAction";
import Input from "./Input";
import MessageContent from "./Message";
const fields = updatePasswordFields;
let fieldsState = {};
fields.forEach((field) => fieldsState[(field.id = "")]);
export default function ForgotPassword() {
const [resetPasswordState, setResetPasswordState] = useState(fieldsState);
const [showMessage, setShowMessage] = useState(false);
const handleChange = (e) =>
setResetPasswordState({
...resetPasswordState,
[e.target.id]: e.target.value,
});
const handleSubmit = (e) => {
e.preventDefault();
setShowMessage(true);
};
return (
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<div className="">
{fields.map((field) => (
<>
<label>{field.labelText}</label>
<Input
key={field.id}
handleChange={handleChange}
value={resetPasswordState[field.id]}
labelText={field.labelText}
labelFor={field.labelFor}
id={field.id}
name={field.name}
type={field.type}
isRequired={field.isRequired}
placeholder={field.placeholder}
/>
</>
))}
<FormAction handleSubmit={handleSubmit} text="Update Password" />
{showMessage && <MessageContent />}
</div>
</form>
);
}
Here is the success message modal
import React from "react";
import { MdMarkEmailRead } from "react-icons/md";
export default function MessageContent() {
return (
<div>
<div className="text-sky-600 text-center w-full flex jutify-center">
<MdMarkEmailRead size={44} />
</div>
<div className="text-center text-sm mb-10 max-w-[300px] mx-auto">
We have sent the update password link to your email, please check that!
</div>
</div>
);
}
Here is the result what I got so far screenshot
I'm not sure if I get your means..
If you want to show MessageContent() and hide reset password form in the same modal, there is an easy way to get it.
export default function ForgotPassword() {
// ellipsis...
if (showMessage) {
return <MessageContent />
}
return (
<form ...>...</form>
)
}

useState passed to useContext not updating state

I am trying to use useContext to create a generic Tooltip component that passes a close() function to the content inside the Tooltip. I have written my Tooltip like this
export function Tooltip(props) {
const [active, setActive] = useState(false);
const close = () => {
setActive(false);
}
return (
<div className="tooltip-wrapper"
onClick={() => setActive(true)}
>
{props.children}
<TooltipContext.Provider value={{close}}>
{active && (
<div className='tooltip-tip bottom' ref={node}>
{props.content}
</div>
)}
</TooltipContext.Provider>
</div>
)
}
I create the Tooltip in a different class component as follows
function Category(props) {
return (
<Tooltip content={<AddCategoryInnerTooltip name={props.name}/>}>
<p className="tooltip-name-opener">{props.name}</p>
</Tooltip>
);
}
function AddCategoryInnerTooltip(props) {
const {close} = useContext(TooltipContext);
return(
<div className="inner-tooltip-wrapper">
<input
className="tooltip-custom-input"
type="text"
defaultValue={props.name}
/>
<div className="button-end">
<button onClick={close}>Cancel</button>
<button>Ok</button>
</div>
</div>
)
}
When I attempt to call close within the AddCategoryInnerTooltip, the state passed from the Tooltip component doesn't update. When I console.log the state, it always comes as true without changing. What am I doing wrong?
should be a callback function
<button onClick={()=>close}>Cancel</button>

Wrong pass prop in react component

I have simple Reactjs app that includes the Card and Modal components. every Card must have a Modal that when clicking on "Show More" button, open it.
Modal should only show the title on its Card and my problem is passed props to Modal, just send the title of the last Card And not about itself!
In summary, the prop of title received properly in Card component but Card component can't pass title to Modal correctly.
Here is my app in CodeSandBox: Demo
Card Components:
const Card = props => {
const { children, title } = props;
const { isShowModal, setIsShowModal } = useContext(MainContext);
const showModalHandler = () => {
setIsShowModal(true);
};
return (
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
<div className="card-content">{children}</div>
<div className="card-footer">
<button onClick={showModalHandler}>Show More</button>
</div>
{isShowModal && <Modal title={title} />}
</div>
);
};
Modal Component:
const Modal = props => {
const { setIsShowModal } = useContext(MainContext);
const closeModalHandler = () => {
setIsShowModal(false);
};
const { title } = props;
return (
<div className="modal">
<h2>Modal: {title}</h2>
<p>
You cliked on <b>{title}</b> Card
</p>
<hr />
<button onClick={closeModalHandler}>Close</button>
</div>
);
};
Note: I use Context for control open/close modal in isShowModal state and maybe that's the problem?
Just as you thought the problem seems to be the useContext that you are using. So I made a couple of changes to the code, most importantly using useState. I recommend you read the documentation about useContext and when to use it. Here is the updated code:
Card.js
import React, { useState } from "react";
import Modal from "./Modal";
import "./Card.scss";
const Card = props => {
const { children, title } = props;
const [ isShowModal, setIsShowModal ] = useState(false);
return (
<div className="card">
<div className="card-header">
<h2>{title}</h2>
</div>
<div className="card-content">{children}</div>
<div className="card-footer">
<button onClick={() => setIsShowModal(true)}>Show More</button>
</div>
{isShowModal && <Modal setIsShowModal={setIsShowModal} title={title} />}
</div>
);
};
export default Card;
Modal.js
import React from "react";
import "./Modal.scss";
const Modal = props => {
const { title } = props;
return (
<div className="modal">
<h2>Modal: {title}</h2>
<p>
You cliked on <b>{title}</b> Card
</p>
<hr />
<button onClick={() => props.setIsShowModal(false)}>Close</button>
</div>
);
};
export default Modal;
As you can see, Modal.js component doesn't have to be a stateful component. You can pass as a prop the setIsShowModal function from Card.js component. That way you can make the modal a reusable component.

Extracting HTML from a stateless React component that's been passed into a trigger event

I'm trying to create a reusable modal that is rendered high in the DOM (direct child of <body>), and gets content passed to it from wherever.
I have to set the state of the modal with something like a trigger event (unless I'm overlooking another option). Redux is not an option, as I don't have it in the app.
My problem is that when I pass the component containing the content into the trigger event, it renders just the object, but none of the html. It makes sense to me why it works like this, but I can't seem to find a way to extract the content from that object.
My modal:
import React from "react"
import PropTypes from "prop-types"
import Rodal from 'rodal'
class ApplicationModal extends React.Component {
state = {
modalIsOpen: false,
htmlContent: ""
}
componentDidMount() {
$(window).on('modalToggle', (e, content) => {
this.modalToggle(() => this.setModalContent(content))
})
}
setModalContent = (content) => {
this.setState({htmlContent: content})
}
modalToggle = (callback) => {
this.setState({modalIsOpen: !this.state.modalIsOpen}, callback())
}
modalClose = () => {
this.setState({modalIsOpen: false})
}
modalOpen = () => {
this.setState({modalIsOpen: true})
}
render () {
return (
<React.Fragment>
<Rodal visible={this.state.modalIsOpen} onClose={this.modalClose} closeOnEsc={true} className={this.props.rodalClasses}>
<div id="modal-container">
<div dangerouslySetInnerHTML={{__html: this.state.htmlContent}}></div>
</div>
</Rodal>
</React.Fragment>
);
}
}
export default ApplicationModal
My page:
import React from "react"
import PropTypes from "prop-types"
class MyPage extends React.Component {
render () {
// This works
const html = `
<div className="modal-content">
<p>This is a question.</p>
<p>This is an answer.</p>
</div>
`
// This does not work
const ModalContent = () => (
<div className="modal-content">
<p>This is a question.</p>
<p>This is an answer.</p>
</div>
)
return (
<React.Fragment>
<h1>My page</h1>
{/* This works */}
<a href='javascript:void(0)' onClick={() => $(window).trigger('modalToggle', html)}>Learn more</a>
{/* This does not work */}
<a href='javascript:void(0)' onClick={() => $(window).trigger('modalToggle', <ModalContent/>)}>Learn more</a>
</React.Fragment>
);
}
}
export default MyPage
I'd like to be able to pass full components into the trigger event, so the content can be rendered with buttons and dynamic inputs.
Am I going about this completely wrong?
In case it's not obvious, Rodal is just a pretty modal library.
What you're describing can be accomplished with Reactstrap (A bootstrap library specific for React) and the built in component Modal:
import React from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
const ApplicationModal = props => {
const modalBody = (
<div className="modal-content">
<p>This is a question.</p>
<p>This is an answer.</p>
</div>
);
return (
<div>
<Button color="danger" onClick={props.toggle}>
{props.props.buttonLabel}
</Button>
<Modal
isOpen={props.modal}
toggle={props.toggle}
className={props.className}
>
<ModalHeader toggle={props.toggle}>Modal title</ModalHeader>
<ModalBody>{modalBody}</ModalBody>
<ModalFooter>
<Button color="primary" onClick={props.toggle}>
Do Something
</Button>{" "}
<Button color="secondary" onClick={props.toggle}>
Cancel
</Button>
</ModalFooter>
</Modal>
</div>
);
};
export default ApplicationModal;
Turns out the dangerouslySetInnerHTML was messing it up. I guess it was trying to render straight HTML, rather than rendering the component I passed in as an executable function. Which... Makes sense now that I think about it...

Categories

Resources