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.
Related
I am Trying to build a simple trivia quiz using React and i am stuck on as to how to show the results when the user clicks "Check Answer" Button. I need to change the colors of label reflecting correct and wrong choices.
props ={
questions: arr[str]
answers: arr[arr[str]]
correct-answer: "str"
}
export default function QuestionPage(props){let [showResults,setShowResults] = React.useState(false)let Questions = []
// create 5 questions using the data from props
function getQuestions(){
for (let i =0;i<props.questions.length;i++){
Questions.push(
<Question
key= {nanoid()}
question = {props.questions[i]}
answers = {shuffle(props.answers[i])}
correct_answer = {props.correct_answers[i]}
showResults = {showResults}
/> )
}
return Questions
}
function TotalScore(){
Questions.forEach( (value,index)=>{
console.log(value,"\t",index)
})
//get all inputs using DOM and check
}
return (
<main>
{getQuestions()}
{ showResults && <TotalScore />}
<button onClick={() => setShowResults(true)}>Check Answers</button>
</main>
)
}
I was hoping to change the background color of labels inside the input field I have created for answering the questions :
Red if user selected the wrong option
Green if correct.
I can handle the css bit of coloring, just need to understand "how to implement the passing of command from button in parent component down to child component" functionality in React.
Below is the code for Question.js Component
export default function Question ({question,answers,correct_answer,showResults}) {
const [selected,setSelected] = React.useState({
selected: ""
})
function handleChange(event){
console.log(event.target,"\t",selected.selected)
setSelected( {
selected: event.target.value}
)
}
return(
<div className='question-container'>
<h4>{question}</h4>
<div className='answers-row'>
<fieldset>
<input
type="radio"
id = {answers[0]}
name = {question}
value = {answers[0]}
onChange = {handleChange}
checked = {selected.selected === answers[0]}
/>
<label htmlFor={answers[0]}>{answers[0]}</label>
<br />
<input
type="radio"
id={answers[1]}
name = {question}
value = {answers[1]}
onChange = {handleChange}
checked= {selected.selected === answers[1]}
/>
<label htmlFor={answers[1]}>{answers[1]}</label>
<br />
<input
type="radio"
id={answers[2]}
name = {question}
value = {answers[2]}
onChange = {handleChange}
checked= {selected.selected === answers[2]}
/>
<label htmlFor={answers[2]}>{answers[2]}</label>
<br />
<input
type="radio"
id={answers[3]}
name = {question}
value = {answers[3]}
onChange = {handleChange}
checked= {selected.selected === answers[3]}
/>
<label htmlFor={answers[3]}>{answers[3]}</label>
<br />
</fieldset>
</div>
</div>
)
}
In your code, you are maintaining the state ie. the answer selected by the user, on each Question component. However, when the user clicks the button QuestionPage components re-render so do its children. The getQuestions() will be invoked and components will be created again and states will again be initialized to the default value ie "".
Read this https://reactjs.org/docs/lifting-state-up.html. The example given here is similar to what you are trying to do.
I've made such a table to put articles with their respective prices, quantity... What's more there's a button and with every click it's created a new article. (put a photo to see it clearer)
The fact is that I need a way to differentiate every line, what i say is that in the square "Num" there should be 1,2,3... this is very important to me because later I need to send the information to a database, but my problem is I don't know how to differentiate every line. Here I put the code
class LinArt extends Component{
constructor(){
super()
this.state = {
quan:2,
price:20,
dto:10,
count:1
}
this.newArt = this.newArt.bind(this)
}
newArt(){
this.setState({count:this.state.count+1})
}
showArt(){
const total = (this.state.cant * this.state.preu) * (1-(this.state.dto/100));
let lines =[];
for(let i=0; i < this.state.count; i++){
lines.push(
<div key={i}>
<TextField type="number" disabled={true} value={this.state.count} label="Num"/>
<TextField label="DescripciĆ³" variant="outlined"/>
<TextField type="number" label="Cant" value={this.state.quan}/>
<TextField type="number" label="Price" value={this.state.price}/>
<TextField type="number" label="Dto" value={this.state.dto }}/>
<TextField type="number" disabled={true} value={total} label="Total"/>
<TextField type="number" disabled={true} value="21" label="IVA"/>
<br/><hr/>
</div>
)
}
return linies || null;
}
render(){
return(
<div>
{this.showArt()}
<Button onClick={this.newArt}>New article </Button>
</div>
)
}
Thank you for your attention, I appreciate so much your help!
Looking at your code, if you want the num column to increment, you should add i + 1 increment variable as the value to your num field so as it runs the loop, it gives the incremented value and starts from 1 rather than 0. Like so,
<TextField type="number" disabled={true} value={i + 1} label="Num"/>
I think I'm going crazy. I have 2 nearly identical pieces of code and in 1 of them the keyword this references the correct scope while in the other it doesn't. I've been staring at it for 3 hours and need other eyes.
The first function is this:
renderField({input, options, label, name, multi}){
let list = options.map(category=>{
return {value:category.name, label:category.name}
});
return(
<div>
<label>{label}</label>
<Select
value={this.state.selected}
multi={multi}
name={name}
className="basic-multi-select"
classNamePrefix="select"
options={list}
onChange={(e)=>{
this.setState({selected:e});
input.onChange(e);
}}
/>
</div>
)
}
the this I'm referring to is the line this.setState({selected:e});. This code works. the 'this' is called in the correct scope. I needed to refactor the code so I wrote another function in a higher level component and bound it to that class. I then proceeded to chane the above to the following:
renderField({defaultValue, input, options, label, name, multi, initialValues}){
let list = options.map(category=>{
return {value:category.name, label:category.name}
});
return(
<div>
<label>{label}</label>
<Select
value={this.props.selected}
multi={multi}
name={name}
className="basic-multi-select"
classNamePrefix="select"
options={list}
onChange={(e)=>{
this.props.changeState(this.props.state_handler, e);
input.onChange(e);
}
}
/>
</div>
)
}
suddenly this is no longer in scope and is now pointing at the e argument that I'm passing in. Why is this happening and how can I fix it?
In event handler, this means the event target.
So you should set a variable to remember the this in renderField.
Here's an example with comment.
renderField({defaultValue, input, options, label, name, multi, initialValues}){
let list = options.map(category=>{
return {value:category.name, label:category.name}
});
// set that to current this
let that = this;
return(
<div>
<label>{label}</label>
<Select
value={this.props.selected}
multi={multi}
name={name}
className="basic-multi-select"
classNamePrefix="select"
options={list}
onChange={(e)=>{
// this.props.changeState(this.props.state_handler, e);
// use that instead
that.props.changeState(that.props.state_handler, e);
input.onChange(e);
}
}
/>
</div>
)
}
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>
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.