React js - Updating state with array values and a line break - javascript

I have an array which is holding some errors "var HoldErrors". I am updating state in my React JS app:
this.setState({
message: HoldErrors
});
When output on screen, this array has become a string of error messages I am displaying to the end user which is great. But how do I separate each error message in state with a line-break?
For example, when I am building my array, I am trying to add a "br" tag after each item:
var HoldErrors = [];
Object.keys(data.errors).forEach(function(key){
HoldErrors.push(data.errors[key].msg + '<br>');
});
Obviously the "br" tag does not work in React like this.
So how can I put each error in the array on it's own line, when I am updating state? Cheers.
Edit: This is how I am rendering my component:
render() {
return (
<div className="Register">
<h1>Register</h1>
<form onSubmit={this.RegisterSubmit}>
<input type="email" ref="email" placeholder="Email address" />
<input type="text" ref="name" placeholder="Your name" />
<input type="password" ref="password" placeholder="Password" />
<input type="password" ref="passwordc" placeholder="Confirm password" />
<input type="submit" value="Register" />
</form>
<div className="validation-msg">{this.state.message}</div>
</div>
)
}

When you say
<div className="validation-msg">{this.state.message}</div>
the {this.state.message} part will be rendered by coercing it to a string (all text in the DOM ends up as a string).
Because message is an array, coercing it to a string is the same as joining all the elements it contains by coercing them individually to a string (in this case they are already strings) and adding a comma in between:
console.log(
['a', 'b','c'].toString() // logs: a,b,c
)
What you want to do is map over this array and convert each string into a block element itself, like a <div>:
<div className="validation-msg">
{this.state.message.map((m, i) => <div key={`message-${i}`}>m</div>)}
</div>
or an inline element such as <span> with a <br /> after each string
<div className="validation-msg">
{this.state.message.map((m, i) => <span key={`message-${i}`}>m<br /></span>)}
</div>
Note: Don't forget to add keys to your array elements.

What you can do here is if this.state.message is an array:
{
this.state.message.map((el, index) => {
<div className={validation-msg} key={index}>{el} <br /></div>
})
}
If you have something like message doesn't exist or things like that then you should:
{this.state.message && this.state.message.map...}
in your render method.

You need to use dangerouslySetInnerHTML to set html. Docs
<div className="validation-msg" dangerouslySetInnerHTML={{__html: this.state.message}}></div>
But a better option would be to store data in state and map it to html inside render function.

You are better off creating a helper function that will render each error message for you like so:
generateError(errorMessage, key) {
return(
<div className="validation-msg" key={`error-${key}`}>{errorMessage}</div>
);
}
This helper function you can use it to both test your code, and modify it separately in the future without it affecting your core code structure.
In your component you can use such helper function by wrapping it within a map that will grab each element in the messages array and feed it to your helper function:
<div className="validation-msg">
{
this.state.message && this.state.message.length > 0 ? this.state.message.map((msg, key) => {
return this.generateError(msg, key);
}) : null;
}
</div>

Related

onClick handler does not update innerHTML of elements

I have an onClick function that is attached to rows of a table by the characterID that is provided from an array of objects (instantiated from a constructor function).
I'm using styled-components, hence the odd tag names further down.
Here's the function:
//this is an onClick function for use in record rows in the JSX populated table for characters
function barkCharData(idIn){
console.log("==========================");
try{
//Get the fields...
let idField = document.getElementById("txt_idField");
let nameField = document.getElementsByName("first_name_field");
//Set the fields
idField.innerHTML = String(ar_charBin[idIn-1].id);
console.log("\n---> idField: "+idField);
console.log("---> idField.innerHTML: "+idField.innerHTML+"\n")
nameField.innerHTML = String(ar_charBin[idIn-1].id);
console.log("\n---> nameField: "+nameField);
console.log("---> nameField.innerHTML: "+nameField.innerHTML+"\n")
}
catch(error)
{
console.log("Pants were shat in the 'barkCharData' function [line 72]:\n\t" +error)
}
}
}
I can access each objects properties fine because they're all stored in an array called ar_charBin. Had them console logged earlier with no issues.
The problem I'm having is that I want to put these properties in to disabled text boxes.
These are defined as JSX here:
export const ExpTable = () => {
let Char1 = new char_Obj(1,"Saloth", "Saar", 45, "Male", "Dragonborn", "Sorcerer", 10);
let Char2 = new char_Obj(2,"Kaedwen", "Isaani", 36, "Male", "Dragonborn", "Sorcerer", 8);
let Char3 = new char_Obj(3,"Euridice", "Swiftblade", 23, "Female", "Human", "Bard", 5);
//array to store characters
let ar_charBin = [];
//push all chars into the array
ar_charBin.push(Char1, Char2, Char3);
return(
<>
...table head stuff...
<tbody>
{ar_charBin.map((character) => (
<TableRow key={character.id} onClick={() => barkCharData(character.id)}>
<td>{character.full_name}</td>
<td>Level {character.level} {character.classType}</td>
</TableRow>
))}
</tbody>
</Table>
<Wrapper>
<h2>Object elements</h2>
<p>These fields should update with object<br/>attributes when the records above are clicked</p>
<input type="text" id="txt_idField" name ="id_field" placeholder="ID"/>
<input type="text" id="txt_fNameField" name ="first_name_field" placeholder="First Name" disabled={true} />
<input type="text" id="txt_sNameField" placeholder="Second Name" disabled={true} />
<input type="text" id="txt_ageField" placeholder="Age" disabled={true} />
<input type="text" id="txt_cNameField" placeholder="Full Name" disabled={true} />
<input type="text" id="txt_genderField" placeholder="Gender" disabled={true} />
<input type="text" id="txt_raceField" placeholder="Race" disabled={true} />
<input type="text" id="txt_classField" placeholder="Class" disabled={true} />
<input type="text" id="txt_levelField" placeholder="Level" disabled={true} />
</Wrapper>
</Wrapper>
</>
)
So in the onClick function, it doesn't update the innerHTML of any element. Not sure why. I've tried inputting strings in VScode and in the page when I run npm start too but nothing seems to change them.
Screenshot of the app:
Looked for solutions but got nowhere so far. What am I doing wrong?
React avoids re-rendering unless state was changed. Since all of the changes happen on local variable scale and no state updates were caused by your onClick event, React will keep all HTML as it was.
Try holding the ID of the selected character in the state and hopefully you should see some updates of HTML.

Object keys not displaying anything in react

I have these (from what i can see) almost identical ways of doing what i want, yet the object.keys way is not displaying anything in my browser.
first way:
{this.state.months.map((month, index) => {
for (var key in month) {
if (month.hasOwnProperty(key)) {
return <div key={index} className="match">
<input className="toggle" type="checkbox" />
<label htmlFor="toggle">{month[key].month}</label>
<div className="expand">
{month[key].p1.name} {month[key].p1.score} {month[key].p2.name} {month[key].p2.score}
</div>
</div>
}
}
})}
second way:
{this.state.months.map((month, index) => {
for (var key in month) {
Object.keys(month).forEach((mnth)=>{
console.log(month, 'm1');
console.log(mnth, 'm2');
return <div key={index} className="match">
<input className="toggle" type="checkbox" />
<label htmlFor="toggle">{month[mnth].month}</label>
<div className="expand">
{month[mnth].p1.name} {month[mnth].p1.score} {month[mnth].p2.name} {month[mnth].p2.score}
</div>
</div>
})
}
})}
the reason i want to use object.keys is because it is allowing me to iterate through multiple object properties. the first way was only showing the first key in each object
when i console.log (month[mnth]) I get exactly what i want.
there are also no errors in the server console or the browser console. any ideas why it wont render? im changing nothing else and consistently doesn't render anything
I think you just use template ES6 string. Wrap your print elements like this
`${month[mnth].p1.name}` `${month[mnth].p1.score} ` `${month[mnth].p2.name} ` `${month[mnth].p2.score}`
You also need to return your elements.
You can remove the outer for loop and just use map in iterating thru the keys.
{this.state.months.map((month, index) => {
Object.keys(month).map((mnth, mnthIndex)=>{
console.log(month, 'm1');
console.log(mnth, 'm2');
return (<div key={`${index}-${mnthIndex}`} className="match">
<input className="toggle" type="checkbox" />
<label htmlFor="toggle">{month[mnth].month}</label>
<div className="expand">
{`${month[mnth].p1.name} ${month[mnth].p1.score} ${month[mnth].p2.name} ${month[mnth].p2.score}`}
</div>
</div>)
})
})}
also, observe the use of combination if indexes in the second iteration as key to avoid similar keys. this could also be the reason why there's nothing being rendered in the browser.

creating elements in React

I don't understand how elements are created in React.
I have some code below where the goal is to create elements on a form submit using a value from a refs - so for every submit in a form, it creates a new <h1> tag with the content of the textbox inside of it. A sample of what I'm trying to do looks like:
...
addHeader(e) {
e.preventDefault();
const newHeader = this.refs.post.value;
var newpost = React.createElement("h1", {
type: "text",
value: newHeader
});
}
...
render() {
return (
<div className="form-section">
{ newPost }
<form onSubmit={this.addHeader.bind(this)}>
<input id="input-post" type="text" placeholder="Post Here" ref="post" />
<input type="submit" value="Submit" />
</form>
<button className="form-section__submit" onClick={this.clearFields.bind(this)}>Clear All</button>
</div>
);
}
Basically my thinking is in my addHeader() function I'm assigning a variable of newPost to the method and calling it within my component. This code is causing 2 errors:
33:9 warning 'newpost' is assigned a value but never used no-unused-vars
49:13 error 'newPost' is not defined no-undef
What I don't understand, is (from what I can see) I am assigning a value to that variable and also using it in the component that I am rendering... along with that, I don't understand this error message. How can something be assigned a value but be undefined at the same time...? Is it because it's in the wrong scope? How do I declare where the new element is rendered specifically in the component?
I read the documentation but it doesn't give a clear answer as to how to control where in the component the new element is rendered.
Made some changes to your code. You're going to want to initialize component state in your constructor. In your addHeader method you will use this.setState to update the state of the component with a new posts value including the value of this.input. I changed your ref on the input an actual ref. You take the element and store on this. Every time you add a new post you will get a new <h1> with the value of the textarea.
...
addHeader(e) {
e.preventDefault();
this.setState((prevState, props) => {
return { posts: [ ...prevState.posts, this.input.value ] };
});
}
...
render() {
const { posts } = this.state;
return (
<div className="form-section">
{ posts.map( text => <h1>{ text }</h1> ) }
<form onSubmit={this.addHeader.bind(this)}>
<input id="input-post" type="text" placeholder="Post Here" ref={ el => this.input = ref } />
<input type="submit" value="Submit" />
</form>
<button className="form-section__submit" onClick={this.clearFields.bind(this)}>Clear All</button>
</div>
);
}
As an aside: Binding functions in the render method of react components will cause a performance hit. There is no need to re-bind the this context of the function on every render. this.clearFields.bind(this) should become this.clearFields and you will need to add this.clearFields = this.clearFields.bind(this) to your constructor. You do not need to bind functions that are not used as callbacks.
You're going to want to do the same thing for this.addHeader.bind(this).

Traversing children's children and adding function to all inputs while keep other children untouched

I have been trying to get this to work for a while now and not sure how to do the following. My form component has children that contain regular html markup as well a inputs. If the child is a Input I want to add the attachToForm and detachFromForm functions. If it is not an input I want to continue traversing the children to make sure that the element does not have a child input field. Wether or not the element is an input I still want it to appear on my page, I just want to add the functions to the inputs.
The problem is I can only get my function to return only the inputs, removing the labels and title. I know that is because Im only adding elements with inputs to newChildren, but if I push the other elements in the else if section I get duplicates and i can think of another way of doing this. Im not sure if im not understanding basic JS or having a brain gap.
React.Children.forEach(children, function(child) {
var current = child;
if (child.props && child.props.name) {
this.newChildren.push(React.cloneElement(child, {
detachFromForm: this.detachFromForm,
attachToForm: this.attachToForm,
key: child.props.name
}));
} else if (child.props && child.props.children){
this.newChildren.push(child);
this.registerInputs(child.props.children);
} else {
*need to keep track of parent elements and elements that do not have inputs
}
}.bind(this));
Edit: Not sure if needed but this is and example form im traversing
return (
<Modal className="_common-edit-team-settings" title={`Edit ${this.props.team.name}`} isOpen={this.props.modalIsOpen && this.props.editTeamModal} onCancel={this.props.toggleEditTeamModal} backdropClosesModal>
<Form onSubmit={this.saveChanges}>
<FormSection className="edit-team-details" sectionHeader="Team Details">
<FormField label="Name">
<Input name="name" value={this.state.values.name} onChange={this.handleInputChange} type="text" placeholder={this.props.team.name}/>
</FormField>
<FormField label="Mission">
<Input name="mission" value={this.state.values.mission} onChange={this.handleInputChange} type="text" placeholder={this.props.team.kitMission || 'Kit Mission'} multiline />
</FormField>
</FormSection>
<FormSection className="privacy-settings" sectionHeader="Privacy Settings">
<FormField label="Included in global search results" >
<SlideToggle name="globalSearch" defaultChecked={this.state.values.globalSearch} onChange={this.handleCheckedChange} type="checkbox" />
</FormField>
<FormField label="Accessible by anyone" >
<SlideToggle name="public" defaultChecked={this.state.values.public} onChange={this.handleCheckedChange} type="checkbox" />
</FormField>
<FormField label="Secured with WitCrypt" >
<SlideToggle name="witcryptSecured" defaultChecked={this.state.values.witcryptSecured} onChange={this.handleCheckedChange} type="checkbox" />
</FormField>
</FormSection>
<FormSection sectionHeader="Participants">
{participantsList}
<div id="add-participant" className="participant" onClick={this.toggleAddParticipantModal}>
<span className="participant-avatar" style={{backgroundImage:'url(/img/blue_add.svg)'}}></span>
<span>Add a Participant</span>
<span className="add-action roll"><a></a></span>
</div>
</FormSection>
<Button type="hollow-primary" size="md" className="single-modal-btn" block submit>Save</Button>
</Form>
<AddParticipant people={this.props.people} toggleAddParticipantModal={this.props.toggleAddParticipantModal} modalIsOpen={this.props.modalIsOpen} toggleAddParticipantModal={this.toggleAddParticipantModal} addParticipantModal={this.state.addParticipantModal} />
</Modal>
);
As an aside I started out a lot simpler wanting to do the following but get:
"Can't add property attachToForm, object is not extensible"
If anyone knows why please let me know.
registerInputs: function (children) {
React.Children.forEach(children, function (child) {
if (child.props.name) {
child.props.attachToForm = this.attachToForm;
child.props.detachFromForm = this.detachFromForm;
}
if (child.props.children) {
this.registerInputs(child.props.children);
}
}.bind(this));
}
Judging of an error message, you have a problem with immutable prop object. Starting from React 0.14 the prop is "frozen":
The props object is now frozen, so mutating props after creating a component element is no longer supported. In most cases, React.cloneElement should be used instead. This change makes your components easier to reason about and enables the compiler optimizations mentioned above.
Blog post on this
So somewhere in your code you try to extend a prop object causing an error.
You could wrap different parts of your prop interactions with try..catch construction which will point you the exact problem place.

Adding/removing input fields

I'm pretty new to ReactJS, I'm liking it a lot, but there are some things like binding that seems to be easier in Angular.
I want to have a form, where a user can click a button to add extra input fields. At any point, they can also "delete" an input field.
On the submit, I want to get these inputs as an array, i.e. pass dynamicInputs to my API which contains an array of name.
This is what I've done (which is probably wrong since I'm treating React like Angular):
var React = require('react');
module.exports = React.createClass({
addInputField: function(e) {
e.preventDefault();
var inputs = this.state.inputs;
inputs.push({name: null});
this.setState({inputs : inputs});
},
removeInputField: function(index) {
var inputs = this.state.inputs;
inputs.splice(index, 1);
this.setState({inputs : inputs});
},
handleSubmit: function (e) {
e.preventDefault();
// What do I do here?
},
getInitialState: function() {
return {inputs : []};
},
render: function (){
var inputs = this.state.inputs;
return (
// Setting up the form
// Blah blah
<div className="form-group">
<label className="col-sm-3 control-label">Dynamic Inputs</label>
<div className="col-sm-4">
{inputs.map(function (input, index) {
var ref = "input_" + index;
return (
<div className="input-group">
<input type="text" className="form-control margin-bottom-12px" placeholder="Enter guid" value={input.name} ref={ref} aria-describedby={ref} />
<span className="input-group-addon" onClick={this.removeInputField.bind(this, index)} id={ref} ><i className="fa fa-times"></i></span>
</div>
)
}.bind(this))}
<button className="btn btn-success btn-block" onClick={this.addInputField}>Add Input</button>
</div>
</div>
);
}
});
Right now removeInputField does NOT work! It just removes the last entry all the time.
Every <div className="input-group"> must have a unique key
<div className="input-group" key={index}>
That's how React distinguishes between collection of rendered nodes.
References:
https://facebook.github.io/react/docs/multiple-components.html#dynamic-children
UPD:
As #WiredPrairie mentioned below in the comments - the suggested solution is far from ideal, since the index is not unique enough. And instead you need to create another array with some unique identifiers (a monotonously growing sequence would be enough) and maintain it in parallel with this.state.inputs and use its values as keys.
So, on adding an element you:
this.keys.push(++this.counter);
on removing - remove from both by the same index. And in the .map you
<div className="input-group" key={this.keys[index]}>

Categories

Resources