Highly reusable react component without any code change and use only properties - reactjs

I would like to create a component which I can reuse also for other projects without any code change (only by passing different properties).
A small example:
I have a login form which has a username input and a password input which can look for example like this:
class Login extends Component {
static propTypes = {
login: PropTypes.func.isRequired,
defaultEmailValue: PropTypes.string,
defaultPasswordValue: PropTypes.string,
};
handleSubmit = (event) => {
if (event) {
event.preventDefault();
}
const username = this.refs.username;
const password = this.refs.password;
this.props.login(username.value, password.value);
};
render() {
const {
defaultEmailValue,
defaultPasswordValue,
} = this.props;
return (
<form method="post">
<input defaultValue={defaultEmailValue} ref="username" type="email" />
<input defaultValue={defaultPasswordValue} ref="password" type="password" />
<button onClick={this.handleSubmit} type="submit">Submit</button>
</form>
);
}
}
This is the minimal version of a login form, but what to do when I want to extend the render function to add container components like this (added column tags):
class Login extends Component {
/*....*/
render() {
const {
defaultEmailValue,
defaultPasswordValue,
} = this.props;
return (
<form method="post">
<div class="row">
<div class="col-md-6">
<input defaultValue={defaultEmailValue} ref="username" type="email" />
</div>
<div class="col-md-6">
<input defaultValue={defaultPasswordValue} ref="password" type="password" />
</div>
</div>
<button onClick={this.handleSubmit} type="submit">Submit</button>
</form>
);
}
}
Therefore I have always to modify the render function. Is there a good possibility to do this only with properties?
I estimated to do this with a Wrapper-Component over the Login component but then I have always rewrite the complete render function.
The next possible solution I thought about is to pass wrapper component classes through properties which can be rendered. But is this so a good solution or is this bad practice?
Unfortunately, I found no real solution for this tutorial in the internet and therefore I try it here. Thanks in advance :-)

Is there a good possibility to do this only with properties?
Representational Components are exactly for this kind of things.
export default const LoginForm = (/**props**/ {username, email, submitHandler, errors}) => {
return (
<form>
{/* as usual */}
</form>
)
}
use in your ContainerComponent:
...
render() {
return (
<div className="loginWrapper">
<LoginForm ...this.props ...this.state ...whatever username="pete" />
</div>
)
}
...

Related

React form w/ custom validation submit to PHP

How can I add basic validation to this form in React in which prevents default submit. I have found many examples but none that help me to implement the validation to my existing code.
``import React from "react";
export default function App() {
return (
<form method="POST" action="/example.php">
<input name="Username" required type="text" />
<input required name="Password" type="Password" />
<button type="submit" className="btn-login" />
</form>
);
}``
First you need to handle onSubmit and call event.preventDefault()— this is to prevent the default submit:
export default function App() {
handleSubmit = event => {
event.preventDefault();
}
return (
<form onSubmit={ handleSubmit }>
...
</form>
);
}
After that, you can use the Constraint Validation API. Essentially it works by checking that the input provided by the user satisfies all the constraints you define in your HTML.
For example, let's say you'd like the Username to always be present and only contain upper and lowercase letters.
Here's a full example in React (browser):
function App() {
const handleSubmit = event => {
event.preventDefault();
const form = event.target;
const nameInput = document.querySelector('[name="Username"]');
nameInput.addEventListener('input', () => {
nameInput.setCustomValidity('');
nameInput.checkValidity();
});
nameInput.addEventListener('invalid', () => {
if (nameInput.value === '') {
nameInput.setCustomValidity('Enter your username!');
} else {
nameInput.setCustomValidity('Usernames can only contain upper and lowercase letters. Try again!');
}
});
}
return (
<form method="POST" action="/example.php" onSubmit={ handleSubmit }>
<input name="Username" required type="text" pattern="[A-Za-z]+" />
<input required name="Password" type="Password" />
<input type="submit" className="btn-login" />
</form>
);
}
ReactDOM.render(
<App />,
document.getElementById("react")
);
<div id="react"></div>
<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>
Notice how I added pattern="[A-Za-z]+" to the Username field. Simple and declarative.
By the way, you may also want to use AJAX to POST the data to /example.php—nowadays you can use the Fetch API, it's present in all modern browsers.

state value is undefined on Redirect

I need to pass data to another page using react redirect method, but I'm getting undefined value of state.
Form Component:
onSubmit I'm getting values of state through console.log but these values are not passing to Show component
class RegisterForm extends React.Component{
//constructor here
// handleInputChange function here
submit(){
console.log(this.state);
const data = this.state;
return <Redirect to={{
pathname: '/show',
state: {data: data},
}}
/>
}
render(){
return(
<div>
<div class="row">
<div class="col-md-6 offset-md-3">
<br /><br />
<h3>Register Form</h3><br />
<form
action="show"
>
<div class="form-row">
<div class="form-group col-md-6">
<label>First Name :</label>
<input type="text" class="form-control" name="first_name" onChange={this.handleInputChange} />
</div>
<div class="form-group col-md-6">
<label>Last Name :</label>
<input type="text" class="form-control" name="last_name" onChange={this.handleInputChange} />
</div>
</div>
<div class="form-row">
<div class="col-md-12 text-center">
<button type="submit" class="btn btn-primary" onClick={()=>this.submit()}>Submit</button>
</div>
</div>
</form>
</div>
</div>
</div>
)
}
}
export default RegisterForm;
props.location.state // undefined
can you help me?
Form action - Attribute for form submission
The URL that processes the form submission.
Question
It redirects to show page because I've used action="show" can you
tell me why submit function is not calling
The submit function isn't linked to anything in the UI. The form has a button of type "submit", so when clicked the default form actions are taken, i.e. the form is submitted and tries to go to the page specified by the action. React doesn't quite work in this way.
Since the button type is already "submit" you can simply replace the action prop with the onSubmit callback.
<form onSubmit={submit}> ...
Now that the submit callback handler is being invoked you'll also want to not take the default submit action on the form (likely a page reload). Returning JSX won't work here either, it won't ever get rendered. In order to do the redirect you'll need to do this using the history object from the Router/Route. This assumes RegisterForm is rendered directly as component or render prop of a Route or has been decorated with the withRouter Higher Order Component.
submit(event) {
event.preventDefault();
console.log(this.state);
const data = this.state;
const { history } = this.props;
history.replace({
pathname: '/show',
state: { data },
});
}
I would recommend you to use Function Components insted of class components,
Class components are too old, Life becomes too much easier with function components, and react hooks. I suggest you to go though react fucntion components and react hooks.
here I converted your class component into the function component with some hooks,
in submit method, just use history.push('/show',state).
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
const RegisterForm = () => {
const [state, setState] = useState({
first_name: "",
last_name: "",
});
const history = useHistory();
const handleInputChange = (key) => (e) => {
let value = e.target.value;
setState((s) => ({ ...s, [key]: value }));
};
const handleSubmit = (e) => {
history.push("/show", state);
};
return (
<div>
<div className="row">
<div className="col-md-6 offset-md-3">
<br />
<br />
<h3>Register Form</h3>
<br />
<div className="form-row">
<div className="form-group col-md-6">
<label>First Name :</label>
<input
type="text"
className="form-control"
name="first_name"
value={state.first_name}
onChange={handleInputChange("first_name")}
/>
</div>
<div className="form-group col-md-6">
<label>Last Name :</label>
<input
type="text"
className="form-control"
name="last_name"
value={state.last_name}
onChange={handleInputChange("last_name")}
/>
</div>
</div>
<div className="form-row">
<div className="col-md-12 text-center">
<button
className="btn btn-primary"
onClick={handleSubmit}
>
Submit
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default RegisterForm;
Now, In the component at /show route,
you can use,
import { useLocation } from "react-router-dom";
....
....
// inside the Component,
let location = useLocation();
console.log(location);
console.log(location.state); // you can see, your state is available here...
that's it!
let me know if you found some difficulties here.
good luck ;)

Adding a form to Gatsby JS, with an existing template that is export default

I am attempting to follow this tutorial to add a form to Gatsby JS. I understand it if my file wasn't setup differently. Firstly the tutorials component starts like this
export default class IndexPage extends React.Component {
Where I have this
export default ({ data }) => (
Then I am asked to place the following inside of it. I tried with both the render and return portion, and without.
state = {
firstName: "",
lastName: "",
}
handleInputChange = event => {
const target = event.target
const value = target.value
const name = target.name
this.setState({
[name]: value,
})
}
handleSubmit = event => {
event.preventDefault()
alert(`Welcome ${this.state.firstName} ${this.state.lastName}!`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
First name
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
/>
</label>
<label>
Last name
<input
type="text"
name="lastName"
value={this.state.lastName}
onChange={this.handleInputChange}
/>
</label>
<button type="submit">Submit</button>
</form>
)
}
Here is all my code without the render and return portion
import React from 'react'
import { HelmetDatoCms } from 'gatsby-source-datocms'
import { graphql } from 'gatsby'
import Layout from "../components/layout"
export default ({ data }) => (
<Layout>
state = {
firstName: "",
lastName: "",
}
handleInputChange = event => {
const target = event.target
const value = target.value
const name = target.name
this.setState({
[name]: value,
})
}
handleSubmit = event => {
event.preventDefault()
alert(`Welcome ${this.state.firstName} ${this.state.lastName}!`)
}
<form onSubmit={this.handleSubmit}>
<label>
First name
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleInputChange}
/>
</label>
<label>
Last name
<input
type="text"
name="lastName"
value={this.state.lastName}
onChange={this.handleInputChange}
/>
</label>
<button type="submit">Submit</button>
</form>
<article className="sheet">
<HelmetDatoCms seo={data.datoCmsPricing.seoMetaTags} />
<section className="left-package-details">
<h1 className="sheet__title">{data.datoCmsPricing.title}</h1>
<p>
<span>${data.datoCmsPricing.priceAmount}</span> | <span>{data.datoCmsPricing.lengthOfSession}</span>
</p>
{data.datoCmsPricing.details.map(detailEntry => { return <li key={detailEntry.id}> {detailEntry.task}</li>})}
<p>
{data.datoCmsPricing.numberOfSessions}
</p>
book
<p>{data.datoCmsPricing.minimumMessage}</p>
</section>
<section className="right-package-details">
<img src={data.datoCmsPricing.coverImage.url} />
<div
className=""
dangerouslySetInnerHTML={{
__html: data.datoCmsPricing.descriptionNode.childMarkdownRemark.html,
}}
/>
</section>
</article>
</Layout>
)
export const query = graphql`
query WorkQuery($slug: String!) {
datoCmsPricing(slug: { eq: $slug }) {
seoMetaTags {
...GatsbyDatoCmsSeoMetaTags
}
title
priceAmount
details{
task
}
lengthOfSession
numberOfSessions
minimumMessage
descriptionNode {
childMarkdownRemark {
html
}
}
coverImage {
url
}
}
}
`
and the error I get is
There was a problem parsing "/mnt/c/Users/Anders/sites/jlfit-cms/src/templates/pricingDetails.js"; any GraphQL
fragments or queries in this file were not processed.
This may indicate a syntax error in the code, or it may be a file type
that Gatsby does not know how to parse.
File: /mnt/c/Users/Anders/sites/jlfit-cms/src/templates/pricingDetails.js
The problem you are facing is because you are trying to use state (and setState) on a functional component when the example uses a class.
Functional components don't have the same tools/syntax/APIs available to you as a class component (for better or worse) so you have to ensure you're using the correct approach for each case.
In the most recent versions of React you can have the equivalent of state and setState made available to you by using React hooks, more specifically the useState hook.
I've put together a quick working example of the code you pasted in your question converted to React hooks. You can find it on this sandbox.
I recommend you have a read over the initial parts of the React docs to ensure you're familiar with the foundational concepts or React, it will save a lot of headache in the future. 🙂

ReactJS On submit change classes of button and input field

I have a form and when I "Submit" the form I want to add an attribute and some extra classes to the "submit" button and the input field
This is my handleSubmit function
handleSubmit = event => {
event.preventDefault();
const formData = new FormData(event.target);
axios.post(`MyPostUrl`,formData)
.then(res => {
})
}
This is my form
<form onSubmit={this.handleSubmit} method="POST">
<div className="form-row">
<input required min="1" max="10" name="grade" className="form-control col-md-5" type="number" />
<button className="btn btn-outline-primary col-md-6">
Grade
</button>
</div>
</form>
So in let's say jQuery i could just go $(this).find("someClass") and do what ever i need to do with it. How can i achieve this with React?
What I'm trying to do is change the input class to col-md-12 and add an disabled attribute and I want to remove the button on submit
And I have a lot of forms since I've mapped over an object
Consider an example like this: https://codesandbox.io/s/throbbing-bird-ob89o
The idea is to use your component-state to control what classes, styles and attributes to use for your markup.
In this case, we define a submitted state and depending on its Boolean-value, we can use ternary operators to toggle the code we want to render.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
grade: "",
submitted: false
};
handleSubmit = e => {
e.preventDefault();
this.setState({
submitted: true
});
};
handleOnChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
render() {
const { submitted, grade } = this.state;
return (
<form onSubmit={this.handleSubmit} method="POST">
<div className="form-row">
<input
required
onChange={this.handleOnChange}
min="1"
max="10"
name="grade"
className={`form-control ${submitted ? "col-md-12" : "col-md-5"}`}
value={grade}
type="number"
disabled={submitted}
/>
{!submitted ? (
<button className="btn btn-outline-primary col-md-6">Grade</button>
) : (
""
)}
</div>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
When you submit the form, we toggle the submitted state to true. Our component re-renders and that recalculates all the ternary operators in our mark-up like ${submitted ? "col-md-12" : "col-md-5"} and etc.
You would have to use react states for managing classes too.
e.g:
<button className={this.state.buttonClass}>
Grade
</button>
Better yet, create a wrapper component around it so that these actions can be controlled via props *e.g disabled={true} would add class

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} />

Resources