Cannot read property of null - react.js state - reactjs

I'm trying to make a todo list app with React and Firebase.
It's a single page app (except for login).
In my Page's component, I call to a List's component.
In my List's component, I get my firebase data that I put in a state.
In React Dev Tool, in my List's component, I can see a state like this :
list{items{...},listName:"exemple"}
So, in my return, I'm trying to show the listName's value like that: {this.state.list.listName}
But I have the following error: : Cannot read property 'listName' of null
This is my List's component :
class ListPage extends Component {
constructor(props) {
super(props);
this.state = {
list: null,
};
}
componentDidMount() {
this.listRef = db.ref(`users/${this.props.user.uid}/lists
${this.props.cle}`);
this.listRef.on('value', data=>{
this.setState({
list: data.val()
})
})
}
render() {
return (
<div>
<h1>List : {this.state.list.listName}</h1>
</div>
);
}
}
Where I am wrong ?
Thanks

The reason is because when your component first mounts, the state list is null. Only when the asynchronous firebase response comes back is it loaded with data and becomes non-null. The solution is to check if it's null, so in your render() function change this part:
<h1>List : {this.state.list.listName}</h1>
to this:
{ this.state.list && <h1>List : {this.state.list.listName}</h1> }
In general it's good to check if objects are defined before accessing their properties. Especially ones you've explicitly defined to be null at some point.

Related

this.setState is not updating the state property

I am setting the state in a method call from a constructor but the state property is still null. Some modification on the code gives be this error in the console,
index.js:1375 Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the ResultContainer component.
In my component constructor i am calling a method. That method in turn calls another method and is trying populate an state property with an array. I get two different variants of error/warning. In my code if uncomment the lines inside render method i get searchedAddress is null error. if i leave the lines as commented then in the console i get the above error.
On my render() method i can check for null and that certainly does not throw and error but the the result items are not getting loaded no matter what i do. this question, State not changing after calling this.setState seems somewhat relevant but i am not sure i could i re-render the item asynchronously.
import React from 'react';
import ResultItem from './ResultItem'
import AddressService from '../Api/AddressService'
class ResultContainer extends React.Component{
constructor(props){
super(props);
this.state = {
searchedAddresses : null
}
this.intiateSearch();
}
intiateSearch =() => {
if(this.props.searchedTerm != null){
var addressService = new AddressService();
//this is just getting an items from json for now. i checked and result does contain items
var result = addressService.searchAddress(this.props.searchedAddresses);
//the below line does not seem to work
this.setState({
searchedAddresses : result
});
}
}
render(){
return(
<div className="mt-3">
<h4>Seaching for postcode - <em className="text-primary">{this.props.searchedTerm}</em></h4>
<div className="container mt-1">
{
//below gives an error stating searchedAddresses is null
// this.state.searchedAddresses.map((address, index)=>{
// return(<ResultItem address={address}/>);
// })
}
</div>
</div>
);
}
}
export default ResultContainer;
You shouldn't call component functions inside of the constructor method, the component is not yet mounted at this point and therefore your component functions aren't available to be used here yet. In order to update your state. You used to be able to use the componentWillMount lifecycle method but that is now considered legacy. You should be calling any component initializing functions inside of the componentDidMount lifecycle method.
Change your code like so :
(Notice the check for state initially being null in the render function)
import React from 'react';
import ResultItem from './ResultItem'
import AddressService from '../Api/AddressService'
class ResultContainer extends React.Component{
constructor(props){
super(props);
this.state = {
searchedAddresses : null
}
}
componentDidMount() {
this.intiateSearch();
}
intiateSearch =() => {
if(this.props.searchedTerm != null){
var addressService = new AddressService();
//this is just getting an items from json for now. i checked and result does contain items
var result = addressService.searchAddress(this.props.searchedAddresses);
this.setState({
searchedAddresses : result
});
}
}
render(){
return(
<div className="mt-3">
<h4>Seaching for postcode - <em className="text-primary">{this.props.searchedTerm}</em></h4>
<div className="container mt-1">
{
this.state.searchedAddresses !== null &&
this.state.searchedAddresses.map((address, index)=>{
return(<ResultItem address={address}/>);
})
}
</div>
</div>
);
}
}
export default ResultContainer;
I see you're calling this.intiateSearch(); in constructor which will call setState on a not yet mounted component.
So why don't you call this.intiateSearch(); inside componentDidMount() lifecyle after the component is mounted?
componentDidMount() {
this.intiateSearch();
}

React.js, correct way to iterate inside DOM

Im new in ReactJS...
I have a project with the following class components structure:
index.js
--app
--chat
--header
--left
--right
In the chat.js component, I make a google search with the api to retrieve images based on specific keyword... My intuitive solution was:
this.client.search("cars")
.then(images => {
for(let el of images) {
ReactDOM.render(<img src="{{el.url}}" syle="{{width: '100%'}}" />, document.querySelector('#gimages'));
}
});
It is correct? Or I may to use Components with stored states with flux (redux)?
Perhaps a simpler more conventional use of react would achieve what your require?
You could follow a pattern similar to that shown below to achieve what you require in a more "react-like" way:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = { images : [] } // Set the inital state and state
// model of YourComponent
}
componentDidMount() {
// Assume "client" has been setup already, in your component
this.client.search("cars")
.then(images => {
// When a search query returns images, store those in the
// YourComponent state. This will trigger react to re-render
// the component
this.setState({ images : images })
});
}
render() {
const { images } = this.state
// Render images out based on current state (ie either empty list,
// no images, or populated list to show images)
return (<div>
{
images.map(image => {
return <img src={image.url} style="width:100%" />
})
}
</div>)
}
}
Note that this is not a complete code sample, and will require you to "fill in the gaps" with what ever else you have in your current Chat component (ie setting up this.client)
This is not the way you should go, you don't need to use ReactDOM.render for each item. Actually, you don't need to use ReactDOM.render at all. In your component you can use a life-cycle method to fetch your data, then set it to your local state. After getting data you can pass this to an individual component or directly render in your render method.
class Chat extends React.Component {
state = {
images: [],
}
componentDidMount() {
this.client.search( "cars" )
.then( images => this.setState( { images } ) );
}
renderImages = () =>
this.state.images.map( image => <Image key={image.id} image={image} /> );
render() {
return (
<div>{this.renderImages()}</div>
);
}
}
const Image = props => (
<div>
<img src={props.image.url} syle="{{width: '100%'}}" />
</div>
);
At this point, you don't need Redux or anything else. But, if you need to open your state a lot of components, you can consider it. Also, get being accustomed to using methods like map, filter instead of for loops.

Unable to access nested data

I noticed I cannot access to a nested js object.
this.DB.get(2) returns an object in the form
{
id:1,
name: "my title",
Obj:{en:"english",fr:"french"}
}
This is the component
export default class Item extends Component {
state = {
Item:{}
}
constructor(props) {
super(props);
}
componentDidMount(){
this.DB = $DB()
this.setState({
Item: this.DB.get(2)
})
}
render() {
const { params } = this.props.navigation.state;
const id = params ? params.id : null;
const Item = this.state.Item
return (
<View style={{flex:1}}>
Number field: {Item.id} |
String field: {Item.name} |
Object field: <FlatList data={Object.entries(Item.Obj)} />
</View>
);
}
}
The problem: I cannot access to Item.Obj.
I got it, setState is async, I'm not sure this is causing the issue though. Anyway: is there a clean way for render the component in setState callback?
edit: I've just rebuilt (still in debug mode) and now it works, I do not changed anything, just added some console.log() around.
Anyway, I don't feel so safe. setState() is async and still I see around the web this function used wildly as it would be sync.
If the data I want to render are in the state, is there a way to render the component always after the state update?
Another doubt: you see the Component, it just does an access to a big JS object and shows some data and that's all. Do I really need to pass through the component state?
What do you think if I would move those 2 lines inside the render() method?
const DB = $DB()
const Item = DB.get(2)

Change items in list in React when an item has been clicked

I'm quite new to ReactJS and I have trouble understand how different components can communicate with each other.
I do have a component that will render a list and each list item is a different component. I want to keep the components as small as possible.
Now, each list item can have a property named active and if the property is set to true, an additional class is added.
This is the class that defines a single item in the component.
See this below code for my component defining a single list item:
export default class OfficeRibbonTab extends React.Component {
constructor(props) {
super(props);
this.state = {
active: props.active ? props.active : false
}
// Assign all the correct event handlers.
this.setActive = this.setActive.bind(this);
}
setActive() {
this.setState({active: true});
}
render() {
// When the tab is defined as active, add the "active" class on it.
if (this.state.active)
{ var tabClassName = "active"; }
return <li onClick={this.setActive} className={tabClassName}>{this.props.tabName}</li>;
}
}
So, I have propery active which is passed to this component, which I store in the components state.
When I click the list item, I set to state of the current item to be active.
The problem is that I want all the other list items to become inactive, thus setting the state of active to false.
The code below is an overview of my list:
export default class OfficeRibbon extends React.Component {
constructor(props) {
// Call the 'base' constructor.
super(props);
}
render() {
var tabs = [];
// Loop over all the tab elements and define how the element should be rendered.
for (var i = 0; i < this.props.dataSource.tabs.length; i ++)
{
if (i == 1)
{ tabs.push(<OfficeRibbonTab active={true} key={this.props.dataSource.tabs[i].name} tabName={this.props.dataSource.tabs[i].name}></OfficeRibbonTab>); }
else
{ tabs.push(<OfficeRibbonTab key={this.props.dataSource.tabs[i].name} tabName={this.props.dataSource.tabs[i].name}></OfficeRibbonTab>); }
}
return (<div>
<div className="wrap-top">
<OfficeRibbonTitle title={this.props.title}/>
<ul className="tabs">
{tabs}
</ul>
</div>
</div>);
}
}
It doesn't seem like rocket science, but I want to do it the React way without re-inventing the wheel.
Anyone who can guide me in the right direction?
Kind regards
It looks like OfficeRibbonTab manages its own state, which is fine, but it never informs its parent component of the state change. The most common approach would be to supply a callback function to each child component, so that it can then communicate back to the parent:
For example, OfficeRibbon will now contain a function handleTabSelect that gets passed down as a prop to each OfficeRibbonTab. And in OfficeRibbonTab, when a tab is clicked, you simply invoke the callback, and pass in the selected tab's index or id:
OfficeRibbonTab.jsx
setActive(tabIndex) {
this.props.handleTabSelect(tabIndex);
}
OfficeRibbon.jsx
handleTabSelect(tabIndex) {
this.setState({activeIndex: tabIndex});
}
Now in OfficeRibbon, you update your state to set the activeIndex or activeId, again either by the index or an identifier of your choosing. By setting state in OfficeRibbon, we necessarily force a render() of its children. As a result, we simply match the index of the iterator with the activeIndex of your state, when we iterate in render():
<OfficeRibbonTab active={index === this.state.activeIndex} onClick={this.handleTabSelect} ... />

componentDidMount() not being called when react component is mounted

I've been attempting to fetch some data from a server and for some odd reason componentDidMount() is not firing as it should be. I added a console.log() statement inside of componentDidMount() to check if it was firing. I know the request to the server works as it should As I used it outside of react and it worked as it should.
class App extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
obj: {}
};
};
getAllStarShips () {
reachGraphQL('http://localhost:4000/', `{
allStarships(first: 7) {
edges {
node {
id
name
model
costInCredits
pilotConnection {
edges {
node {
...pilotFragment
}
}
}
}
}
}
}
fragment pilotFragment on Person {
name
homeworld { name }
}`, {}). then((data) => {
console.log('getALL:', JSON.stringify(data, null, 2))
this.setState({
obj: data
});
});
}
componentDidMount() {
console.log('Check to see if firing')
this.getAllStarShips();
}
render() {
console.log('state:',JSON.stringify(this.state.obj, null, 2));
return (
<div>
<h1>React-Reach!</h1>
<p>{this.state.obj.allStarships.edges[1].node.name}</p>
</div>
);
}
}
render(
<App></App>,
document.getElementById('app')
);
The issue here is that the render method is crashing, because the following line is generating an error
<p>{this.state.obj.allStarships.edges[1].node.name}</p>
Fix this to not use this.state.obj.allStarships.edges[1].node.name directly, unless you can guarantee that each receiver is defined.
Check your component's key
Another thing that will cause this to happen is if your component does not have a key. In React, the key property is used to determine whether a change is just new properties for a component or if the change is a new component.
React will only unmount the old component and mount a new one if the key changed. If you're seeing cases where componentDidMount() is not being called, make sure your component has a unique key.
With the key set, React will interpret them as different components and handle unmounting and mounting.
Example Without a Key:
<SomeComponent prop1={foo} />
Example with a Key
const key = foo.getUniqueId()
<SomeComponent key={key} prop1={foo} />
Also check that you don't have more than one componentDidMount if you have a component with a lot of code. It's a good idea to keep lifecycle methods near the top after the constructor.
I encountered this issue (componentDidMount() not being called) because my component was adding an attribute to the component state in the constructor, but not in the Component declaration. It caused a runtime failure.
Problem:
class Abc extends React.Component<props, {}> {
this.state = { newAttr: false }; ...
Fix:
class Abc extends React.Component<props, {newAttr: boolean}> {
this.state = { newAttr: false }; ...

Resources