State of React child doesn't update after updating parent component - reactjs

I try to update the center prop of the child BaseMap component via the parent component. Even though the parent components state gets updated (and I could read the new updated properties in the console.log), it is not passed down to the child properties.
The last thing I tried was the componentWillReceiveProps method. It still doesn't work.
This is my code:
const google = window.google;
let geocoder = new google.maps.Geocoder();
class App extends Component {
constructor(props) {
super(props);
this.state = {
avatar: '',
username: 'someUse03',
realName: '',
location: '',
followers: '',
following: '',
repos: '',
address: '',
}
}
render() {
return (
<div>
<SearchBox fetchUser={this.fetchUser.bind(this)}/>
<Card data={this.state} />
<BaseMap />
</div>
);
}
fetchApi(url) {
fetch(url)
.then((res) => res.json())
.then((data) => {
this.setState({
avatar: data.avatar_url,
username: data.login,
realName: data.name,
location: data.location,
followers: data.followers,
following: data.following,
repos: data.public_repos,
address: geocoder.geocode({'address': data.location}, function(results, status) {
if (status == 'OK') {
var coords = [];
var results = results.map((i) => {
i.geometry.location = i.geometry.location
.toString()
.replace(/[()]/g, '')
.split(', ');
coords.push(i.geometry.location[0], i.geometry.location[1]);
results = coords.map((i) => {
return parseInt(i, 10)
});
return results;
});
} else {
alert('Geocoding was not successfull because ' + status)
}
})
})
});
}
fetchUser(username) {
let url = `https://api.github.com/users/${username}`;
this.fetchApi(url);
}
componentDidMount() {
let url = `https://api.github.com/users/${this.state.username}`;
this.fetchApi(url);
}
}
export default App;
This is the child component:
BaseMap extends React.Component {
constructor(props) {
super(props);
this.state = {
center: [41, 21],
}
}
componentWillReceiveProps(nextProps) {
this.setState({ center: nextProps.address});
}
render() {
return (
<Col md={10} mdPull={1}>
<div className="map">
<GoogleMap
bootstrapURLKeys={'AIzaSyBhzwgQ3EfoIRYT70fbjrWASQVwO63MKu4'}
center={this.state.center}
zoom={11}>
</GoogleMap>
</div>
</Col>
);
}
}

You are fetching inside the render method. this is a big NO NO.
Instead do that in the componentDidMount life cycle method
Another thing that may or may not be related to your problem, Arrays are reference types, that means if you mutate them they still points to the same ref in the memory. this could be problematic for the Reconciliation and diff algorithm of react to determine if the state indeed changed.
when you want to change or return a new array you could simply use the ES6 spread operator:
const nextArray = [...nextProps.address]
this.setState({ center: nextArray });
EDIT
Ok i forgot to mention the most important part here :)
You are not passing any props to <BaseMap /> so you won't get any helpful data in componentWillReceiveProps.

Related

Child component in React doesn't get updated?

The user.entries state gets updated; however, it's not getting passed immediately to the child component, it only gets updated on the second button submit and it passes the first user.entries not the second one, though.
App.js file:
class App extends Component {
constructor(){
super();
this.state = {
input: '',
imageUrl:'',
box:{},
route:'signin',
isSignedIn: false,
user: {
id: '',
name: '',
email: '',
entries: 0,
joined: ''
}
}
}
onButtonSubmit = () => {
this.setState({imageUrl: this.state.input})
app.models.predict(Clarifai.FACE_DETECT_MODEL,this.state.input).then(response => {
if (response){
fetch('http://localhost:3000/image', {
method: 'put',
headers: {'content-type': 'application/json'},
body: JSON.stringify(
{id: this.state.user.id}
)
})
.then(resp => resp.json())
.then(count => {Object.assign(this.state.user, {entries: count})});
}
this.displayFaceBox(this.calculateFaceLocation(response))
}).catch(err => console.log(err));
}
render(){
return(
<div>
<Rank name={this.state.user.name} entries={this.state.user.entries} />
</div>
)
};
Rank.js file:
import React from 'react';
const Rank = ({name, entries}) => {
return(
<div className='mv2'>
<p className='f3 mv0 white'>
{`Welcome back ${name}, your entries count is...`}
</p>
<p className='f1 mv0 b white'>
{`#${entries}`}
</p>
</div>
)
}
export default Rank;
It is not re-rendering the component after you update since you are directly updating the object. You need to instead use this.setState
fetch(...)
.then(...)
.then(count => {
this.setState((previousState) => ({
user: {
...previousState.user,
entries: count,
}
}));
});
Along with updating the state, this.setState also triggers a re-render of the component which is required to see the updated state on the UI as soon as the state is updated.

Why is setState(this.state) not triggering a rerender?

I am trying to get my component to rerender after deleting an entry from the table.
I have tried binding my functions and using this.forceUpdate() as well but nothing seems to work. Any help will be much appreciated!
This is my component
class Directory extends Component {
constructor() {
super();
this.state = {
tenantData: [],
searchText: "",
searchedColumn: "",
tenantInfo: {},
visible: false,
userId: null,
rerender: "",
};
// this.delTenant = this.delTenant.bind(this);
this.deleteAudit = deleteAudit.bind(this);
// this.deleteTenant = this.deleteTenant.bind(this);
// this.onDeleteClick = this.onDeleteClick.bind(this);
}
This is my delete function within my component
onDeleteClick = () => {
this.setState({
...this.state,
visible: false,
});
var tenantList = this.state.tenantData;
for (var i = 0; i < tenantList.length; i++) {
if (tenantList[i].userId == this.state.userId) {
console.log(this.state.userId);
delTenant({ _id: tenantList[i]._id });
deleteTenant({ _id: this.state.userId });
this.deleteAudit({ tenantID: tenantList[i]._id }).then(() => {
this.setState(this.state); // this should rerender the component but it does not
console.log("force update");
});
break;
}
}
And this is my AntD component
<Modal
title="Modal"
visible={this.state.visible}
onOk={this.onDeleteClick}
onCancel={this.hideModal}
okText="Confirm"
cancelText="Cancel"
>
<p>Are you sure you want to delete this Tenant?</p>
</Modal>
EDIT:
Soultion found - My component did rerender, but my tenantData state was unchanged as I forgot to get the data from the database after deleting the entry.
this.deleteAudit({ tenantID: tenantList[i]._id }).then(() => {
// this.setState(this.state); // this should rerender the component but it does not
this.getTenantFunction(); // this gets the new data from database and sets the state of tenantData to the updated one
});
this.setState(this.state); // this should rerender the component but it does not
React doesn't re-render your component if your state or props doesn't change.
I don't know why forceUpdate doesn't work, look at this example
import React from "react";
export default class App extends React.Component {
constructor() {
super();
this.state = {
tenantData: [],
searchText: "",
searchedColumn: "",
tenantInfo: {},
visible: false,
userId: null,
rerender: ""
};
}
onDeleteClick = () => {
console.log("click");
};
onDeleteClickForced = () => {
console.log("clickForced");
this.forceUpdate();
};
render = () => {
console.log("render");
return (
<>
<button onClick={() => this.onDeleteClick()}>Not forced</button>
<button onClick={() => this.onDeleteClickForced()}>Forced</button>
</>
);
};
}

State won't stay updated

After I use setState in getClients(), the state clientOptions is showed correctly. But when I try to pass it on to a child component it is an empty array. I've tried logging it again after componentDidMount and there it seems that the state clientOptions is resetted to its original state ([]). Sorry if this seems like a noob question, I'am pretty new to react.
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
clientFilter: 'ALL',
positionFilter: 'ALL',
clientOptions: []
};
}
componentDidMount = () => {
this.getClients()
this.getTitles()
console.log('this one shows only []: ' + this.state.clientOptions)
}
getClients = () => {
axios.get('http://localhost:5000/clients')
.then((response) => {
let clientObj = [{value: 'ALL', label: 'ALL'}];
const clientOptions = []
response.data.forEach(function (client, index) {
clientObj.push({value: client.name, label: client.name})
});
clientOptions.push({'options' : clientObj});
this.setState(prevState =>{
return{
...prevState,
clientOptions : clientOptions
}
})
console.log('this one works: ' + this.state.clientOptions)
});
}
As requested the state passed on to the child:
render() {
return (
<div className="main">
<Header
clientOptions={this.state.clientOptions}
/>
</div>
);
}

How to use react-data-table-component to display array stored in the constructor this:state?

I am new to the React, And I want to using react-data-table-component to display my fetch data from Api in a sorted table. but the issue I do not know the correct method to use the react-data-table-component.and the instruction of react-data-table-component do not include such example.
Following is my code:
I was trying to put offence or this.state.offence direct into data, but show nothing, anyone please give me some advises about the correct way to use this or some other way create sorted table to show this data.and there is link to the react-data-table-component a link:
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { off } from 'rsvp';
import DataTable from 'react-data-table-component';
const columns = [
{
name: 'Offences',
selector: 'Offences',
sortable: true,
},
{
name: 'Year',
selector: 'year',
sortable: true,
right: true,
},
];
class SignInForm extends Component {
constructor() {
super();
this.state = {
email: '',
password: '',
offence:[],
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleClick =this.handleClick.bind(this);
this.handleLogin =this.handleLogin.bind(this);
}
handleClick(){
const url ="https://xxxxxxxxxx.sh/offences";
fetch(url)
.then(response => {
console.log(response.clone().json())
console.log(response.headers.get('Content-Type'))
if (response.ok) {
return response.clone().json();
} else {
throw new Error('Something went wrong ...');
}
})
.then((res) =>{
console.log(res)
this.setState({
offence: [res]
});
}) // get just the list of articles
console.log(this.state.offence);
}
render() {
return (
<button className="FormField__offence" onClick{this.handleClick}>offence</button>
</div>
<div>
<DataTable
title="Offences"
columns={columns}
data={this.state.offence}
/>
</div>
</form>
</div>
);
}
}
export default SignInForm;
I was expecting one column decent table show
const columns = [
{
name: 'Offences',
selector: 'Offences',
sortable: true,
}
];
class SignInForm extends React.Component {
constructor() {
super();
this.state = {
email: '',
password: '',
offence: [],
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const url = "https://xxxxxxxx.sh/offences";
fetch(url)
.then(response => {
if (response.ok) {
return response.clone().json();
} else {
throw new Error('Something went wrong ...');
}
})
.then((res) => {
this.setState({
offence: res.offences.map((item,id) => ({id, Offences: item}))
});
}) // get just the list of articles
}
render() {
console.log(this.state.offence)
return (
<div className="App">
<button
className="FormField__offence"
onClick={this.handleClick}>offence</button>
<DataTable
title="Offences"
columns={columns}
data={this.state.offence}
/>
</div>
);
}
}
Live Link
Replace this,
this.setState({
offence: [res]
});
with this,
this.setState({
offence: res
});
In your case this is res from API call
{offences: Array(88)}
offences: (88) ["Advertising Prostitution", ... "Weapons Act Offences", "Weapons Act Offences - Other"]
__proto__: Object
So you can get offences like this,
this.setState({
offence: res.offences
});

react child component not updating after parent state changed

I am trying to make a react messaging app where the channel page is composed a channel bar Channel.js (Parent Component) with a general and random channel and a message-list ChannelMessage.js (Child Component).
Currently, I can click on the channel bar and it changes the url and this.props.channelName, but the child component displays the same text, regardless of Link clicked. I believe it is because ComponentDidMount does not get called in the child component. How would I go about updating/rerendering the child component to get ComponentDidMount to reload?
Channel.js
export default class Channel extends React.Component {
constructor(props) {
super(props);
this.state = {
channelName: 'general'
};
this.handleSignOut = this.handleSignOut.bind(this);
}
...
render() {
return (
<div className="container">
<div className="row">
<div className="channel-list col-lg-2">
<h2>Channels</h2>
<ul className="list-group">
<li><Link to="/channel/general"
onClick={() => this.setState({ channelName: 'general' })}>General</Link></li>
<li><Link to="/channel/random"
onClick={() => this.setState({ channelName: 'random' })}>Random</Link></li>
</ul>
<div className="footer-segment">
...
</div>
</div>
<ChannelMessages channelName={this.state.channelName} />
</div>
</div>
);
}
ChannelMessages.js
export default class ChannelMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
channelName: this.props.channelName,
message: '',
messages: [],
textValue: ''
}
...
}
componentWillReceiveProps(nextProps) {
this.setState({channelName: nextProps.channelName})
}
componentDidMount() {
this.messageRef = firebase.database().ref('messages/' + this.state.channelName);
this.messageRef.limitToLast(500).on('value', (snapshot) => {
let messages = snapshot.val();
if (messages !== null) {
let newMessages = [];
for (let message in messages) {
newMessages.push({
author: {
displayName: messages[message].author.displayName,
photoURL: messages[message].author.photoURL,
uid: messages[message].author.uid
},
body: messages[message].body,
createdAt: messages[message].createdAt,
id: message
});
}
this.setState({ messages: newMessages, textValue: newMessages.body });
}
console.log(this.state.textValue)
});
}
componentWillUnmount() {
this.messageRef.off('value');
}
handleSubmitMessage(event) {
event.preventDefault();
let user = firebase.auth().currentUser;
this.messageRef.push().set({
author: {
displayName: user.displayName,
photoURL: user.photoURL,
uid: user.uid
},
body: this.state.message,
createdAt: firebase.database.ServerValue.TIMESTAMP
});
this.setState({ message: '' });
}
...
render() {
return (...);
}
}
why are you not using the channelName directly from the props? you are using the componentDidMount event. It runs only once. If you want to run the firebase code whenever a new channel name is passed; extract the fetching logic function and run it in componentDidMount and componentDidUpate(check here if it's different from the previous one)
ComponentDidMount runs only once - more here
ComponentDidUpdate doesn't run on initial trigger - more here
export default class ChannelMessages extends React.Component {
constructor(props) {
super(props);
this.state = {
message: '',
messages: [],
textValue: ''
}
...
}
componentDidMount() {
const {channelName} = this.props;
fetchFromDb(channelName);
}
componentDidUpdate(prevProps) {
if (prevProps.channelName !== this.props.channelName) {
fetchFromDb(this.props.channelName);
}
}
componentWillUnmount() {
this.messageRef.off('value');
}
handleSubmitMessage(event) {
event.preventDefault();
let user = firebase.auth().currentUser;
this.messageRef.push().set({
author: {
displayName: user.displayName,
photoURL: user.photoURL,
uid: user.uid
},
body: this.state.message,
createdAt: firebase.database.ServerValue.TIMESTAMP
});
this.setState({ message: '' });
}
...
render() {
return (...);
}
}

Resources