can't access object's properties within object in react - reactjs

I can't seem to access data that's part of an object within an object. here I'm trying to access likes in profile which would otherwise be fine using vanilla javascript to print out this.state.data.profile.likes
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: {}
};
}
componentDidMount() {
var x = {
"notifications": 12,
"profile": {
"likes": 5
}
};
this.setState({
data: x
});
}
render() {
const {notifications, profile} = this.state;
return (
<div>
<span>Notifications {notifications}</span>
<span>Likes {profile.likes}</span>
</div>
);
}

Before mounting - and on the initial render - your state looks like this:
{
data: {}
}
After mounting - on the second render - your state looks like this:
{
data: {
notifications: 12,
profile: {
likes: 5
}
}
}
You're trying to access this.state.profile.likes which doesn't exist. Presumably you mean to access this.state.data.profile.likes which does exist, but only on the second render.

I noticed this while also trying to fix the same problem. The constructor should be:
constructor(props) {
super(props);
this.state = {
data: {
profile: {}
}
};
}
Always initialize objects within objects

https://reactjs.org/docs/react-component.html#mounting
render -> componentDidMount
put state data in constructor

Related

Reusing React components to handle a different DB collection

I have a beginner question about React. I just wrote this component:
class MovieInput extends React.Component {
constructor(props) {
super(props);
firebase.initializeApp(config);
this.state = {
movies: []
};
}
....
}
It is working fine, saving data in Firebase under a collection called movies.
I am starting to work on a second component looking like this:
class BookInput extends React.Component {
constructor(props) {
super(props);
firebase.initializeApp(config);
this.state = {
books: []
};
}
....
}
I can already see that most of the code for the two components is going to be the same, making it pointless to write it twice. So here comes the question. How can I write a standard component, using props that I could pass, and have something like:
<MediaInput type='movies'/>
<MediaInput type='books'/>
instead of:
<MovieInput />
<BookInput />
The new component would probably look like:
class MediaInput extends React.Component {
constructor(props) {
super(props);
firebase.initializeApp(config);
this.state = {
// Make use of some prop to set collection adequately ....
// This is what I don't know how to do ....
collection: []
};
}
....
}
It may be useful to set the background of my question, to say that I got inspired by this tutorial to get started on writing the code above.
........
After some more work:
I am trying to implement a more generic component (MediaInput) (as suggested in the answer by srgbnd). I do that by modifying the code in MovieInput (already working). I still hit a few issues on its implementation:
componentDidUpdate(prevProps, prevState) {
//if (prevState !== this.state) { // This line may need to be modified to the following.
if (prevState.db !== this.state.db) {
this.writeUserData();
}
}
writeUserData = () => {
firebase.database()
.ref("/")
.set(this.state);
};
handleSubmit = event => {
event.preventDefault();
.....
// These 3 lines should be modified. Probably replacing movies by something like state.db.{props.type} ???
const { movies } = this.state;
movies.push({ ... });
this.setState({ movies });
.....
};
I'm not familiar with firebase database.
But if I assume that
Firebase.database()
.ref("/")
.set(this.state);
Handle on his own the differents keys of your state (like all the CRUD behaviour on each values) this simple trick should work with your type props :
class MediaInput extends React.Component {
constructor(props) {
super(props);
firebase.initializeApp(config);
this.state = {
// The array will have the key 'movies' or 'books'
[props.type]: []
};
}
....
}
But take care to always have a 'type' prop defined !
First, I would put the firebase details into a separate class to respect the SOLID Dependency Inversion Principle. For example:
class AppDatabase {
constructor() {
firebase.initializeApp(config);
}
addCollection(data) {
return firebase.database().ref('/').set(data);
}
}
Second, I would use the type prop as you do.
<MediaInput type='movies'/>
<MediaInput type='books'/>
Finally, use the AppDatabase in the components. For example
import { AppDatabase } from '../services';
class MediaInput extends React.Component {
constructor(props) {
super(props);
this.appDatabase = new AppDatabase();
this.state = {
db: {
[props.type]: []
}
};
}
addCollection() {
this.appDatabase.addCollection(this.state.db);
}
}

How to update React component?

I have a child object (element of list) which is rendered inside(?) the parent one. The component has the following properties (from JSON):
contract
{
id,
name,
}
But I need to add another one additional property which is filled in after an HTTP request with an external function to the API (for example, uuid) using one of the existing properties of an object.
My current React code looks the following way (only child component is provided):
class Contract extends Component {
constructor(props){
super(props);
this.state = {data: this.props.contract};
getUuidByName(this.state.data.name).then(val => {
this.state.data.uuid = val;
});
}
componentDidUpdate(){ }
render() {
return <tr>
<td>{this.state.data.id}</td>
<td>{this.state.data.name}</td>
<td>{this.state.data.uuid}</td>
</tr>
}
}
Everything rendered good except an additional property: uuid. Of course I do something wrong or don't do some important thing, but I have no idea what to do.
You are mutating state in the constructor. Never mutate state directly. If you are needing to set/initialize some state after it's been constructed, or mounted, then you should use the componentDidMount lifecycle method. Ensure you enqueue the state update via the this.setState method.
class Contract extends Component {
constructor(props){
super(props);
this.state = {
data: props.contract,
};
}
componentDidMount() {
getUuidByName(this.state.data.name).then(val => {
this.setState(prevState => ({
data: {
...prevState.data,
uuid: val,
},
}));
});
}
componentDidUpdate(){ }
render() {
return (
<tr>
<td>{this.state.data.id}</td>
<td>{this.state.data.name}</td>
<td>{this.state.data.uuid}</td>
</tr>
);
}
}
Do not modify state directly.
Because you're directly modifying the state, React isn't triggering a re-render.
Try the following in your constructor instead:
constructor(props){
super(props);
this.state = {data: this.props.contract};
getUuidByName(this.state.data.name).then(val => {
this.setState({
data: {
...this.state.data,
uuid: val
}
});
});
}

Attempt to update state onClick results in "this.setState is not a function" error

In the following SideMenuRow child div inside my SideMenuContainer component, I'm calling the changeInfoList method. The method is in the parent App.js.
In App.js:
<SideMenuContainer changeInfoList={this.changeInfoList} genInfoList={this.state.genInfoList} groupedObjectsList={this.state.groupedObjectsList}/>
In SideMenuContainer.js:
<SideMenuRow onClick={this.props.changeInfoList(genInfoElement.name)}>
Here are the relevant sections of the App.js code involving the method and state:
constructor(props) {
super(props);
this.state = {
genInfoList: [],
groupedObjectsList: [],
cardInfo: {
name: data[0].name
},
data: []
};
}
changeInfoList(rowName){
//change the card rendered based on which row is clicked
this.setState({
cardInfo: {
name: rowName
}
})
}
What's incorrect about the way I'm structuring this method that isn't allowing setState to work?
You're getting this error because the keyword this is not pointing to your component's execution context and it's current execution context does not have a method called setState()
You can approach this a couple of ways:
1) Define an arrow function like the following:
const changeInfoList = (rowName) => {
//change the card rendered based on which row is clicked
this.setState({
cardInfo: {
name: rowName
}
})
}
By using an arrow-function (non-binding this), the this keyword now points to the closest object, in this case your class component, which has a setState() method.
2) Bind your current function inside your component's constructor.
constructor(props) {
super(props);
this.state = {
genInfoList: [],
groupedObjectsList: [],
cardInfo: {
name: data[0].name
},
data: []
};
this.changeInfoList = this.changeInfoList.bind(this)
}
This explicitly tells your function that this is now pointed to your class component's context.
Now assuming you did either of these steps, you would then have to pass down changeInfoList into your child components:
In App.js:
<SideMenuContainer changeInfoList={this.changeInfoList} genInfoList={this.state.genInfoList} groupedObjectsList={this.state.groupedObjectsList}/>
In SideMenuContainer:
<SideMenuRow onClick={() => { this.props.changeInfoList(genInfoElement.name) }}>
constructor(props) {
super(props);
this.state = {
genInfoList: [],
groupedObjectsList: [],
cardInfo: {
name: data[0].name
},
data: []
};
// Bind the function
this.changeInfoList = this.changeInfoList.bind(this)
}
changeInfoList(rowName){
//change the card rendered based on which row is clicked
this.setState({
cardInfo: {
name: rowName
}
})
}

Accessing React state variables when they are loaded form promise

I have a simple component that loads in the data from a job as follows
export class ViewJob extends Component {
constructor() {
super();
this.state = {
currentJob: {},
checkedCompleted: false,
};
}
componentDidMount() {
loadJobFromId(this.props.id)
.then(job => this.setState({currentJob: job}))
}
In my render when I try to access a nested property:
this.state.currentJob.selectedCompany
I get an error:
Cannot read property 'root' of undefined
This seems to be because the state of selectedCompany is first undefined and then when the promises resolves it is set.
What is the best practice for handling this in React?
You should render the part that's using this.state.currentJob.selectedCompany only once the promise is resolved. So you can try something like this:
export class ViewJob extends Component {
constructor() {
super();
this.state = {
currentJob: {},
checkedCompleted: false,
jobsFetched: false
};
}
componentDidMount() {
loadJobFromId(this.props.id)
.then(job => this.setState({currentJob: job, jobsFecthed: ture}))
}
render(){
{ this.state.jobsFetched ? **[RENDER_WHAT_YOU_ARE_RENDERING_NOW]** : <Text>Loading...</Text> }
This way the component will be rendered only when the jobs are fetched/ promise resolved.

React child component can't get props.object

My parent component is like this:
export default class MobileCompo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
datasets: {}
};
this.get_data = this.get_data.bind(this);
}
componentWillMount() {
this.get_data();
}
async get_data() {
const ret = post_api_and_return_data();
const content={};
ret.result.gsm.forEach((val, index) => {
content[val.city].push()
});
this.setState({data: ret.result.gsm, datasets: content});
}
render() {
console.log(this.state)
// I can see the value of `datasets` object
return (
<div>
<TableElement dict={d} content={this.state.data} />
<BubbleGraph maindata={this.state.datasets} labels="something"/>
</div>
)
}
}
child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
console.log(this.props);
// here I can't get this.props.maindata,it's always null,but I can get labels.It's confusing me!
}
componentWillMount() {
sortDict(this.props.maindata).forEach((val, index) => {
let tmpModel = {
label: '',
data: null
};
this.state.finalData.datasets.push(tmpModel)
});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}
I tried many times,but still don't work,I thought the reason is about await/async,but TableElement works well,also BubbleGraph can get labels.
I also tried to give a constant to datasets but the child component still can't get it.And I used this:
this.setState({ datasets: a});
BubbleGraph works.So I can't set two states at async method?
It is weird,am I missing something?
Any help would be great appreciate!
Add componentWillReceiveProps inside child componenet, and check do you get data.
componentWillReceiveProps(newProps)
{
console.log(newProps.maindata)
}
If yes, the reason is constructor methos is called only one time. On next setState on parent component,componentWillReceiveProps () method of child component receives new props. This method is not called on initial render.
Few Changes in Child component:
*As per DOC, Never mutate state variable directly by this.state.a='' or this.state.a.push(), always use setState to update the state values.
*use componentwillrecieveprops it will get called on whenever any change happen to props values, so you can avoid the asyn also, whenever you do the changes in state of parent component all the child component will get the updates values.
Use this child component:
export default class BubbleGraph extends React.Component {
constructor(props) {
super(props);
this.state = {
finalData: {datasets: []}
};
}
componentWillReceiveProps(newData) {
let data = sortDict(newData.maindata).map((val, index) => {
return {
label: '',
data: null
};
});
let finalData = JSON.parse(JSON.stringify(this.state.finalData));
finalData.datasets = finalData.datasets.concat(data);
this.setState({finalData});
}
render() {
return (
<div>
<h2>{this.props.labels}</h2>
<Bubble data={this.state.finalData}/>
</div>
);
}
}

Resources