I have a form in react that I'm refactoring. I'm going to move most of the state and logic to the parent, because the parent state will be updated with the form result... but I was going to refactor before and can't seem to get a switch statement to work. I was told it would help performance in the long run.
The Validate function is where I'm trying to add a switch statement.
import React from 'react'
import styles from './style.addLibForm.css'
class AddLibForm extends React.Component {
constructor(props) {
super(props);
this.state = {
input: {
title: "",
content: "",
imgURL: ""
},
blurred: {
title: false,
content: false,
imgURL: ""
},
formIsDisplayed: this.props.toggle
};
}
handleInputChange(newPartialInput) {
this.setState(state => ({
...state,
input: {
...state.input,
...newPartialInput
}
}))
}
handleBlur(fieldName) {
this.setState(state => ({
...state,
blurred: {
...state.blurred,
[fieldName]: true
}
}))
}
***//TURN INTO SWITCH STATEMENT!***
validate() {
const errors = {};
const {input} = this.state;
if (!input.title) {
errors.title = 'Title is required';
} //validate email
if (!input.content) {
errors.content = 'Content is required';
}
if (!input.imgURL) {
errors.imgURL = 'Image URL is required';
}
console.log(Object.keys(errors).length === 0);
return {
errors,
isValid: Object.keys(errors).length === 0
};
}
render() {
const {input, blurred} = this.state;
const {errors, isValid} = this.validate();
return (
<div className="flex">
<form
className={styles.column}
onSubmit={(e) =>
{ e.preventDefault();
this.setState({})
return console.log(this.state.input);
}}>
<h2> Add a library! </h2>
<label>
Name:
<input
className={styles.right}
name="title"
type="text"
value={input.title}
onBlur={() => this.handleBlur('title')}
onChange={e => this.handleInputChange({title: e.target.value})}/>
</label>
<br/>
<label>
Content:
<input
className={styles.right}
name="content"
type="text"
value={input.content}
onBlur={() => this.handleBlur('content')}
onChange={e => this.handleInputChange({content: e.target.value})}/>
</label>
<br/>
<label>
Image URL:
<input
className={styles.right}
name="imgURL"
type="text"
value={input.imgURL}
onBlur={() => this.handleBlur('imgURL')}
onChange={e => this.handleInputChange({imgURL: e.target.value})}/>
</label>
<br/>
<p>
<input className={styles.button} type="submit" value="Submit" disabled={!isValid}/>
</p>
{/* CSS THESE TO BE INLINE WITH INPUTS */}
<br/>
{blurred.content && !!errors.content && <span>{errors.content}</span>}
<br/>
{blurred.title && !!errors.title && <span>{errors.title}</span>}
<br/>
{blurred.imgURL && !!errors.imgURL && <span>{errors.imgURL}</span>}
</form>
</div>
);
}
}
export default AddLibForm
I've was putting the switch statement inside the validate function. I tried inputs, errors, this.state.input, this.state.errors, {input}... what am I missing?
Switch statement makes most sense when you need to compare one variable with different values
if(x === 1){
//...
} else if(x === 2) {
//...
} else if(x === 3) {
//...
} else {} {
//...
}```
=>
switch (x) {
case 1:
//...
break;
case 2:
//...
break;
case 3:
//...
break;
default:
//...
}
since it clearly expresses this "1 variable to many values" mapping condition.
In your case though you don't compare one var to many values, you simply check multiple variables for existence, i.e. semantically compare them with a truthy value, so switch will not make much sense.
It's better to leave as is since all these ifs are checking different conditions and are still pretty readable.
Overall you can check this article for a more verbose directions on approaching condition handling more efficiently.
Related
I have the following React Component, which holds a form with two inputs and a button.
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: null,
password: null
}
}
emailInputChanged = (e) => {
this.setState({
email: e.target.value.trim()
});
};
passwordInputChanged = (e) => {
this.setState({
password: e.target.value.trim()
});
};
loginButtonClicked = (e) => {
e.preventDefault();
if (!this.isFormValid()) {
//Perform some API request to validate data
}
};
signupButtonClicked = (e) => {
e.preventDefault();
this.props.history.push('/signup');
};
forgotPasswordButtonClicked = (e) => {
e.preventDefault();
this.props.history.push('/forgot-password');
};
isFormValid = () => {
const {email, password} = this.state;
if (email === null || password === null) {
return false;
}
return isValidEmail(email) && password.length > 0;
};
render() {
const {email, password} = this.state;
return (
<div id="login">
<h1 className="title">Login</h1>
<form action="">
<div className={(!isValidEmail(email) ? 'has-error' : '') + ' input-holder'}>
<Label htmlFor={'loginEmail'} hidden={email !== null && email.length > 0}>email</Label>
<input type="text" className="input" id="loginEmail" value={email !== null ? email : ''}
onChange={this.emailInputChanged}/>
</div>
<div className={(password !== null && password.length === 0 ? 'has-error' : '') + ' input-holder'}>
<Label htmlFor={'loginPassword'}
hidden={password !== null && password.length > 0}>password</Label>
<input type="password" className="input" id="loginPassword"
value={password !== null ? password : ''}
onChange={this.passwordInputChanged}/>
</div>
<button type="submit" className="btn btn-default" id="loginButton"
onClick={this.loginButtonClicked}>
login
</button>
</form>
<div className="utilities">
<a href={'/signup'} onClick={this.signupButtonClicked}>don't have an account?</a>
<a href={'/forgot-password'} onClick={this.forgotPasswordButtonClicked}>forgot your password?</a>
</div>
</div>
)
}
}
export function isValidEmail(email) {
const expression = /\S+#\S+/;
return expression.test(String(email).toLowerCase());
}
The values of the inputs are stored in the component's state. I have given them initial value of null and update them using setState() in the onChange event.
In the render() method I use the state to colour inputs with invalid values. Currently I am only checking the email value against a simple regex and the password to be at least 1 character in length.
The reason I have set the initial values of the state variables to null so I can check in the layout and the initial style on the input to not be marked as "has-errors". However I need to extend the check to:
this.state.email !== null && this.state.email.length === 0
in order to work, because null has no length property.
Is there a cleaner and "React-ish" way to achieve this:
- initial state of the div holding the inputs has no class has-errors
- less checks when setting the value attribute on the input (because React doesn't accept null as value of the attribute)
Edit:
If the initial value of this.state.email and this.state.password are empty strings, I get has-error applied to the div holding the inputs. In my opinion this is bad UX, because the user hasn't done anything and he is already wrong.
The hidden attribute is used by a custom component I made, which acts like "placeholder" (gets erased if something is typed in the input).
Video below showing how my form looks like when this.state.email and this.state.password are empty strings as well how my <Label> component works:
You could create a validate function which return an errors object to know which fields are in error, and use empty strings as initial values. I don't understand what you are trying to do with the hidden attribute.
Edit: add a touched property in the state, to know which field have been touched.
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
touched: {},
};
}
emailInputChanged = e => {
this.setState({
email: e.target.value.trim(),
touched: {
...this.state.touched,
email: true,
},
});
};
passwordInputChanged = e => {
this.setState({
password: e.target.value.trim(),
touched: {
...this.state.touched,
password: true,
},
});
};
loginButtonClicked = e => {
e.preventDefault();
if (!this.isFormValid()) {
//Perform some API request to validate data
}
};
isFormValid = () => {
const errors = this.validate();
return Object.keys(errors).length === 0;
};
validate = () => {
const errors = {};
const { email, password } = this.state;
if (!isValidEmail(email)) {
errors.email = true;
}
if (password.length === 0) {
errors.password = true;
}
return errors;
};
render() {
const { email, password, touched } = this.state;
const errors = this.validate();
return (
<div id="login">
<h1 className="title">Login</h1>
<form action="">
<div
className={
(errors.email && touched.email ? 'has-error' : '') +
' input-holder'
}
>
<Label htmlFor={'loginEmail'}>email</Label>
<input
type="text"
className="input"
id="loginEmail"
value={email}
onChange={this.emailInputChanged}
/>
</div>
<div
className={
(errors.password && touched.password ? 'has-error' : '') +
' input-holder'
}
>
<Label htmlFor={'loginPassword'}>password</Label>
<input
type="password"
className="input"
id="loginPassword"
value={password}
onChange={this.passwordInputChanged}
/>
</div>
<button
type="submit"
className="btn btn-default"
id="loginButton"
onClick={this.loginButtonClicked}
>
login
</button>
</form>
</div>
);
}
}
export function isValidEmail(email) {
const expression = /\S+#\S+/;
return expression.test(String(email).toLowerCase());
}
I want to add a red border only if an input is empty. I couldn't find a way to "addClass" in React so I'm using state. Right now the code will add red border to all inputs, even if it has text.
State:
this.state = {
inputBorderError: false,
};
HTML/JSX:
<label>Name</label>
<input className={
this.state.inputBorderError ? 'form-input form-input-fail' : 'form-input'
} />
<label>Email</label>
<input className={
this.state.inputBorderError ? 'form-input form-input-fail' : 'form-input'
} />
<label>Message</label>
<textarea className={
this.state.inputBorderError ? 'form-input form-input-fail' : 'form-input'
} />
CSS:
.form-input-fail {
border: 1px solid red;
}
JS:
let inputFields = document.getElementsByClassName('form-input');
for (var i = 0; i < inputFields.length; i++) {
if (inputFields[i].value === '') {
this.setState({
inputBorderError: true,
});
}
}
I see the error in my code as it's basically setting the state to true anytime it finds an empty input. I think I may be approaching this incorrectly as there's only one state. Is there a solution based on my state approach, or is there another solution?
Right now, you have single state-value that affects all inputs, you should consider having one for each input. Also, your inputs are not controlled, it will be harder to record and track their values for error-handling.
It is good practice to give each input tag a name property. Making it easier to dynamically update their corresponding state-value.
Try something like the following, start typing into each input, then remove your text: https://codesandbox.io/s/nervous-feynman-vfmh5
class App extends React.Component {
state = {
inputs: {
name: "",
email: "",
message: ""
},
errors: {
name: false,
email: false,
message: false
}
};
handleOnChange = event => {
this.setState({
inputs: {
...this.state.inputs,
[event.target.name]: event.target.value
},
errors: {
...this.state.errors,
[event.target.name]: false
}
});
};
handleOnBlur = event => {
const { inputs } = this.state;
if (inputs[event.target.name].length === 0) {
this.setState({
errors: {
...this.state.errors,
[event.target.name]: true
}
});
}
};
handleOnSubmit = event => {
event.preventDefault();
const { inputs, errors } = this.state;
//create new errors object
let newErrorsObj = Object.entries(inputs)
.filter(([key, value]) => {
return value.length === 0;
})
.reduce((obj, [key, value]) => {
if (value.length === 0) {
obj[key] = true;
} else {
obj[key] = false;
}
return obj;
}, {});
if (Object.keys(newErrorsObj).length > 0) {
this.setState({
errors: newErrorsObj
});
}
};
render() {
const { inputs, errors } = this.state;
return (
<div>
<form onSubmit={this.handleOnSubmit}>
<label>Name</label>
<input
className={
errors.name ? "form-input form-input-fail" : "form-input"
}
name="name"
value={inputs.name}
onChange={this.handleOnChange}
onBlur={this.handleOnBlur}
/>
<label>Email</label>
<input
className={
errors.email ? "form-input form-input-fail" : "form-input"
}
name="email"
value={inputs.email}
onChange={this.handleOnChange}
onBlur={this.handleOnBlur}
/>
<label>Message</label>
<textarea
className={
errors.message ? "form-input form-input-fail" : "form-input"
}
name="message"
value={inputs.message}
onChange={this.handleOnChange}
onBlur={this.handleOnBlur}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
You are correct that there is only one state.
What you need to do is store a separate error for each input. one way to do this is with a set or array on state like state = {errors: []} and then check
<label>Name</label>
<input className={
this.state.errors.includes('name') ? 'form-input form-input-fail' : 'form-input'
} />
<label>Email</label>
<input className={
this.state.errors.includes('email') ? 'form-input form-input-fail' : 'form-input'
} />
} />
You should keep track of the input value in the state instead of checking for borderStyling state only.
Base on your code, you could refactor it to something like this:
// keep track of your input changes
this.state = {
inputs: {
email: '',
name: '',
comment: '',
},
errors: {
email: false,
name: false,
comment: false,
}
};
// event handler for input changes
handleChange = ({ target: { name, value } }) => {
const inputChanges = {
...state.inputs,
[name]: value
}
const inputErrors = {
...state.errors,
[name]: value == ""
}
setState({
inputs: inputChanges,
errors: inputErrors,
});
}
HTML/JSX
// the name attribut for your input
<label>Name</label>
<input name="name" onChange={handleChange} className={
this.errors.name == "" ? 'form-input form-input-fail' : 'form-input'
} />
<label>Email</label>
<input name="email" onChange={handleChange} className={
this.errors.email == "" ? 'form-input form-input-fail' : 'form-input'
} />
<label>Message</label>
<textarea name="comment" onChange={handleChange} className={
this.errors.comment == "" ? 'form-input form-input-fail' : 'form-input'
} />
And if you are probably looking at implementing it with CSS and js, you can try this article matching-an-empty-input-box-using-css-and-js
But try and learn to make your component reusable and Dry, because that is the beginning of enjoying react app.
[Revised]
I've created this method that gets the state of the calculator input and checks if its empty or not. I need help with two things:
What's the cleanest way to add here a validation to check if each input is also a number and outputs and error "Input must be a number"
Currently I have one error message that fires whether all the inputs are present or not, where what I want is for it to validate each input separately and fire an error under each one. How do I do it but still keep this function concise?
constructor(props) {
super(props);
this.state = {
price: 0,
downP: 0,
term: 0,
interest: 0,
error: ''
};
}
handleValidation = () => {
const {
price,
downP,
loan,
interest,
} = this.state;
let error = '';
let formIsValid = true;
if(!price ||
!downP ||
!loan ||
!interest){
formIsValid = false;
error = "Input fields cannot be empty";
}
this.setState({error: error});
return formIsValid;
}
And then this is the error message
<span style={{color: "red"}}>{this.state.error}</span>
If you want to keep your error messages separate I would recommend to reorganize your state.
So scalable solution (you may add more controls by just adding them to state) may look like:
class NumberControlsWithErrorMessage extends React.Component {
constructor(props) {
super(props);
this.state = {
inputs: [
{ name: 'price', value: 0, error: ''},
{ name: 'downP', value: 0, error: '' },
{ name: 'term', value: 0, error: '' },
{ name: 'interest', value: 0, error: '' }
]
};
}
handleInputChange = (idx, event) => {
const target = event.target;
const name = target.name;
let error = '';
if (isNaN(target.value)) {
error = `${name} field can only be number`
}
if (!target.value) {
error = `${name} field cannot be empty`
}
this.state.inputs[idx] = {
...this.state.inputs[idx],
value: target.value,
error
}
this.setState({
inputs: [...this.state.inputs]
});
}
render() {
return (
<form>
{this.state.inputs.map((input, idx) => (
<div>
<label htmlFor="">{input.name}</label>
<input type="text" value={input.value} onChange={(e) => this.handleInputChange(idx, e)}/>
{input.error && <span>{input.error}</span> }
</div>
))}
</form>
);
}
}
Working example
Also if you are building a complex form, you may want to try some React solution for forms, where all the mechanism for listening to events, state updates, validatoin are already handled for you. Like reactive-mobx-form
A straightforward way of handling multiple objects needing validation is to store an errors object in your state that has a property for each input field. Then you conditionally render the error underneath each input field depending on whether or not it has an error. Here is a very basic example:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {
price: 0, downP: 0, term: 0, interest: 0,
errors: { price: '', downP: '', term: '', interest: '' }
};
}
handleValidation = () => {
const { price, downP, loan, interest } = this.state;
let errors = { price: '', downP: '', term: '', interest: '' };
if (!price) {
errors.price = 'Price is required';
} else if (isNaN(price)) {
errors.price = 'Price must be a number';
}
if (!downP) {
errors.downP = 'Down Payment is required';
}
// Rest of validation conditions go here...
this.setState({ errors });
}
render() {
const { errors } = this.state;
return (
<form>
<input name="price" value={this.state.price} onChange={this.handleChange} />
{errors.price != '' && <span style={{color: "red"}}>{this.state.errors.price}</span>}
<input name="downP" value={this.state.downP} onChange={this.handleChange} />
{errors.downP != '' && <span style={{color: "red"}}>{this.state.errors.downP}</span>}
{/** Rest of components go here */}
</form>
);
}
}
You can choose whether or not to run validation once the form submits or on every keypress and that will affect how when those messages appear and disappear, but this should give you an idea on how you would manage error messages specific to each input field.
You can do this:
handleValidation() {
const { price, downP,loan, interest} = this.state;
// only each block with generate error
if (!price || isNaN(price)) {
this.setState({ error: 'price is not valid' });
} else if (!downP || isNaN(downP)) {
this.setState({ error: 'downP is not valid' });
} else {
this.setState({error: ""})
// submit code here
}
}
Note: you dont need to return anything. It will update the error state and only submit the form if it goes into no error (else{}) part
and for render() part add this:
{(this.state.error !== '')
? <span style={{color: "red"}}>{this.state.error}</span>
: ''
}
If you want validation msg on each add errPrice, errDownP and so on to the state, and check for them in render like (this.state.errPrice!== '') {} and so on.
One solution assuming you want a one size fits all error message only checking if it was a number or not would be to put them into an array and set error if the input is not a number.
const inputs = [ price, downP, loan, interest ]
inputs.map(input => {
if (!input || isNaN(input)){
error = "Input must be a number"
formIsValid = false
}
}
this.setState({error})
Something like that maybe.
I tried a lot of ways to make it works but it didn't. Probably I don't understand the React idea or I missed something. My child via callback try to modify the global/parent state and then I got the well known error.
index.js:1452 Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
I try to use variable _isMounted so before setState I did:
if (this.isMounted()) {
this.setState({...});
}
Unfortunately it doesn't work.
The problem is when the function this.handleChangeAlgorithms is invoked from the child.
My code
class ConfigurationForm extends Component {
constructor(props) {
super(props);
this.state = {
choosenAlgorithm: null,
htmlType: null,
customHtml: null,
isOpenModal: false,
generatedHTMlConfig: {
headers: 1,
paragraphs: 1,
buttons: 1,
links: 1,
inputs: 1,
images: 1,
},
};
}
handleChangeAlgorithms = event => {
let algorithmName = event.target.value;
let newChoosenAlgorithm = this.props.algorithms.filter(
e => e.value === algorithmName,
)[0];
// this.setState({choosenAlgorithm: newChoosenAlgorithm});
this.props.callbackConfigurationForm(algorithmName);
};
handleChangeHtmlType(event) {
let newHtmlType = event.target.value;
console.log(newHtmlType);
if (newHtmlType === "default") {
this.setState({ isOpenModal: true });
} else {
this.setState({ htmlType: newHtmlType });
}
}
handleUserFile(event) {
let file = event.target.files[0];
this.setState({ customHtml: file });
this.props.callbackConfigurationFormPreview(file);
}
handleSubmit(event) {
event.preventDefault();
let htmlContent = null;
let htmltypeKey = Object.keys(HtmlType).find(
key => HtmlType[key] === HtmlType.default,
);
console.log(htmltypeKey);
// if (this.state.htmlType === 'default'){
// htmlContent = this.loadTemplate();
htmlContent = generateHTML(this.state.generatedHTMlConfig);
console.log(htmlContent);
// } else {
// htmlContent=this.state.customHtml;
// }
let config = {
algorithm: this.state.choosenAlgorithm,
html: htmlContent,
};
console.log(config);
this.props.callbackConfigurationForm(config);
}
loadTemplate() {
let loadedTemplate = null;
let file = "html-templates/example.html";
let request = new XMLHttpRequest();
request.open("GET", file, false);
request.send(null);
if (request.status === 200) {
loadedTemplate = request.responseText;
}
return loadedTemplate;
}
renderHtmlTypesList = () => {
let list = [];
for (const htmlKey of Object.keys(HtmlType)) {
list.push(
<option key={htmlKey} value={htmlKey}>
{HtmlType[htmlKey]}
</option>,
);
}
return list;
};
generateHTMLFromPopup() {
this.setState({ isOpenModal: false });
}
changeHeaders(event, type) {
console.log(type);
console.log(event.target.value);
let prevConfig = { ...this.state.generatedHTMlConfig };
switch (type) {
case "Headers":
prevConfig.headers = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Paragraphs":
prevConfig.paragraphs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Buttons":
prevConfig.buttons = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Links":
prevConfig.links = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Inputs":
prevConfig.inputs = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
case "Images":
prevConfig.images = event.target.value;
this.setState({ generatedHTMlConfig: prevConfig });
break;
}
}
render() {
return (
<Row>
{/* todo 12.11.2018 extract to another component! */}
<Modal
open={this.state.isOpenModal}
header="Generate Html"
actions={
<div>
<Button
modal="close"
waves="light"
className="red lighten-2"
>
Cancel
</Button>
<Button
modal="close"
waves="light"
className="blue"
onClick={this.generateHTMLFromPopup.bind(this)}
>
<Icon left>build</Icon>Generate
</Button>
</div>
}
>
<p>Choose HTML elements for generated HTML.</p>
<Input
type="number"
label="Headers"
value={this.state.generatedHTMlConfig.headers}
onChange={e => this.changeHeaders(e, "Headers")}
/>
<Input
type="number"
label="Paragraphs"
value={this.state.generatedHTMlConfig.paragraphs}
onChange={e => this.changeHeaders(e, "Paragraphs")}
/>
<Input
type="number"
label="Buttons"
value={this.state.generatedHTMlConfig.buttons}
onChange={e => this.changeHeaders(e, "Buttons")}
/>
<Input
type="number"
label="Links"
value={this.state.generatedHTMlConfig.links}
onChange={e => this.changeHeaders(e, "Links")}
/>
<Input
type="number"
label="Inputs"
value={this.state.generatedHTMlConfig.inputs}
onChange={e => this.changeHeaders(e, "Inputs")}
/>
<Input
type="number"
label="Images"
value={this.state.generatedHTMlConfig.images}
onChange={e => this.changeHeaders(e, "Images")}
/>
</Modal>
<h2>Algorithm</h2>
<Row>
<Input
s={12}
type="select"
label="Select algorithm"
defaultValue=""
onChange={this.handleChangeAlgorithms}
>
<option value="" disabled>
Choose an algorithm
</option>
{this.props.algorithms.map(item => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Input>
</Row>
{this.state.choosenAlgorithm ? (
<Collapsible popout>
<CollapsibleItem header="Details" icon="notes">
<ol>
{this.state.choosenAlgorithm.details.steps.map(
(step, index) => (
<li key={index}>{step}</li>
),
)}
</ol>
</CollapsibleItem>
</Collapsible>
) : null}
<h2>HTML to obfuscate</h2>
<Row>
<Input
s={12}
type="select"
label="HTML type"
defaultValue=""
onChange={this.handleChangeHtmlType}
>
<option value="" disabled>
Choose HTML
</option>
{this.renderHtmlTypesList()}
</Input>
</Row>
{this.state.htmlType === "custom" ? (
<Row>
<Input
type="file"
label="File"
onChange={this.handleUserFile}
/>
</Row>
) : null}
<div className="center-align">
<Button type={"button"} onClick={this.handleSubmit}>
Process<Icon left>autorenew</Icon>
</Button>
</div>
</Row>
);
}
}
class App extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
isMounted: false,
//export to enum
algorithms: [
{
name: "Html to Javascript",
value: "1",
details: {
steps: [
"Split HTML file line by line.",
"Replace white characters.",
"Create function which add lines to document using document.write function.",
],
},
},
{
name: "Html to Unicode characters",
value: "2",
details: {
steps: [
"Create js function encoding characters to Unicode characters.",
"Create decoding function.",
"Add output from decoding function to HTML.",
],
},
},
{
name: "Html to escape characters",
value: "3",
details: {
steps: [
"Change endcoding using escape javascript function.",
"Decode using unescape javascript function.",
"Add element to HTML.",
],
},
},
{
name:
"Using own encoding and decoding function. [NOT IMPLEMENTED YET]",
value: "4",
details: {
steps: [
"Encode HTML using own function.",
"Save encoded content into js variable.",
"Decode using own decoding function.",
"Add element to HTML document.",
],
},
},
{
name: "Combine above methods [NOT IMPLEMENTED YET]",
value: "5",
details: {
steps: ["To be done..."],
},
},
],
previewHtml: null,
obfuscationConfig: null,
activeTab: 1,
};
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
processDataFromConfigurationForm = config => {
console.info(config);
if (this._isMounted) {
this.setState({
test: Date.now(),
// obfuscationConfig: config,
// doObfuscation: true,
// activeTab:3,
// previewHtml: config.html
});
}
};
render() {
return (
<div>
<header className="App">
<h1>HTML obfuscator</h1>
</header>
<Tabs>
<Tab
title="Configuration"
active={this.state.activeTab === 1}
>
<ConfigurationForm
algorithms={this.state.algorithms}
config={this.state.obfuscationConfig}
callbackConfigurationForm={
this.processDataFromConfigurationForm
}
/>
</Tab>
<Tab
title="HTML Preview"
active={this.state.activeTab === 2}
disabled={!this.state.previewHtml}
>
<HTMLPreview previewHtml={this.state.previewHtml} />
</Tab>
<Tab
title="Result"
active={this.state.activeTab === 3}
disabled={!this.state.obfuscationConfig}
>
{this.state.obfuscationConfig ? (
<ObfuscationOutput
config={this.state.obfuscationConfig}
/>
) : null}
</Tab>
</Tabs>
</div>
);
}
}
export default App;
The error
Try to add a constructor to your parent class, indeed, you can initialize the component state before the first modification:
class App extends Component {
constructor(props) {
super(props);
this.state = {
test1: Date.now()
};
}
// here you can update the state as you want, for example
foobar(event) {
this.setState({test1: event.target.value });
}
}
Hope it can help you...
Setting the state inside of the lifecycle method componentDidMount() should do the trick.
Oh I didn't see that someone already suggested that, glad you got it working
I am dynamically building a form based on the state build in the constructor. I am having success building the outer html but the inner form html is not rendering. cAN SOMEONE POINT OUT WHAT i AM DOING WRONG HERE?
class Forms extends Component {
constructor(props) {
super(props);
this.state = {
enrollment: {
class: "form-style",
fieldsets: [{
id: "1",
title: "Company Enrollment Form",
formElements: [{
label: "Company Name:",
element: "input",
type: "text",
class: "",
name: "cName",
placeholder: "Your Company's Name *",
required: true,
disabled: false
}, {
label: "Company Type:",
element: "select",
type: "populateDDL",
class: "",
name: "sltCompanyType",
placeholder: "",
required: true,
disabled: false
}]
}]
}
}
}
render() {
let Content = null;
if (this.props.type === "enrollment") {
Content = <EnrollmentForm state={this.state.enrollment} />
} else if (this.props.type === "contact") {
Content = <ContactUsForm />
} else {
Content = <fourOhFour />
}
return (
<div className="container formContent">
{Content}
</div>
);
}
};
function EnrollmentForm(form) {
function renderFieldsets(fieldsets) {
if (fieldsets.length > 0) {
return fieldsets.map((fieldset, index) => (
<Fieldset key={index} set={fieldset} />
));
}
else return [];
}
function renderFormElements(formElements) {
if (formElements.length > 0) {
return formElements.map((formElement, i) => (
<FormElement key={i} set={formElement} />
));
}
else return [];
}
const FormElement = (props, index) => {
console.log(props);
/* ^^^ NOT APPEARING/LOGGING IN THE CONSOLE ^^^ */
if (props.tag === "input") {
return (
<input key={index} name={props.name} />
);
}else if (props.tag === "select") {
return (
<select key={index} />
);
}
};
const Fieldset = (props, index) => {
const elements = renderFormElements(props.set.formElements);
return (
<fieldset key={index}>
<legend>
<span className="number fa fa-address-card"></span>
{props.set.title}
</legend>
</fieldset>
);
};
const fieldsets = renderFieldsets(form.state.fieldsets);
return (
<form className={form.state.class}>
{fieldsets}
</form>
);
}
The FormElement variable is not returning the html... I need to return a different type of html element based on what the tag is in the data model. In the code I have successfully created the outer fieldset but when I go to insert the html elements inside the fieldset, it doesn't do anything. I put a console.log in the code block but apparently even when I try to use the "FormElement" the code isn't firing ...
You have bugs in your code.
Your FormElement should look like
const FormElement = (props, index) => {
console.log(props);
/* ^^^ NOT APPEARING/LOGGING IN THE CONSOLE ^^^ */
if (props.set.element === "input") {//changed from props.tags-- 1
return (
<input key={index} name={props.name} />
);
}else if (props.set.element === "select") {//changed from props.tags --1
return (
<select key={index} />
);
}
return <div>Something which is not select or input</div>; //added a fallback return. --1
};
The bugs that were fixed above are:
(1) props.tags changed to props.set.element. There was no tags component in the props. So none of the if blocks were being rendered and this led to the component not returning anything. This led to an error that said
FormElement(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
This was resolved by adding the fallback return statement (2).
Your FieldSets should look like
const Fieldset = (props, index) => {
const elements = renderFormElements(props.set.formElements);
return (
<fieldset key={index}>
<legend>
<span className="number fa fa-address-card"></span>
{props.set.title}
</legend>
<div>{elements}</div> // consumed the elements that was created in renderFormElements.
</fieldset>
);
};
The elements variable was never used in render, which led to no FormElements showing up. That was the only error fixed in the above code.
I would suggest cleaning up your code by
Converting EnrollmentForm to a class.
Moving FieldSet and FormElement to seperate classes or at least funtional components
That would make the logic a lot easier to debug in future.