State Variable assigned to props data model undefined inside setState React - reactjs

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] });

Related

Render child component in parent after re-rendering sibling component

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>;
}
}

How do I limit the user to only selecting one component?

I have the following code that simply constructs blocks for our products and the selected state allows the component to be selected and unselected. How can I figure out which of these components are selected and limit the user to only selecting one at a time. This is ReactJS code
import React from 'react';
export default class singleTile extends React.Component{
constructor(props){
super(props);
this.title = this.props.title;
this.desc = this.props.desc;
this.svg = this.props.svg;
this.id = this.props.id;
this.state = {
selected: false
}
}
selectIndustry = (event) => {
console.log(event.currentTarget.id);
if(this.state.selected === false){
this.setState({
selected:true
})
}
else{
this.setState({
selected:false
})
}
}
render(){
return(
<div id={this.id} onClick={this.selectIndustry}className={this.state.selected ? 'activated': ''}>
<div className="icon-container" >
<div>
{/*?xml version="1.0" encoding="UTF-8"?*/}
{ this.props.svg }
</div>
</div>
<div className="text-container">
<h2>{this.title}</h2>
<span>{this.desc}</span>
</div>
</div>
)
}
}
You need to manage the state of the SingleTile components in the parent component. What i would do is pass two props to the SingleTile components. A onClick prop which accepts a function and a isSelected prop that accepts a boolean. Your parent component would look something like this.
IndustrySelector.js
import React from 'react';
const tileData = [{ id: 1, title: 'foo' }, { id: 2, title: 'bar' }];
class IndustrySelector extends Component {
constructor(props) {
super(props);
this.state = { selectedIndustry: null };
}
selectIndustry(id) {
this.setState({ selectedIndustry: id });
}
isIndustrySelected(id) {
return id === this.state.selectedIndustry;
}
render() {
return (
<div>
{tileData.map((data, key) => (
<SingleTile
key={key}
{...data}
onClick={() => this.selectIndustry(data.id)}
isSelected={this.isIndustrySelected(data.id)}
/>
))}
</div>
);
}
}
The way this works is as follows.
1. Triggering the onClick handler
When a user clicks on an element in SingleTile which triggers the function from the onClick prop, this.selectIndustry in the parent component will be called with the id from the SingleTile component.
Please note that in this example, the id is remembered through a
closure. You could also pass the id as an argument to the function of
the onClick prop.
2. Setting the state in the parent component
When this.selectIndustry is called it changes the selectedIndustry key of the parent component state.
3. Updating the isSelected values form the SIngleTile components
React will automatically re-render the SingleTile components when the state of the parent component changes. By calling this.isIndustrySelected with the id of the SingleTile component, we compare the id with the id that we have stored in the state. This will thus only be equal for the SingleTile that has been clicked for the last time.
Can you post your parent component code?
It's not so important, but you can save some time by using this ES6 feature:
constructor(props){
super(props);
const {title, desc, svg, id, state} = this.props;
this.state = {
selected: false
}
}

How to update parent state from child component in React + send a paramater

I am following this tutorial but it does not say how to pass to the function a parameter.
In they the child they have
<Button onClick={this.props.action} />
handler(id) {
this.setState({
messageShown: true,
id : id
});
}
what happens if I want to send a value along with it(say some id or something).
I tried to do
<Button onClick={() => this.props.action(1)} />
but then my "state" is undefined.
It's hard to say what's going wrong without seeing a full code example, but what you're trying to do is certainly possible. Here's a working example.
class Parent extends React.Component {
constructor(props) {
super(props)
// Bind the this context to the handler function
this.handler = this.handler.bind(this);
// Set some state
this.state = {
messageShown: false
};
}
// This method will be sent to the child component
handler(id) {
this.setState({
messageShown: true,
id: id
});
}
// Render the child component and set the action property with the handler as value
render() {
console.log(this.state);
return (
<div>
<Child action={this.handler} />
<div>{this.state.id}</div>
</div>
);
}
}
class Child extends React.Component {
render() {
return (
<div>
{/* The button will execute the handler function set by the parent component */}
<button onClick={() => this.props.action(1)} > button </button>
</div>
)
}
}
ReactDOM.render(<Parent />, document.getElementById('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="main"></div>
To achieve what you want, in your Child component you should call a function that calls passed function. In this case you’ll be able to pass any parameter you want.
Let’s code!
Your Parent component will be:
class Parent extends React.Component {
constructor(props) {
super(props)
// Bind the this context to the handler function
this.handler = this.handler.bind(this);
// Set some state
this.state = {
messageShown: false,
id: -1 // initialize new state property with a value
};
}
// This method will be sent to the child component
handler(id) {
this.setState({
messageShown: true,
id: id
});
}
// Render the child component and set the action property with the handler as value
render() {
return <Child action={this.handler} />
}
}
And your Child component will be
class Child extends React.Component {
render() {
return (
<div>
{/* The button will execute the handler function set by the parent component, passing any parameter */}
<Button onClick={() => this.props.action(1)} />
</div>
)
}
}
Hope this helps
Usually when this.state is undefined after invoking a callback function it is a binding issue. Double check that the handler function has this bound to it in the parent component's constructor.
this.handler = this.handler.bind(this);
More on binding: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

React child receive old state props, NOT updated state props

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.

How to setState for all instances of the same component type in that component

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.

Resources