How to use react state hook in dynamic modal - react-hooks

I'm trying to use a react modal to allow someone to change a value and have the modal buttons be dynamic such that initially there are Cancel / Submit buttons, but after Submit is pressed and the value is changed, the buttons are replaced with a Close button.
The problem I am having is that using "{modalButtonGroup}" in the modal results in "tmpName" being undefined when "handleSetNameSubmit" is called. If I instead comment out the "{modalButtonGroup}" line and just use hard coded buttons (which are currently commented out in the below code), then "tmpName" is set correctly when "handleSetNameSubmit" is called.
Is there some aspect of state context that causes "tmpName" to not be known when "{modalButtonGroup}" is used?
import { useState, useEffect } from 'react';
import { Row, Table, Form, Button, Modal, Alert } from 'react-bootstrap';
const System = () => {
const [tmpName, setTmpName] = useState();
const [showName, setShowName] = useState(false);
const handleClose = () => {
setShowName(false);
}
const handleCancel = () => {
setShowName(false);
};
const handleSetNameSubmit = () => {
console.log('tmpName: ', tmpName);
//code to change the name to tmpName
setModalButtonGroup(modalButtonsPostSubmit);
}
const modalButtonsPreSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
<Button variant="primary" onClick={handleSetNameSubmit}>
Submit
</Button>
</>
)
};
const modalButtonsPostSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</>
)
};
const [modalButtonGroup, setModalButtonGroup] = useState(modalButtonsPreSubmit);
return (
<>
<div className="card">
<h5>System</h5>
<Table variant="dark" responsive>
<tr>
<td>Name:</td>
<td>Name <Button onClick={() => setShowName(true)}>Edit</Button></td>
</tr>
</Table>
</div>
{/* Set Name */}
<Modal show={showName} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Set Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<span>
<Form.Control
type="text"
defaultValue=name
onChange={(event) => setTmpName(event.target.value)}
/>
</span>
</Modal.Body>
<Modal.Footer>
{modalButtonGroup}
{/*<Button variant="secondary" onClick={handleCancel}>*/}
{/* Cancel*/}
{/*</Button>*/}
{/*<Button variant="primary" onClick={handleSetNameSubmit}>*/}
{/* Submit*/}
{/*</Button>*/}
</Modal.Footer>
</Modal>
}
export default System;
UPDATE, I tried updating the code per suggestion as follows but now no buttons are appearing at all.
import { useState, useEffect } from 'react';
import { Row, Table, Form, Button, Modal, Alert } from 'react-bootstrap';
const System = () => {
const [tmpName, setTmpName] = useState();
const [showName, setShowName] = useState(false);
const [submitted, setSubmitted] = useState(false);
const handleClose = () => {
setShowName(false);
}
const handleCancel = () => {
setShowName(false);
};
const handleSetNameSubmit = () => {
console.log('tmpName: ', tmpName);
//code to change the name to tmpName
setSubmitted(modalButtonsPostSubmit);
}
const modalButtonsPreSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
<Button variant="primary" onClick={handleSetNameSubmit}>
Submit
</Button>
</>
)
};
const modalButtonsPostSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</>
)
};
const buttons = submitted ? modalButtonsPostSubmit : modalButtonsPreSubmit;
return (
<>
<div className="card">
<h5>System</h5>
<Table variant="dark" responsive>
<tr>
<td>Name:</td>
<td>Name <Button onClick={() => setShowName(true)}>Edit</Button></td>
</tr>
</Table>
</div>
{/* Set Name */}
<Modal show={showName} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Set Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<span>
<Form.Control
type="text"
defaultValue=name
onChange={(event) => setTmpName(event.target.value)}
/>
</span>
</Modal.Body>
<Modal.Footer>
{buttons}
{/*<Button variant="secondary" onClick={handleCancel}>*/}
{/* Cancel*/}
{/*</Button>*/}
{/*<Button variant="primary" onClick={handleSetNameSubmit}>*/}
{/* Submit*/}
{/*</Button>*/}
</Modal.Footer>
</Modal>
}
export default System;

Here's what's happening. It's kind of complicated because of the unusual way in which you have written your component. I'll suggest a simpler way to do it below, but it might be educational to unpack what's going on.
Your <System> component renders for the first time:
tmpName is undefined
the handleNameSubmit function is generated and it "closes over" the current value of tmpName. This means every time this particular function value is called, it will always console.log 'tmpName: undefined'. See background on JavaScript closures
the modalButtonsPreSubmit function is generated and it closes over the current value of handleNameSubmit and binds this value to the submit button click event.
Then, you pass the modalButtonsPreSubmit function as the initial value of a useState hook. The way that useState works, this initial value is only used in the first render (see docs). The modalButtonsGroup value returned by this useState call will be frozen to this particular value (with all the closures) through subsequent re-renders, until you change it by calling setModalButtonsPreSubmit with a new function.
The user types some text in the textbox. For each character your onChange handler calls setTempName, which triggers the <System> component to re-render with a new value in the tmpName state. However, modalButtonsPreSubmit is still frozen to what it was in the first render.
The user clicks "Submit", which triggers the version handleNameSubmit that was generated on the first render, when tmpName was undefined.
The way to simplify things so that it works as expected is to not store functions in state. That way they'll get re-generated on each re-render with fresh values for any other state that they reference.
So instead of..
const modalButtonsPreSubmit = () => (
<> {/* Markdown for "Submit" and "Cancel" buttons */} </>
);
const modalButtonsPostSubmit = () => (
<> {/* Markdown for "Close" button */} </>
);
const [modalButtonGroup, setModalButtonGroup] = useState(modalButtonsPreSubmit);
return (
<div>
{/* The rest of the app */}
{modalButtonGroup}
</div>
);
You'd do something like this...
const [submitted, setSubmitted] = useState(false);
const buttons = submitted ?
<> {/* Markdown for "Close" button */} </> :
<> {/* Markdown for "Submit" and "Cancel" buttons */} </>;
return (
<div>
{/* The rest of the app */}
{buttons}
</div>
);
See this codesandbox for a working solution.

Related

React formik form onsubmit event callings many times?

import { Formik, Form, Field } from "formik";
import { Button } from "antd";
const AddUser = () => {
const initialValues = {
name: "",
};
return (
<>
<Formik
initialValues={initialValues}
onSubmit=(values) => {
alert("hi");//calling mamy times
Here added api call (post method)
}}
>
{({ isValid, submitForm, isSubmitting, values }) => {
return (
<Form>
<Field
name="name"
label="Name"
placeholder="Dataset Name"
/>
<Button
type="primar"
htmltype="submit"
loading=(props.addingdata) // this is my reducer state intial was false after post call request became true and success state value false
>
Add Dataset
</Button>
</Form>
);
}}
</Formik>
</div>
</>
);
};
export default AddUser;
I have simple formik form antd button I have used when click submit button post api calling twice and thrice even If I added loading property in button why its happening like this?

react hook, validate Form with reduxForm

I Have external component managing my input Field and throws an error if no input is made.
On submit of form previously with class component along with reduxForm effect, this would throw an error of missing input, am wondering how to achieve this with hooks since submission passes whether i have input or Not.
import ConstructField from '../components.render';
const ActivitiesForm = () => {
const handleSubmit_ = () => {
console.log({ activityName });
};
const [activityName, setActivityName] = useState(null);
const handleInputName = (e) => setActivityName(e.target.value);
const { items } = useSelector((state) => ({
items: state.items,
}));
const { register, handleSubmit, errors, control } = useForm();
return (
<div>
<Form onSubmit={handleSubmit(handleSubmit_)} className='ui form'>
<Form.Group widths='equal'>
<Field
component={ConstructField('input')}
onChange={handleInputName}
label='Activity Name'
name='activityName'
placeholder='Activity Name'
validate={required}
/>
</Form.Group>
<br />
<Form.Group inline>
<Button.Group>
<Button primary>Save</Button>
<Button.Or />
<Button positive onClick={goBackButton}>
Go Back
</Button>
</Button.Group>
</Form.Group>
</Form>
</div>
);
};
const required = (value) => (value ? undefined : 'this field is required');
const activityform = reduxForm({
form: 'activityform',
enableReinitialize: true,
})(ActivitiesForm);
export default activityform;

React ajax call when button onClick event using hooks

import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [contact, setContact] = useState({
fName: "",
lName: "",
email: ""
});
function handleClick() {
const res = axios.get("url");
}
useEffect(()=>{
handleClick();
})
return (
<div className="container">
<h1>
Hello {contact.fName} {contact.lName}
</h1>
<p>{contact.email}</p>
<input name="fName" placeholder={contact.fName} />
<input name="lName" placeholder={contact.lName} />
<input name="email" placeholder={contact.email} />
<button onClick={handleClick}>Submit</button>
</div>
);
}
export default App;
I set initial state with empty string but I am trying to update input attributes with data from external source whenever user clicks submit button.
I heard I need to use useEffect method to api call in react, but I have no idea where to start.
if you're going to update the data on the button click, then you can use a count mechanism, a separate variable to keep track of the count.
const [count, setCount] = useState(0);
<button onClick={() => setCount(count + 1 )}>Submit</button>
async function handleClick() {
const res = await axios.get("url");
setContact(res.data);
}
useEffect(() => {
handleClick();
}, [contact, count]);

Using Refs in a Dynamic Length Array for scrollIntoView

I am somewhat new to using useEffect and useRef. What I'm trying to do is create a content management system that enables scrollIntoView for n number of page elements from a database for a single page scrolling app.
I'm not able to get it to work so I'm hoping for some assistance.
I've posted a simple test where I have a functional react component where section 2 works (clicking the button scrolls down to the page element.
But my goal is to take 'n' sections from a database and create 'n' number of refs to scroll down to 'n' number of sections.
I tries useRef, useEffect, etc but I'm stumped. My code snippet shows a working example with manual ref declarations for Section 2 but Section 1 attempts to use a collection of Refs and that's not working
Here is a code example: https://codesandbox.io/embed/trusting-stallman-bsjj1?fontsize=14
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
let pageRef = useRef([
React.createRef(),
React.createRef()
]);
const pageHomeRef = React.createRef();
const scrollToRef = ref => ref.current.scrollIntoView({ behavior: "smooth" });
const scrollToPane = ref => scrollToRef(ref);
return (
<div className="App">
<div className="menu">
<button
onClick={() => {
scrollToPane(pageRef[1]);
}}
>
Scroll to Section 1
</button>
<button onClick={() => scrollToPane(pageHomeRef)}>
Section 2
</button>
</div>
<div className="page" style={{ marginTop: "1500px", marginBottom: "1500px" }}>
<div className="section1" ref={pageRef[1]}>
Section 1
</div>
<div className="section2" ref={pageHomeRef}>
Section 2
</div>
</div>
</div>
);
};
I'd like to feed an array of page elements and have the refs dynamically change as needed.
Little order the code - try it
You can use React.createRef() or useRef(null) in the array.
And make us many refs you want in array.
And even make map if you have a list (https://dev.to/ajsharp/-an-array-of-react-refs-pnf)
And you have other ways insted of refs.
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
let pageRef = [useRef(null),useRef(null)];
const pageHomeRef = [React.createRef(),React.createRef()];
const scrollToRef = ref => ref.current.scrollIntoView({ behavior: "smooth" });
const scrollToPane = num => scrollToRef(pageRef[num]);
return (
<div className="App">
<div className="menu">
<button
onClick={() => {scrollToPane(0)}}>Scroll to Section 1</button>
<button onClick={() => scrollToPane(1)}>Section 2</button>
</div>
<div
className="page"
style={{ marginTop: "1500px", marginBottom: "1500px" }}
>
<div className="section1" ref={pageRef[0]}>
Section 1
</div>
<div className="section2" ref={pageRef[1]}>
Section 2
</div>
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

How to hijack submit to add values?

I have a form that can have different state based on which button was used for submission; one does a simple submit while the other one adds a flag then submit.
I found a working solution that is, imo, quite ugly, so I'd like to know how else to do it ?
class MyForm extends Component {
// Hijack submit to add published flag
handlePublish = (e) => {
e.preventDefault();
const { handleSubmit, onSubmit } = this.props;
handleSubmit((values) => {
onSubmit({
...values,
isPublished: true,
});
})();
}
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<Field
name="foo"
component="input"
/>
<button
type="submit"
onClick={this.handlePublish}
>
Publish
</button>
<button type="submit">
Save
</button>
</form>
);
}
}
✅ This is the idiomatic way. Alternatively, you could provide any number of values as initialValues that don't actually have a Field on the form, but will be submitted.

Resources