React component will mount is not getting called - reactjs

I have been working on a chat application using react and websockets, My problem is the method
componetWillUnmount()
doesn't get called when the state changes and component re-renders.
I have been trying to add 'li' elements to my chatArea component for every new message coming in a chat, and as soon as I am selecting another chat, I want to remove all those 'li' elements that were rendered in previous chat, for that I have tried 2 things, one to remove all child of or I am changing the state. But componentWillUnmount is not getting called. And i am not able to remove li elements.
Below is my code
import React from 'react'
export default class ChatArea extends React.Component {
constructor (props) {
super(props)
this.state = {
currentUser: this.props.currentUser,
selectedUser: this.props.selectedUser,
messages: []
}
this.handleMessage = this.handleMessage.bind(this)
}
handleMessage (obj) {
let messages = this.state.messages
messages.push(obj)
this.setState({
messages: messages
})
}
componentWillMount () {
window.socket.on('show message', obj => {
this.handleMessage(obj)
})
}
componentDidMount () {
window.socket.emit('join', {
sender: this.state.currentUser,
receiver: this.state.selectedUser
})
}
componentWillUnmount () {
console.log('something')
const chatList = this.refs.chatList
while (chatList.hasChildNodes()) {
console.log('removing children', chatList.lastChild)
chatList.removeChild(chatList.lastChild)
}
orrrrrrrrrrrrrr
this.setState({
messages: []
})
}
render () {
console.log('chatARea state', this.state)
let messages = this.state.messages
let i = 0
return (
<div className='row chat-area'>
<ul className='col m12' ref='chatList'>
{messages.map(msg => <li key={i++}>{msg.sentBy.firstname + ': ' + msg.message}</li>)}
</ul>
</div>
)
}
}
module.exports = ChatArea

I found out the answer of my own question, the state of the component was not getting changed, so the method componentWillMount() was not getting called.
Thanks everyone for the help

Related

How to render updated state in react Js?

I am working on React Js in class component I am declaring some states and then getting data from API and changing that state to new value but React is not rendering that new value of state. but if I console.log() that state it gives me new value on console.
My code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState({ unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}
This is printing 2 on console but rendering 0 on screen. How can I get updated state(2) to render on screen.
and if I visit another page and then return to this page then it is rendering new value of state (2).
Please call getUnread() function in componentDidMount, something like this
componentDidMount() {
this.getUnread()
}
This is because in React class components, while calling setState you it is safer to not directly pass a value to set the state (and hence, re-render the component). This is because what happens that the state is set as commanded, but when the component is rerendered, once again the state is set back to initial value and that is what gets rendered
You can read this issue and its solution as given in react docs
You pass a function that sets the value.
So, code for setState would be
this.setState((state) => { unread: data.count });
Hence, your updated code would be :
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState((state) => { unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}

Unmounting child component throws can't call setstate on an unmounted component in reactjs

when i unmount child component i get can't setstate on unmounted component warning.
What i am trying to do?
I have a parent component ViewItems and child component ItemsList.
In the ViewItems component i retrieve the items list from the server using load_items method which in turn uses client.get_file method. and store those items list in state named "items". I call this load_items method in componenDidMount method. However this will not show the new details for another item.
To give a clear picture of the problem. Consider i render items in one page. When i click on one item it takes me to other page (in this case items component in mounted) and when i click a button click to view item details it lists the details related to that item. When i click the button to get back to list of items (page where we were before) and click another item. It should display details related to the new item clicked.
However, in this case when i click the new item it shows previous item details unless page refresh.
To overcome this, on componentDidUpdate i call this load_items method. This works. But, when i dont close the child component meaning the layout where details of item is shown...i get the can't call setstate on unmounted component warning. This error is shown after child component is unmounted.
Below is the code,
class ViewItems extends React.PureComponent {
constructor(props) {
super(props);
this.default_root_item = {
name: 'Items',
index: 0,
children: [],
};
this.state = {
root_items: this.default_root_item,
items: [],
};
}
componentDidMount() {
this.load_items();
}
componentDidUpdate() {
this.load_items();
}
componentWillUnmount() {
this.unlisten_path_change();
}
load_items = () => {
const file_name = 'file_name.json';
client.get_file(this.props.item_id, file_name, 'json')
.then((request) => {
const items = request.response;
this.setState({items: [this.default_root_item]});}
this.handle_path_change(this.props.location.pathname);})};
return (
<ChildComponent
on_close={this.handle_item_close}
root_item={this.state.root_item}/>)}
export default class ChildComponent extends React.PureComponent {
<Items
items={root_item}/>
function Items(props) {
return (
<ul className="Items_list">
<div className="items">
{props.items.map((item, index) => {
return (
<Item
key={index}
item={item}
/>
);
})}
</div>
</ul>
);}
}
First of all, do not make an API call in componentDidUpdate without making a check whether the props changed or not.
Second: While setting state from API request response, check if the component is still mounted or not
class ViewItems extends React.PureComponent {
constructor(props) {
super(props);
this.default_root_item = {
name: 'Items',
index: 0,
children: [],
};
this.state = {
root_items: this.default_root_item,
items: [],
};
this._isMounted = true;
}
componentDidMount() {
this.load_items();
}
componentDidUpdate(prevProps) {
if (prevProps.item_id !== this.props.item_id) {
this.load_items();
}
}
componentWillUnmount() {
this._isMounted = false;
this.unlisten_path_change();
}
load_items = () => {
const file_name = 'file_name.json';
client.get_file(this.props.item_id, file_name, 'json')
.then((request) => {
const items = request.response;
if (this._isMounted) {
this.setState({items: [this.default_root_item]});
this.handle_path_change(this.props.location.pathname);
}
})
};

Passing value to props reactjs

I am trying pass value to my child components. The value that I am getting is coming from the an API that I called in my parent component and being called in the componentDidMount but the problem is the child components is not reading the props I am passing in his own componentDidMount, its only getting blank even in the reactdevtool it passing correct values. I solved this before but cannot remember what I did can you help. Thanks
Child:
componentDidMount() {
const {
events
} = this.props;
console.log(events)
}
Parent:
class App extends Component {
componentDidMount() {
let self = this;
GetAllMainItems().then(function(GetAllMainItemsResults) {
let MainObject = self.state.MainObject;
self.setState({
GetAllMainItemsResults
});
}
}
render() {
constructor() {
super();
this.state = {
MainObject: []
};
}
return ( <
div className = "App row" >
<
Calendar events = {
this.state.MainObject
}
/>
<
/div>
);
}
There are a few things you need to review.
constructor should be outside of render method.
You do not have to use let self = this. you can just do this.setState({...}) there.
Look at your GetAllMainItems callback. I don't know what you get
there. but you are definitely not setting mainObject in your state.
Instead, you will have this.state.GetAllMainItemsResults.
Recommendations
Try to understand object destructuring.
Use arrow functions
Hope it helps.
Parent Component
```
class App extends Component {
state = {
mainObject: ""
};
componentDidMount() {
GetAllMainItems().then(response => {
this.setState({
mainObject: response
});
});
}
render() {
const { mainObject } = this.state;
return (
<div className="App row">
<Calendar events={mainObject} />
</div>
);
}
}
The problem you are having is that your child component is re-rendering when it receives new events props.
Try adding a componentDidUpdate method to see these props updating:
componentDidUpdate(prevProps, prevState) {
console.log(prevProps, prevState);
console.log('events:', prevProps.events, this.props.events);
}

getting Objects are not valid as a React child error

I'm trying to make a fetch request to my server and set the response json as the component's state.
I get this error
Objects are not valid as a React child (found: object with keys {description,
id, matched_substrings, place_id, reference, structured_formatting, terms, types}). If you meant to render a collection of children, use an array instead.
in li (at Search.js:25)
in ul (at Search.js:30)
in div (at Search.js:28)
in Search (at index.js:8)
this is my code -
import React from 'react'
import { ReactDOM } from "react-dom";
export default class Search extends React.Component {
constructor(props) {
super(props);
this.state = {
places: []
}
this.handleUserInput = this.handleUserInput.bind(this)
}
handleUserInput(e) {
var searchInput = e.target.value;
var url = '/searchLocation/autocomplete?text=' + (searchInput)
if (searchInput.length > 2) {
fetch(url).then(function(response) {return response.json()})
.then((responseJson) => this.setState({places: responseJson["predictions"]}));
//.then( responseJson => console.log(responseJson["predictions"]))
}
}
render() {
const placeList = this.state.places.map(place =>
{
return <li>{place}</li>
});
return (
<div>
<input onChange={this.handleUserInput} type="text" id="PlaceSearch" />
<ul>
{ placeList }
</ul>
</div>
);
}
}
I found similiar errors by googling it but none helped..
Thank you very much for helping
This issue is you are trying to render place which I assume is an object inside the li.
you need to choose which property to render
ex:
return <li>{place.id}</li>
I think you're missing parenthesis;
const placeList = this.state.places.map(place =>
{
return ( <li>{place}</li> )
});

What is best approach to set data to component from API in React JS

We have product detail page which contains multiple component in single page.
Product Component looks like:
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
Here we have 3 APIs for
- Details
- Similar Product
- Other Products
Now from Detail API we need to set data to these components
<Gallery/>
<Video/>
<Details/>
<Contact/>
In which component we need to make a call to API and how to set data to other components. Lets say we need to assign a,b,c,d value to each component
componentWillMount(props) {
fetch('/deatail.json').then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...');
}
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
OR
Do we need to create separate api for each components?
Since it's three different components you need to make the call in the component where all the components meet. And pass down the state from the parent component to child components. If your app is dynamic then you should use "Redux" or "MobX" for state management. I personally advise you to use Redux
class ParentComponent extends React.PureComponent {
constructor (props) {
super(props);
this.state = {
gallery: '',
similarPdts: '',
otherPdts: ''
}
}
componentWillMount () {
//make api call and set data
}
render () {
//render your all components
}
}
The Product component is the best place to place your API call because it's the common ancestor for all the components that need that data.
I'd recommend that you move the actual call out of the component, and into a common place with all API calls.
Anyways, something like this is what you're looking for:
import React from "react";
import { render } from "react-dom";
import {
SearchBar,
Gallery,
Video,
Details,
Contact,
SimilarProd,
OtherProd
} from "./components/components";
class Product extends React.Component {
constructor(props) {
super(props);
// Set default values for state
this.state = {
data: {
a: 1,
b: 2,
c: 3,
d: 4
},
error: null,
isLoading: true
};
}
componentWillMount() {
this.loadData();
}
loadData() {
fetch('/detail.json')
.then(response => {
// if (response.ok) {
// return response.json();
// } else {
// throw new Error('Something went wrong ...');
// }
return Promise.resolve({
a: 5,
b: 6,
c: 7,
d: 8
});
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
if (this.state.error) return <h1>Error</h1>;
if (this.state.isLoading) return <h1>Loading</h1>;
const data = this.state.data;
return (
<div>
<SearchBar/>
<Gallery a={data.a} b={data.b} c={data.c} d={data.d} />
<Video a={data.a} b={data.b} c={data.c} d={data.d} />
<Details a={data.a} b={data.b} c={data.c} d={data.d} />
<Contact a={data.a} b={data.b} c={data.c} d={data.d} />
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
render(<Product />, document.getElementById("root"));
Working example here:
https://codesandbox.io/s/ymj07k6jrv
You API calls will be in the product component. Catering your need to best practices, I want to make sure that you are using an implementation of FLUX architecture for data flow. If not do visit phrontend
You should send you API calls in componentWillMount() having your state a loading indicator that will render a loader till the data is not fetched.
Each of your Components should be watching the state for their respective data. Let say you have a state like {loading:true, galleryData:{}, details:{}, simProducts:{}, otherProducts:{}}. In render the similar products component should render if it finds the respective data in state. What you have to do is to just update the state whenever you receive the data.
Here is the working code snippet:
ProductComponent:
import React from 'react';
import SampleStore from '/storepath/SampleStore';
export default class ParentComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
loading:true,
}
}
componentWillMount () {
//Bind Store or network callback function
this.handleResponse = this.handleResponse
//API call here.
}
handleResponse(response){
// check Response Validity and update state
// if you have multiple APIs so you can have a API request identifier that will tell you which data to expect.
if(response.err){
//retry or show error message
}else{
this.state.loading = false;
//set data here in state either for similar products or other products and just call setState(this.state)
this.state.similarProducts = response.data.simProds;
this.setState(this.state);
}
}
render () {
return(
<div>
{this.state.loading} ? <LoaderComponent/> :
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
{this.state.similarProducts && <SimilarProd data={this.state.similarProducts}/>}
{this.state.otherProducts && <OtherProd data={this.state.otherProducts}/>}
</div>
</div>
);
}
}
Just keep on setting the data in the state as soon as you are receiving it and render you components should be state aware.
In which component we need to make a call to API and how to set data
to other components.
The API call should be made in the Product component as explained in the other answers.Now for setting up data considering you need to make 3 API calls(Details, Similar Product, Other Products) what you can do is execute the below logic in componentDidMount() :
var apiRequest1 = fetch('/detail.json').then((response) => {
this.setState({detailData: response.json()})
return response.json();
});
var apiRequest2 = fetch('/similarProduct.json').then((response) => { //The endpoint I am just faking it
this.setState({similarProductData: response.json()})
return response.json();
});
var apiRequest3 = fetch('/otherProduct.json').then((response) => { //Same here
this.setState({otherProductData: response.json()})
return response.json();
});
Promise.all([apiRequest1,apiRequest2, apiRequest3]).then((data) => {
console.log(data) //It will be an array of response
//You can set the state here too.
});
Another shorter way will be:
const urls = ['details.json', 'similarProducts.json', 'otherProducts.json'];
// separate function to make code more clear
const grabContent = url => fetch(url).then(res => res.json())
Promise.all(urls.map(grabContent)).then((response) => {
this.setState({detailData: response[0]})
this.setState({similarProductData: response[1]})
this.setState({otherProductData: response[2]})
});
And then in your Product render() funtion you can pass the API data as
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details details={this.state.detailData}/>
<Contact/>
<SimilarProd similar={this.state.similarProductData}/>
<OtherProd other={this.state.otherProductData}/>
</div>
);
}
}
And in the respective component you can access the data as :
this.props.details //Considering in details component.

Resources