Object keys not displaying anything in react - javascript

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.

Related

Vue 3 v-model not properly updating on in Andoid's Chrome?

I have a component, which is essentially an input/select hybrid field, which allows users to type in the input field, and select items from the dropdown, based on their query.
It works perfectly fine on most devices I've tried, i.e. as the user types something into the input field, the list of items updates and only shows those items which contain that piece of string.
Except the Chrome browser on my Android device - as you type, the list doesn't seem to update, unless I press the "space bar". Very strange. Anyone have any ideas why this might be?
Here is the code in <script setup>:
const props = defineProps([ 'items', 'searchBy' ])
const searchTerm = ref('')
const itemsToShow = computed(() => {
if (props.items) {
if (searchTerm.value) {
return props.items.filter(el => {
if (props.searchBy) {
return el[props.searchBy].toUpperCase().indexOf(searchTerm.value.toUpperCase()) > -1
}
return el.toUpperCase().indexOf(searchTerm.value.toUpperCase()) > -1
})
} else {
return props.items
}
} else {
return []
}
})
And the HTML:
<input
type="text"
class="input"
v-model="searchTerm"
placeholder=" "
/>
<div class="items-list">
<div
v-for="item in itemsToShow"
:key="item"
#click="() => handleAdd(item)"
class="item text"
>
{{ item }}
</div>
<div
v-if="!itemsToShow.length"
class="text"
>
No items match searched term
</div>
</div>
UPDATE:
I've investigated a little, and it seems the searchTerm ref, isn't updating properly, even though its bound using v-model... Still no idea why though.
I've ran into this issue before.
It seems that on certain devices, the v-model waits for a change event, instead of an input one.
Apparently, it's to do with the input method editor (IME) for the specific device.
You can check a discussion about this at https://github.com/vuejs/core/issues/5580
The workaround is to simply bind the input field with value and listen for the input event manually, e.g.
<input
type="text"
class="input"
:value="searchTerm"
#input="(e) => searchTerm = e.target.value"
placeholder=" "
/>

React component not sending variable value with onChange event

I am trying to display more than one task using the same react component and a map function. The components is supposed to upload a file and display the file that has been upload or is uploading with an onChange event, an onSubmit event will be added later to send the file to the back-end. Since the component is being dynamically rendered with an onChange event I am sending the li key as a parameter then only rendering the filename to the li that matches based on the key. The issue I am currently having is when I send the key in the onChange the value is not getting to the function. I have even tried setting a state for the key and then pulling that into the function but I get back undefined I have tried 100 other things as well and either get back undefined or 0. This is my first post on here and I am also just a junior dev so I may be missing info needed here if so please let me know.
Here is the code
const onChange = (e, index) => {
setFilename(e.target.files[0].name)
setArrFiles([...arrFiles, {key:index, file:e.target.files[0].name} ])
}
const displayTask = (index) => {
let upload = []
arrFiles.forEach(el => {
if(el.key === index) {
upload.push(el.file)
}
})
return upload.map((file) => (
<li key={index}>{file}</li>
))
}
{database.map((task, index) => (
<li key={index}>
<p>{index}</p>
<div className="userPortal__tasksContainer">
<div className='userPortal__tasksHeader'>
<p className='userPortal__tasksHeaderTitle'>{task.taskTitle}</p>
<p className='userPortal__tasksHeaderInfo'>{task.taskMessage}</p>
</div>
<div className='userPortal__tasksBodyButtons'>
<form className='userPortal__fileUpload'>
<input className='userPortal__inputButton' multiple id='file' type="file" onChange={e => onChange(e, index)}/>
<label htmlFor='file'>Choose File</label>
</form>
<button className='userPortal__tasksBodyButton green'>Task Complete</button>
</div>
<p id='userPortalFooterTitle'>Successfully Uploaded</p>
<div className='userPortal__footer'>
<ul>
{displayTask(index)}
</ul>
</div>
</div>
</li>
))}
The issue here was like a dummy I hard coded the input ID so when referencing the input field it would only get the hard coded value instead of the index of the component.
Here is the updated form.
<form className='userPortal__fileUpload'>
<input className='userPortal__inputButton' multiple id={index} type="file" onChange={(() => (e) => onChange(e, index))()}/>
<label htmlFor={index}>Choose File</label>
</form>
No Do It Like This
const onChange = (e, index) => {
setFilename(e.target.files[0].name);
// Okay You Need To Filter The State In Order To Get The Object With THe Current Index
// Set The File Value Of The Object To e.target.files[0].name
setArrFiles((state) => {
const allFiles = state;
const currentFile = allFiles.filter(
(file) => parseInt(file.key) === parseInt(index)
)[0];
// i converted it to an integer just to prevent further bugs
currentFile.file = e.target.files[0].name;
return [allFiles];
});
};

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

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>

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