I use React Material-UI for my project. I put many components in render method. Then weird things happened. Some components can not be referred by 'this.refs.REFNAME'. And then I checked 'this.refs' object, it shows as the pic below:
As you can see, only some of the components, which can be referred, are showed on the first line. I don't understand why. Can anyone explain it to me? Thanks lot.
Update: Thanks to James, I get it that the first line should be the status of 'this.refs' at that moment. But still, why some of the components are not in the refs object?
Here's the relevant part of code:
render(){
return <div>
<Table ref='fromform'>
<TableHeader>
<TableRow>
<TableHeaderColumn tooltip='The ID'>No.</TableHeaderColumn>
<TableHeaderColumn tooltip='The Name'>Name</TableHeaderColumn>
<TableHeaderColumn tooltip='The Status'>Notes</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody ref='fromformb'>
{playerList.map(function(player, index){
return <TableRow onTouchTap={this._onShowInfo.bind(this, index)} key={'row' + index}>
<TableRowColumn>{index + 1}</TableRowColumn>
<TableRowColumn>{player.name}</TableRowColumn>
<TableRowColumn>{player.notes}</TableRowColumn>
</TableRow>;
}.bind(this))}
</TableBody>
</Table>
<Dialog
title='Player Info'
ref='playerInfoDialog'>
<form role='form' ref='fromfo'>
<div className='form-group' ref='frowwmformb'>
<TextField type='text' hintText='Player Name' ref='txtName' fullWidth={true} />
<TextField type='text' hintText='Notes' ref='txtNotes' fullWidth={true} />
</div>
</form>
</Dialog>
<MainButtonGroup page='players' />
<Spinner />
</div>;
}
_onShowInfo(index){
var player = playerList[index];
console.log(this.refs);
this.refs.playerInfoDialog.show();
this.refs.txtName.setValue(player.name);
this.refs.txtNotes.setValue(player.notes);
}
}
What I want to do with this code is to generate rows in a table with player list, and when I tap a row, a dialog shows up with the data of the row.
But the two 'TextField's with refs of 'txtName' and 'txtNotes' can't be referred(as on the last 2 lines, produce errors). The pic above is produced by 'console.log' in '_onShowInfo' method.
I added some 'ref's with random names just to test.
I found a solution. Change _onShowInfo as below:
_onShowInfo(tid){
var player = playerList[index];
this.refs.playerInfoDialog.show();
setTimeout(function(){
console.log(this.refs);
this.refs.name.setValue(player.name);
this.refs.notes.setValue(player.notes);
}.bind(this), 0);
}
But I don't know why.
This happens because when the dialog is hidden, its children are not mounted yet. So the refs defined in the children are not set yet.
You see them in the console, though, as Chrome automatically updates it when it gets set because there is referential equality between the old this.refs and the new one. If you want to log the refs at a given point in time and make sure it is not updated by Chrome, you could do : console.log({ ...this.refs })
Another approach would be to hold a reference to the selected player in your component's state. Then you would have:
_onShowInfo(tid){
var player = playerList[index];
this.setState({selectedPlayer: player});
}
and
<Dialog
title='Player Info'
open={ !!this.state.selectedPlayer }
>
<form role='form'>
<div className='form-group'>
<TextField type='text' hintText='Player Name' defaultValue={ this.state.player.name} fullWidth={true} />
</div>
</form>
</Dialog>
Related
Currently, my react/nextjs dynamic list is not updating correctly, I've given an array to map so it'll show a new row on frame update since it's stored on useState, Usually, the issue is with the key of the list that is not unique, but my key is unique
Heres my code
const [allSelectedMaterials, setAllSelectedMaterials] = useState([]) // This variable stores a javascript object into the array
{console.log('-- Updated --')}
{allSelectedMaterials.map((material, i) => {
const key = Object.keys(material).toString()
console.log(`${i} - [${key}] ${material}`)
return (
<div key={key}>
<Row className='align-items-center'>
<Col xs='auto'>
<h6>{material[key].name}</h6>
</Col>
<Col>
<Button variant='danger' className='mb-1' onClick={() => handleDeleteMaterial(key)}>
Remove
</Button>
</Col>
</Row>
<InputGroup>
<InputGroup.Text>
<Image src={material[key].image} className={`${styles.allMaterialsImage}`} />
</InputGroup.Text>
<Form.Control type='number' min={1} ref={(e) => (selectedMaterials.current[i] = e)} required />
</InputGroup>
<div className='mt-1' />
</div>
)
})}
The problem is after I added the first item on the list and adding a new one it won't update the view, here's the console output
And here's me adding a second entry to the list
It clearly says on the log that the array (stored in useState) is updated but it's not changing the view it's still the same as the previous one. But if I reupdate the frame by updating any useState variable it updated the list
Update:
Here's my code for adding new material
(loadedMaterials is just a list of materials that is retrieved from an API)
const handleAddSelectedMaterial = () => {
loadedMaterials.map((material) => {
const key = Object.keys(material)
if (key == currentSelectedMaterial) {
let _material
if (allSelectedMaterials.length > 0) _material = allSelectedMaterials
else _material = []
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials(_material)
}
})
}
Try replacing
from
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials(_material)
to
_material.push({ [material[key].id]: material[key] })
setAllSelectedMaterials([..._material])
Thank you everyone for the input and responses, but I've managed to solve this issue by redoing how the form & dynamic list work and it's working perfectly now.
If anyone is wondering I just basically follow this person implementation on a dynamic list
https://www.cluemediator.com/add-or-remove-input-fields-dynamically-with-reactjs
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 a form where menu has to be uploaded with menu title and type. The type can be food-menu and beverage-menu. Each type will have only 3 images. I have done that part
but when selecting the radio button for menu type, if i select second or other than first radio button, the first radio button gets selected. How do I solve this issue?
The code can be large so here is the demo
https://codesandbox.io/s/zxxrnw2qx4
here is the code in brief
const Preview = props => {
return (
<div>
{props.files.map((file, index) => {
if (['image/png', 'image/jpg', 'image/jpeg'].includes(file.type)) {
return (
<ClientUploadedImage
key={index}
file={file}
id={index}
menu_title={props.menu_title}
type={props.type}
foodmenuError={props.foodmenuError}
handleChange={props.handleChange}
handleTypeChange={props.handleTypeChange}
/>
);
}
return <div>No File to Show Preview</div>;
})}
</div>
);
};
const ClientUploadedImage = props => {
return (
<section id="menus-photo">
<img src={props.file.preview} width={'400px'} height={'auto'} />
<div className="content">
<form className="form">
<Radio
value="food-menu"
label={`Food Menu${props.id}`}
name={props.id}
handleChangeEvent={props.handleTypeChange}
isChecked={props.type[props.id] === 'food-menu'}
/>
<Radio
value="beverages-menu"
label={'Beverages Menu'}
name={props.id}
handleChangeEvent={props.handleTypeChange}
isChecked={props.type[props.id] === 'beverages-menu'}
/>
<InputField
name={props.id}
type="text"
label="Menu Title"
onChange={props.handleChange}
value={props.menu_title[props.id]}
/>
</form>
</div>
</section>
);
};
Your type array is empty in initial state. If you directly select radio button from second image, handleTypeChange is being called with index 1. In the function, ...types.slice(0, index) becomes types.slice(0,1) which eventually performs spread operation on a blank array and your newValue is appended at 0th position which leads to selection of first image radio button. Here you need to handle the blank array condition for first selection inside the function and you will be good to go.
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.