Developing a generic React component to open and close arbitrary forms - reactjs

I got the following snippet to open and close forms on my page:
<div>
{this.state.editFormOpen?
<div>
<Link
to=''
onClick={()=>{this.setState({editFormOpen:false})}}>
Close
</Link>
<PostForm
onSubmit={(post)=>this.createPost(post)}
/>
</div>:
<Link
to=''
onClick={()=>{this.setState({editFormOpen:true})}}>
Add post
</Link>}
</div>
Now in order to avoid repeating myself, I want to refactore that code into a seperate <FormHandler/> component, such that it works for arbitrary forms, not only
<PostForm/>. I.e., I need to be able to pass any form component to the <FormHandler/>.
How can I do this in React?

I will go about it by making a factory that returns a React Component, than render the children as props
const FormHandlerFactory = ({Form = PostForm}) => {
return class FormHandler extends React.Component {
render() {
return (
<div>
{this.state.editFormOpen?
<div>
<Link
to=''
onClick={()=>{this.setState({editFormOpen:false})}}>
Close
</Link>
<Form
onSubmit={(post)=>this.createPost(post)}
/>
</div>:
<Link
to=''
onClick={()=>{this.setState({editFormOpen:true})}}>
Add post
</Link>}
</div>
)
}
}
}
You can use this factory as
const FormHandler = FormHandlerFactory({Form: YourCustomFormComponent});
const App = (props) => (
<Form {...props}
)

You could create a stateless component that could render any children that you passed in that component you also need to put the states in the parent component
const ToggleForm = ({ editFormOpen, editFormHandler, children }) => (
<div>
{editFormOpen?
<div>
<Link
to=''
onClick={() => editFormHandler(false)}>
Close
</Link>
{children}
</div>:
<Link
to=''
onClick={() => editFormHandler(true)}>
Add post
</Link>}
</div>
);
class ParentCmp extends React.Component {
constructor(props) {
super(props);
this.state = {
editFormOpen: false,
}
this.editFormHandler = this.editFormHandler.bind(this);
this.createPost = this.createPost.bind(this);
}
editFormHandler(boolValue) {
this.setState({
editFormOpen: boolValue
});
}
render() {
return (
<div>
<ToggleForm
editFormOpen={this.state.editFormOpen}
editFormHandler={this.state.editFormHandler}
>
<PostForm
onSubmit={(post)=>this.createPost(post)}
/>
</ToggleForm>
</div>
)
}
}

You already have everything you need in order to accomplish that. Maybe just change the line where you render PostForm and accept props.children coming from your new component. With that you will be able to render any form you want inside of it.
Remember that the less business logic you keep outside of FormHandler the better. Each form should be aware of what to do on every field change or on submit.
For example, this.createPost should not be part of FormHandler in case you want to reuse it throughout your code.
class FormHandler extends React.Component {
constructor(props) {
super(props)
this.state = {
formOpen: false,
}
this.toggleFormOpen = this.toggleFormOpen.bind(this)
}
toggleFormOpen() {
this.setState({
formOpen: !this.state.formOpen,
})
}
render() {
return (
<div>
<button onClick={this.toggleFormOpen}>
{this.state.formOpen ? 'Close' : 'Open'}
</button>
{this.state.formOpen && React.cloneElement(
this.props.children,
{
onClose: this.toggleFormOpen
}
)}
</div>
)
}
}
const PostForm = ({ onClose, onSubmit }) =>
<form>
<input type="text" placeholder="Post Name" />
<input type="text" placeholder="Post Title" />
<input type="text" placeholder="Date" />
<button onClick={event => {
event.preventDefault()
onSubmit({ postName: '', postTitle: '', date: ''})
onClose()
}}>
Submit
</button>
</form>
const UserForm = ({ onClose, onSubmit }) =>
<form>
<input type="text" placeholder="First Name" />
<input type="text" placeholder="Last Name" />
<button onClick={event => {
event.preventDefault()
onSubmit({ firstName: '', lastName: '' })
onClose()
}}>
Submit
</button>
</form>
const App = () =>
<div>
<FormHandler>
<PostForm
onSubmit={formData => console.log('PostForm', formData)}
/>
</FormHandler>
<br />
<FormHandler>
<UserForm
onSubmit={formData => console.log('UserForm', formData)}
/>
</FormHandler>
</div>
ReactDOM.render(
<App />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Related

How to pass Parent props to Children, when rendering using this.props.children() and React.cloneElement?

<CheckboxGroup name="fruits" value={fruits} onChange={this.setFruits}>
{(cb) => (
<>
<label>
<Checkbox value="apple" /> Apple
</label>
<label>
<Checkbox value="orange" /> Orange
</label>
<label>
<Checkbox value="watermelon" /> Watermelon
</label>
</>
)}
</CheckboxGroup>
Here is the parent component. I want to pass the name, value and onChange to the children.
class CheckboxGroup extends React.Component {
constructor(props){
super(props);
this.state = {
value: this.props.value,
name: this.props.name
}
}
render() {
const children = React.Children.map(this.props.children(), child => {
return React.cloneElement(child);
});
return(
<>
{children}
</>
)
}
}
class Checkbox extends React.Component {
constructor(props){
super(props);
}
render() {
console.log(this.props);
return (
<>
<input type="checkbox" value="" name="" />
</>
);
}
}
How to get the props from parents in children? I am using this.props.children() not this.props.children. From parent, I have tried React.cloneElement(child, {this.props}) but does not work.
From what I can tell, there doesn't seem to be a need to use the cloneElement API. You should be able to accomplish what you're after with something much simpler.
We can provide our data (i.e. the fruits) at the very top level, and use the data structure to map over each item to render a checkbox component with the name, value and onChange callback. No need to clone anything, and our components have no state of their own, so they can be simple function components.
const Checkbox = ({ name, value, onChange, isChecked }) => (
<input
type="checkbox"
name={name}
value={value}
onChange={onChange}
checked={isChecked}
/>
)
const CheckboxGroup = ({ name, values, onChange, selectedValues }) => (
<div>
{values.map((value, index) => (
<label key={index}>
<Checkbox
{...{ name, value, onChange }}
isChecked={selectedValues.includes(value)}
/>
{value}
</label>
))}
</div>
)
// an example implementation...
const theFruits = ['Banana', 'Apple', 'Orange']
const MyFruitForm = () => {
const [selectedFruits, setSelectedFruits] = React.useState([])
const handleChange = (event) =>
setSelectedFruits((oldFruits) => {
if (event.target.checked) {
return [...oldFruits, event.target.value]
} else {
return oldFruits.filter((fruit) => fruit !== event.target.value)
}
})
return (
<CheckboxGroup
name="fruit-checkboxes"
values={theFruits}
onChange={handleChange}
selectedValues={selectedFruits}
/>
)
}
ReactDOM.render(<MyFruitForm />, document.getElementById('root'))
<script src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How to set pre-field data in initial value of formik form in reactjs

I am creating a Formik form using map function in react js, but I also want to fill the initial data from the state. I am able to it in a static form via setting initial value like the following code
<Formik
initialValues={{
firstName: Data.firstName,
lastName: Data.lastName,
}}
validationSchema={SignupSchema}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
console.log("inside form", values);
this.updateForm(values);
setSubmitting(false);
}, 400);
}}>
So, How can I do it if I am creating my input like the following code
const masterCategory = Object.keys(campaignObj.newMasterCategory).map(item => {
return (
<Field name={`master_category${fc}`} key={fc}>
{({ field }) => <input {...field} className="form-control nopadding-r input-width" type="text" placeholder="Enter Master Category" />}
</Field>
);
});
and rendering it like this
<div className={`col-6 ${this.state.retailflag}`}>
{masterCategory}
</div>
it works for me.
import React, { Component } from "react";
import { Formik, Field } from "formik";
export default class Form extends Component {
formlist = ["username", "password"];
render() {
return (
<Formik
initialValues={{ username: "", password: "" }}
onSubmit={this.props.handleSubmit}
{...this.props}
>
{formikProps => {
return (
<ul>
{this.formlist.map((item, i) => {
return (
<li key={i}>
<Field type="email" name={item} placeholder="Email" />
</li>
);
})}
<li>
<button type="submit" onClick={formikProps.handleSubmit}>
Login
</button>
</li>
</ul>
);
}}
</Formik>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Example => https://codesandbox.io/s/empty-monad-obfvx

How to accept and pass two parameter as props

Hi I need to pass two parameters, to the class Chat. Currently it is getting only one parameter and displaying correctly.
const Chat = props => (
<div >
<ul>{props.messages.map(message => <li key={message}>{message}</li>)}</ul>
</div>
);
This Chat.js file is called from the Home.js. Suppose I need to pass the Chat component two parameters and I tried it like following.
import React, { Component } from 'react';
import { User } from './User';
import Chat from './Chat';
export class Home extends Component {
displayName = Home.name
state = {
messages: [],
names: []
};
handleSubmit = (message,name) =>
this.setState(currentState => ({
messages: [...currentState.messages, message],
names: [...currentState.names,name]
}));
render() {
return (
<div>
<div>
<User onSubmit={this.handleSubmit} />
</div>
<div>
<Chat messages={this.state.messages,this.state.name} />
</div>
</div>
);
}
}
In this scenario how should I change the Chat component to accept two parameters and display inside div tags.
This is what I tried. But seems it is incorrect.
const Chat = props => (
<div >
<ul>{props.messages.map((message, name) => <li key={message}>{message}</li> <li key={name}>{name}</li>)}</ul>
</div>
);
PS: The User Method
import * as React from 'react';
export class User extends React.Component{
constructor(props) {
super(props);
this.state = {
name: '',
message: ''
}
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
render() {
return (
<div className="panel panel-default" id="frame1" onSubmit={this.handleSubmit}>
<form className="form-horizontal" action="/action_page.php" >
<div className="form-group">
<label className="control-label col-sm-2" htmlFor="name">Your Name </label>
<div className="col-sm-10">
<input type="text" className="form-control" name="name" placeholder="Enter your Name" onChange={this.handleChange} />
</div>
</div>
<div className="form-group">
<label className="control-label col-sm-2" htmlFor="message">Message</label>
<div className="col-sm-10">
<input type="text" className="form-control" name="message" placeholder="Enter your Message" onChange={this.handleChange}/>
</div>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-10">
<button type="submit" id="submit" className="btn btn-default">Submit</button>
</div>
</div>
</form>
</div>
);
}
handleChange(evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
handleSubmit = (e) => {
e.preventDefault();
this.props.onSubmit(this.state.message, this.state.name);
this.setState({ message: "" });
this.setState({name:""});
};
}
You can do this by using separate attributes to pass different props. So for instance, you might revise your <Home/> components render method like so:
<Chat messages={this.state.messages} names={this.state.names} />
and then to access these two bits of data (messages and name) from inside the <Chat /> component you could do the following:
const Chat = props => (
<div >
<ul>{props.messages.map((message, index) => <li key={message}>
From: { Array.isArray(props.names) ? props.names[index] : '-' }
Message: {message}</li>)}
</ul>
</div>
);
Hope this helps!
You have to pass them separately:
<Chat messages={this.state.messages} name={this.state.name} />

How can I get an input's value on a button click in a Stateless React Component?

I have the following functional component
const input = props => (
<div>
<input placeholder="Type a message..." />
<div onClick={props.send} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
How could I possibly pass the value of the input to props.send()?
I found a solution for this exact scenario on React's official docs: https://reactjs.org/docs/refs-and-the-dom.html#refs-and-functional-components
This approach allows your component to remain stateless and also doesn't require you to update the parent component on every change.
Basically,
const input = props => {
let textInput = React.createRef();
function handleClick() {
console.log(textInput.current.value);
}
return (
<div>
<input ref={textInput} placeholder="Type a message..." />
<div onClick={handleClick} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
}
Edit May 2021: Since this answer seems to be getting some attention, I have updated the answer to use a hooks based approach as well, since that is what I would use now (If using React 16.8 and above).
const input = props => {
const [textInput, setTextInput] = React.useState('');
const handleClick = () => {
console.log(textInput);
props.send(textInput);
}
const handleChange = (event) => {
setTextInput(event.target.value);
}
return (
<div>
<input onChange={handleChange} placeholder="Type a message..." />
<div onClick={handleClick} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
}
There are many ways to do it since you're very much concerned about performance. Here is the implementation, your component will be rendered only when you click on send button which actually means state will be updated once and input value will be displayed in parent component.
const Input = props => {
return (
<div>
<input onChange={props.changeHandler} placeholder="Type a message..." />
<button onClick={props.send}>send</button>
</div>
);
};
class App extends Component {
state = {
inputValue: ""
};
inputValue = '';
send = () => {
this.setState({ inputValue: this.inputValue });
};
changeHandler = event => {
this.inputValue = event.target.value;
};
render() {
console.log("In render");
return (
<React.Fragment>
<Input changeHandler={this.changeHandler} send={this.send} />
<div> {this.state.inputValue}</div>
</React.Fragment>
);
}
}
Since you mentioned that you just started with React, I'd suggest that you work through the documentation (which offers nice explanation).
According to your comment, the usage of a functional component is not a requirement. Therefore I'd recommend to do it that way -->
Your CustomInput component:
import React from "react";
import PropTypes from "prop-types";
class CustomInput extends React.Component {
constructor() {
super();
this.textInput = React.createRef();
}
static propTypes = {
send: PropTypes.func
};
render() {
const { send } = this.props;
return (
<React.Fragment>
<input placeholder="Type a message..." ref={this.textInput} />
<div
onClick={() => send(this.textInput.current.value)}
className="icon"
>
CLICK ME
</div>
</React.Fragment>
);
}
}
export default CustomInput;
If you noticed, I've replaced the empty div with React.Fragment. In that case you can omit the unnecessary <div> wrappings (if those are not required) which will keep your DOM clean (Read more about it here.
Usage:
<CustomInput
send={(prop) => {
console.log(prop)
}}
/>
I just used a dummy function which will log the input value to the console..
You can check the working example (Make sure to trigger the console in the editor) here
Posting this answer, If incase someone is using an earlier release of React 16.3. We can achieve the same thing by using callback refs instead without having to maintain state or having onChange event handler:
const input = props => (
<div>
<input ref={props.myRef} placeholder="Type a message..." />
<div onClick={props.send} className="icon">
<i className="fa fa-play" />
</div>
</div>
)
Calling that Input Component
handleClick = () => console.log(this.inputRef.value);
<Input myRef={el => this.inputRef = el} send={this.handleClick} />

react add many items in component

I have a component that accepts another component with 3 fields, and I want that I can add new entries to different fields
  now I can add only the entry in firstname and I do not know how to make that for for the lastname and telegrams
if I just copy them, then the values are accepted only from one field
import React, { Component } from 'react';
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
dataItems: []
}
}
addItem(value) {
let newListItems = this.state.dataItems.slice();
newListItems.push(value);
this.setState({
dataItems : newListItems
});
}
render() {
return (
<div>
{this.state.dataItems.map(function (item,index) {
return (
<Hello key={index} firstname={item} lastname={item2} telegram={item3}/>
);
}, this)}
<AddItem addItem={this.addItem.bind(this)} />
</div>
)
}
}
class Hello extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div className='form__row'>
<p className='form__input' > firstname: {this.props.firstname} </p>
<p className='form__input'> lastname: {this.props.lastname} </p>
<p className='form__input'> telegram: {this.props.telegram} </p>
</div>;
}
}
class AddItem extends React.Component{
handleClick(){
this.props.addItem(this.item.value);
}
render(){
return (
<div className='form__row'>
<div>
<label >firstname</label>
<input className='form__input' type="text" ref={item => this.item=item} />
<label >lastname</label>
<input className='form__input' type="text" ref={item2 => this.item=item2} />
<label >telegram</label>
<input className='form__input' type="text" ref={item3 => this.item=item3} />
</div>
<button onClick={this.handleClick.bind(this)}> add new in state</button>
</div>
);
}
}
export default Table;
There are a couple important things you got wrong. I'll break them down for you:
In your AddItem component, you are using an uncontrolled component, but overriding the same property this.item over and over again whenever setting the values for each form field. In this case, because it is the last one, telegrams value is the final one remaining. So it is the only one being set. It would be the same thing as trying to set three different value for the same variable.
item.value = 1
item.value = 2
item.value = 3
If you used this code snippet, obviously the only value of item will be 3. So we first fix this component by associating different properties to each form:
// AddItem component's render function
render(){
return (
<div className='form__row'>
<div>
<label >firstname</label>
<input className='form__input' type="text" ref={item => this.item=item} />
<label >lastname</label>
<input className='form__input' type="text" ref={item2 => this.item2=item2} />
<label >telegram</label>
<input className='form__input' type="text" ref={item3 => this.item3=item3} />
</div>
<button onClick={this.handleClick.bind(this)}> add new in state</button>
</div>
);
}
On another note, I highly recommend actually giving your three items meaningful names. So I would switch them to firstName, lastName and telegram specifically.
Your handleClick also suffers from the same problem: You were passing the same this.item.value, meaning you were just passing your last set field. For you to pass all three of them while keeping the function in it's same format, you need to create an object with all three of your item.value. With that change, it should now look like this:
handleClick(){
const value = {item: this.item.value, item2: this.item2.value, item3: this.item3.value}
this.props.addItem(value);
}
Finally, in your Table component, some changes are needed in its render function. It looks like you are confusing the item you named in your map function with item you passed in. Again, you should probably find a more meaningly names for your variables so that doesn't happen.
The item in the map function, is each item of the array you are mapping through. In this case, it would be just the object with all three of your fields. You actually want the item fields from the item of the map function.
{this.state.dataItems.map(function (item,index) {
return (
<Hello key={index} firstname={item.item} lastname={item.item2} telegram={item.item3}/>
);
}, this)}
Here is a working version of your app:
class Table extends React.Component{
constructor (props) {
super(props);
this.state = {
data: [],
dataItems: []
}
}
addItem(value) {
let newListItems = this.state.dataItems.slice();
console.warn(newListItems)
newListItems.push(value);
this.setState({
dataItems : newListItems
});
}
render() {
return (
<div>
{this.state.dataItems.map(function (item,index) {
return (
<Hello key={index} firstname={item.item} lastname={item.item2} telegram={item.item3}/>
);
}, this)}
<AddItem addItem={this.addItem.bind(this)} />
</div>
)
}
}
class Hello extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div className='form__row'>
<p className='form__input' > firstname: {this.props.firstname} </p>
<p className='form__input'> lastname: {this.props.lastname} </p>
<p className='form__input'> telegram: {this.props.telegram} </p>
</div>;
}
}
class AddItem extends React.Component{
handleClick(){
const value = {item: this.item.value, item2: this.item2.value, item3: this.item3.value}
this.props.addItem(value);
}
render(){
return (
<div className='form__row'>
<div>
<label >firstname</label>
<input className='form__input' type="text" ref={item => this.item=item} />
<label >lastname</label>
<input className='form__input' type="text" ref={item2 => this.item2=item2} />
<label >telegram</label>
<input className='form__input' type="text" ref={item3 => this.item3=item3} />
</div>
<button onClick={this.handleClick.bind(this)}> add new in state</button>
</div>
);
}
}
ReactDOM.render(
<Table />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Resources