I have a funky set up. I need a multi stage registration form. I have a parent:
class ContactPage extends React.Component {
constructor(props){
super(props);
this.state = {
stage:0,
name:'',
message:'',
email:'',
phone:''
}
this.setName=(e)=>{
this.setState({name:e});
}
this.setMessage=(e)=>{
this.setState({message:e});
}
this.setEmail=(e)=>{
this.setState({email:e});
}
this.setPhone=(e)=>{
this.setState({phone:e});
}
this.nextStage=()=>{
if(this.state.stage < 3){
this.setState({stage:this.state.stage+1})
}
}
this.previousStage=()=>{
if(this.state.stage >= 1){
this.setState({stage:this.state.stage-1})
}
}
this.stage = [
<ContactName onChange={this.setName} />,
<ContactInfo />,
<ContactMessage name={this.state.name} onChange={this.setMessage} />,
<Send />
]
}
render(){
return (
<div>
{this.stage[this.state.stage]}
<button primary style={style.button} onClick={this.previousStage}> Previous </button>
<button primary style={style.button} onClick={this.nextStage}> Next </button>
</div>
This component renders children based on in what stage of registration the user is. I can receive callbacks from children in parent(children do set the state of the parent), but, when passing state.name from parent to child as a prop, the child receives the initial state, which means the name is empty string.
Child component:
class ContactMessage extends React.Component {
constructor(props){
super(props);
this.state ={
message:'',
name:''
}
this.handleChange=(event)=>{
this.props.onChange(event.target.value);
this.setState({message: event.target.value});
}
}
componentWillReceiveProps(props){
this.setState({name:props.name})
}
render(){
return(
<div>
<h1>{this.state.name}</h1>
<form onSubmit={this.handleSubmit}>
<label htmlFor='messageField'>
Message:
<input className='messageField' type="textfield" value={this.state.message}
onChange={this.handleChange} />
</label>
</form>
</div>
UPDATE: I am receiving initial props in the child components, not updated props from parent state. How do I receive new and updated props from parent state?
As I see, in ContactPage you trying to set event (e) as value of state.name instead of setting this.setState({ name: e.target.value }) in this. setName method.
By the way, you don't have to pass everything through constructor.
For example,
class MyComponent extends Component {
state = {
name: ''
}
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
}
render() {
return (
<input name="name" value={this.state.name} onChange={this.handleChange} />
);
}
}
Try extracting the stage array into its own method. Something like:
class ContactPage extends Component {
constructor(props) {
this.state = {
stage: 0,
name: 0
// and so on
}
}
getStage(index) {
let stages = [
<ContactName onChange={this.setName} />,
<ContactInfo />,
<ContactMessage name={this.state.name} onChange={this.setMessage} />,
<Send />
];
return stages[index];
}
render() {
return (
{this.getStage(this.state.stage)}
<AllTheOtherStuff />
)
}
}
3 things I'm noticing here, which may or may not be your problem.
First: Using state the way you are is inherently bug prone. If you are displaying this.state.name, and are setting name from this.props, both in the constructor and componentWillReceiveProps, then just skip the middle man and display this.props.name instead. Trying to maintain a correct state when the value in state is coming from props is really easy to mess up, thus it is better to just use props directly.
Second: I believe your problem is a mixture of 1 and 2 here, but you are declaring your stage variable in your constructor, so it is only going to use what it has available in the constructor. Move this.stage into your render() and it should display the correct state information.
Third: the way you are using componentWillReceiveProps can lead to confusion. It is better to name your variables something that accurately describes what they are, without possible confusion. I would change componentWillReceiveProps(props) to componentWillReceiveProps(nextProps) since nextProps is more explicit about what those props are you are dealing with.
Related
I have a input box where I am showing the value if there is any fetched from the database and the input text box is editable. However, this input box doesn't reflect the changes on key press.
Here is the necessary following code:
constructor(props) {
super(props)
const { minorModel } = this.props;
this.state =
{
galleryModel: minorModel.galleryModel,
selectedIndex: 0
}
}
componentDidUpdate() {
this.props.minorModel.galleryModel = this.state.galleryModel;
}
onInputChange=(e)=>{
this.setState({
[e.target.name]:e.target.value
})
}
{this.state.galleryModel.photos.length > 0 &&
<PtInput type="text" label="Comment"
value={this.state.galleryModel.Comments[this.state.selectedIndex] === null ?
"Not Available " :
this.state.galleryModel.Comments[this.state.selectedIndex]}
onChange={this.onInputChange}
name={`${"Comment "+[this.state.selectedIndex]}`}
disabled={this.props.disabled}
/>
}
I probably think the problem is because I am using a data model which is passed as props in the constructor and the values in that data model is not changing so the new value is not reflected. Is that so? Any help is really appreciated to solve this issue.I also have a componentDidUpdate to reflect the changes.
There is no connection between the value attribute and your onChange handler, since they aren't relying on the same state, thus you have an uncontrolled input.
When the component mounts, you can load your data into the same state that is used to control the input.
Here is a simplified potential implementation:
class App extends React.Component {
constructor() {
super();
this.state = {
"pics": ['https://placehold.co/200x100', 'https://placehold.co/200x100'],
"comments": [["comment 1 pic 1", "comment 2 pic 1"], ["comment 1 pic 2", "comment 2 pic 2"]],
}
}
render() {
return (
<div>
{this.state.pics.map((pic, i) =>
<Picture pic={pic} comments={this.state.comments[i]} />)}
</div>
)
}
}
const Picture = ({ pic, comments }) => {
return <div>
<img src={pic} />
{comments.map(comment => <Comment comment={comment} />)}
</div>
}
class Comment extends React.Component {
constructor(props) {
super(props);
this.state = { value: this.props.comment };
this.handleChange = this.handleChange.bind(this);
}
componentDidUpdate() {
// do stuff when Comment component updates
}
handleChange(e) {
this.setState({ value: e.target.value });
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Also, you shouldn't be updating props inside a component. They are meant to be immutable and top-down.
this.state.galleryModel.Comments[this.state.selectedIndex] is not the same state as this.state[${"Comment "+[this.state.selectedIndex]}]
So you are feeding the onChange value into state that you are not passing into the value prop on PtInput
to get things working properly, you need to decide which format you want to go with. You could resolve the galleryModel.Comments whenever minorModel updates to these indices ${"Comment "+[this.state.selectedIndex]} on your state, or work within the data model you have in galleryModel.Comments.
I'd suggest you consider why you need two sources of the same information.
I have passed a data model as props from Parent Component (MinorStructures) to Child component (PhotoGallery).
The parent Component looks like the following:
export default class MinorStructures extends Component {
constructor(props)
{
super(props);
// This is a super data model class, its main function is to collect
// data from all the children components.
this.state = {
MinorStructures: {
layer: 3,
layerName: 'MinorStructures',
layerId: -1,
agencyId: -1, //to be determined later
galleryModel:{
selectedFile: null
}
}
};
this.panes = [
{
menuItem: 'Photo Gallery', render: () =>
<Tab.Pane>
<PhotoGallery triggerNeedSave={this.props.triggerNeedSave}
disabled={this.props.disabled}
loggedIn = {this.props.loggedin}
minorModel={this.state.MinorStructures}/>
</Tab.Pane>
},
];
}
}
I have removed few codes from the parent class which is not necessary for this problem.
The Child Component is like the following:
export default class PhotoGallery extends Component{
constructor(props) {
super(props)
const {minorModel} = this.props
this.state={
cameraOpen: false,
photoModel: minorModel.galleryModel
}
console.log("Constructor State ", this.state)
}
handleChange = e =>{
this.props.triggerNeedSave();
this.setState({[photoModel.selectedFile]:e.target.files[0]})
console.log(this.state)
}
render() {
const uploadClick = e => {
hiddenFileInput.current.click();
};
return (
<div>
{!this.state.cameraOpen && <Button size='tiny' onClick={uploadClick}
color='brown'
disabled ={this.props.disabled}>Upload Photos from Device</Button>}
<input id="photo" name="selectedFile" type="file"
onChange={this.handleChange}
ref={hiddenFileInput} style={{display:'none'}} />
<Button size='tiny' onClick={checkModel} color='brown'
disabled ={this.props.disabled}>
Click To Check
</Button>
</div>
);
};
return (
<div id="root">
<Gallery />
</div>
)}
}
In the state of PhotoGallery class I have a photoModel that takes the data model from MinorStructures as props. When I select a picture and do setState in the handleChange method of PhotoGallery class it says photoModel is not defined. But I have defined that variable in the state which stores data model passed as props from MinorStructures.
You are using dynamic keys when changing state with [] to access properties on this.state
this.setState({[photoModel.selectedFile]:e.target.files[0]})
The correct way would be to change state with
this.setState({photoModel.selectedFile:e.target.files[0]})
I set a name attribute for the file type input html tag and then used the below code to set the value
this.setState({ [name]: e.target.files[0] });
I have a parent component housing two children components(AddPersonForm and PeopleList). When I submit a name via the AddPersonForm, I expect it to be rendered in the PeopleList component, but it doesn't.
Here is my AddPersonForm:
class AddPersonForm extends React.Component {
state = {
person: ""
}
handleChange = (e) => this.setState({person: e.target.value});
handleSubmit = (e) => {
if(this.state.person != '') {
this.props.parentMethod(this.state.person);
this.setState({person: ""});
}
e.preventDefault();
}
render() {
return (
<form onSubmit={this. handleSubmit}>
<input type="text" placeholder="Add new contact" onChange={this.handleChange} value={this.state.person} />
<button type="submit">Add</button>
</form>
);
}
My PeopleList component:
class PeopleList extends React.Component {
constructor(props) {
super(props);
const arr = this.props.data;
this.state = {
listItems: arr.map((val, index) => <li key={index}>{val}</li> );
}
}
render() {
return <ul>{this.state.listItems}</ul>;
}
}
Now the parent component, ContactManager:
class ContactManager extends React.Component {
state = {
contacts: this.props.data
}
addPerson = (name) => {
this.setState({contacts: [... this.state.contacts, name]});
render() {
return (
<div>
<AddPersonForm parentMethod={this. addPerson}×/>
<PeopleList data={this.state.contacts} />
</div>
);
Please what I'm I doing wrong, or not doing?
The issue is in your PeopleList component. The state object which renders your list is created in the constructor when the component mounts, but you have no way of updating it when it recieves new values. It will always give you the initial value.
You could introduce a lifecycle method, componentDidUpdate, which would allow you to compare the previous props to the new props when they arrive, and update the state accordingly. I would recommend you not do this for two reasons:
Storing props directly in a components state is not good practice. You are just creating a copy of the state in the component above and that creates opportunities for confusion and stale values when one of them updates. Ideally, each piece of data should live in only one place.
If all PeopleList is doing is rendering your data, then it doesn't need any state at all. It can act as a display component that maps your props in place and doesn't have to worry about updating itself or managing its own data. This would actually make it a good candidate for conversion into a functional component.
class PeopleList extends React.Component {
render() {
return (
<ul>
{this.props.data.map((val, index) => (
<li key={index}>{val}</li>
))}
</ul>
);
}
}
You are initializing PeopleList with props when its created and mounted but then you are not using new values of props for updating it.
To fix your issue use current value of prop when rendering:
class PeopleList extends React.Component {
render() {
return <ul>{ this.props.data.map((val, index) => <li key={index}>{val}</li>) }</ul>;
}
}
I'm planning to add a prefilled form with React. I have the actual data on props. This is what I came up with.
#connect(...)
class Some extends React.Component {
state = {
...this.props.auth.user
}
render() {
// Create a form using the data on state
}
}
It looks not correct since I'm not using a react lifecycle hook here. I would like to ask if there is a better practice to achieve what I'm trying to do.
I am not sure about your architecture,since you are using uncontrolled component here, it is recommended to keep the source of truth at one place.
you can do something like this:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
userName:this.props.auth.user
}
}
handleChange = (event) => {
this.setState({userName: event.target.value});
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.userName}/>
</div>
)
}
}
If you want to use controlled component that is controlled through parent/container. you can manage the values through props and set the props onChange.
So to elaborate on my previous responses you would do something like this to achieve what you want:
#connect(...)
class Some extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
render() {
return(
<div>
<input onChange={this.handleChange} id="some" type="text" value= {this.state.value|| this.props.value}/>
</div>
)
}
}
While your value is an empty string (in the state), the fields will be populated from your props and as soon as you start typing it will overwrite the prepopulated values with the ones in your state.
Best practices would be to actually have a Component that handles this logic and then passes the props to the form that should be just a dumb presentational component:
class SomeController extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
}
}
handleChange = (event) => {
this.setState({ value: event.target.value });
}
return (<Form handleChange={this.handleChange} value={this.state.value} />)
}
And then your form component:
const Form = (props) => (
<form>
<input onChange={props.handleChange} value={props.value} />
</form>
);
Hope this explanation helps.
How to setState for all instances of the same component type in that component.
In ParentComponent
render() {
return(
<ChildComponent ... />
<ChildComponent ... />
<ChildComponent ... />
);
}
In ChildComponent
//onClick Handler should set state of all instances
onClick() {
this.setState({value: ''})
}
If you have some value which is used by multiple child components, then the correct way is to take that value one level up (i.e. in parent) and pass that value to those child as prop so that all children share the same value. So maintain a state in parent and pass them as prop to children like this
onClick() {
this.setState({value: ''})
}
render() {
return(
<ChildComponent value={this.state.value} onClick={this.onClick}... />
<ChildComponent value={this.state.value} onClick={this.onClick}... />
);
}
Ok, so...
I'm working on some kind of picker.
There are 3 components of the same type. Each component stores different state.
States depends on what user typed in input field (combined with react-autosuggest).
User fill up 3 inputs, and choose 1 image that is rendered depends on state.
After user click image, all inputs should be cleared (value is in state).
I made that working but not rly satisfied
and this is combined with redux
Parent
I made a ref to each component and save a instance to his state and pass callback to trigger methods in all child instances.
class Parent extends Component {
constructor(props) {
super(props);
this.state = {};
this.clearAllInputs = this.clearAllInputs.bind(this) // coz callback returns child own props
}
componentDidMount() {
this.setState({
firstChild: this.firstChild.getWrappedInstance(),
secondChild: this.secondChild.getWrappedInstance(),
thirdChild: this.thirdChild.getWrappedInstance(),
})
}
clearAllInputs() {
//call methods from all child instances
this.state.firstChild.clearInput();
this.state.secondChild.clearInput();
this.state.thirdChild.clearInput();
}
...
render() {
return(
<Child ref={ context => this.firstChild = context } clearAllInputs={this.clearAllInputs} ... />
<Child ref={ context => this.secondChild = context } clearAllInputs={this.clearAllInputs} ... />
<Child ref={ context => this.thirdChild = context } clearAllInputs={this.clearAllInputs} ... />
);
}
...
}
class Child extends Component {
...
clearInput() {
this.setState( { value : '' } );
}
render() {
return(
...
<img ... onClick={ this.props.clearAllInputs } />
);
}
}
export default connect(state, null, dispatchers, { withRef: true })(Child);
Since you want the same state in all of the child instances, I'd say that what you want to do is actually set the state in the parent, then pass that into all of the children as a prop. You'll need a click handler method in the parent, which you'll pass to the children as well.
Ok, I haven't tested this code, but the basic logic will be something like:
Parent
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.state = {
"value": "" // assuming 'value' is a string
}
}
handleClick(value) {
this.setState({ "value": value })
}
render() {
return(
<ChildComponent
handleClick={this.handleClick}
value={this.state.value} />
<ChildComponent
handleClick={this.handleClick}
value={this.state.value} />
<ChildComponent
handleClick={this.handleClick}
value={this.state.value} />
)
}
Child (since you talk about state of a child, setting this up as though it is a stateful component, not a presentational component)
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.state = {
"value": "" // assuming 'value' is a string
}
}
componentWillReceiveProps(nextProps) {
this.setState( {"value": nextProps.value} )
}
handleClick() {
const value = "Hey here's a value!"
props.handleClick(value) // call the parent's handleClick
}
render() {
return(
<div>
<button onClick={this.handleClick}>Set value</button>
</div>
)
}
But truth be told, I wouldn't even bother setting the state in the child - just set it in the parent and access it via props.