How to receive props only after state of parent has updated? - reactjs

I'm trying to build a little weather widget, where the geolocation of the user is captured in one component and then passed onto a child component which fetches the weather data (based on the location) and then eventually renders an icon indicating the current weather conditions.
I'm passing the longitude and latitude state as props to my WeatherWidget. Unfortunately, the WeatherWidget also receives the initial state null. How I can I avoid that?
Thank you for your help!
class GetGeolocation extends Component{
constructor(){
super();
this.state = {
lngt: null,
latd: null
}
}
componentDidMount(){
this.getLocation()
}
getLocation = () => {
if(navigator.geolocation){
navigator.geolocation.getCurrentPosition(position => {
this.setState({lngt: position.coords.longitude.toFixed(4)});
this.setState({latd:position.coords.latitude.toFixed(4)});
}
);
};
}
render(){
return (
<>
<WeatherWidget lngt = {this.state.lngt} latd = {this.state.latd} />
</>
)
}
class WeatherWidget extends Component{
constructor(props){
super(props);
this.state = {
weather:[]
}
}
componentWillReceiveProps(nextProps){
this.getWeather(nextProps)
}
getWeather = (location) => {
console.log(location)
// The console logs twice:
// First:
//{lngt: "-12.3456", latd: null}
//Then, the correct values:
//{lngt: "-12.3456", latd: "78,9999"}
}

Don't use componentWillReceiveProps, that will be deprecated in later versions of React.
But also, you can just setup conditional logic in your life-cycle methods to determine what code to execute.
componentWillReceiveProps(nextProps){
//condition says if both value are truthy then run code.
if(nextProps.lngt && nextProps.latd){
this.getWeather(nextProps)
}
}
You can also use componentDidUpdate()
componentDidUpdate(){
//condition says if both value are truthy then run code.
if(this.props.lngt && this.props.latd){
this.getWeather(this.props)
}
}

One option is to conditionally render in the parent component:
class GetGeolocation extends React.Component {
constructor(props) {
super(props);
this.state = {
lngt: null,
latd: null
};
}
componentDidMount() {
this.getLocation();
}
getLocation = () => {
// Simulate the network request
setTimeout(() => this.setState({ lngt: 100 }), 1000);
setTimeout(() => this.setState({ latd: 100 }), 1000);
};
render() {
const { lngt, latd } = this.state;
if (!lngt || !latd) return null;
return <WeatherWidget lngt={lngt} latd={latd} />;
}
}
class WeatherWidget extends React.Component {
constructor(props) {
super(props);
this.state = {
weather: []
};
}
componentDidMount() {
this.getWeather(this.props);
}
getWeather = location => {
console.log(location);
};
render() {
return null;
}
}

Related

How do you call a function from another component when navigating in react native?

What I want to do
I want to use function Refresh in First, when navigating from Third to First.
Class First
export default class First extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
checkedItems: [],
};
}
Refresh = async () => {
const querySnapshot = await DB.getAllItems();
const items = [];
querySnapshot.forEach(doc => {
items.push(doc.data());
});
this.setState({
items,
checkedItems: [],
});
}
}
It navigates to Second from First.
Class Second
export default class Second extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalVisible: false,
};
}
toggleModal = () => {
this.setState({ isModalVisible: !this.state.isModalVisible });
}
render() {
return (
<Modal isVisible={this.state.isModalVisible}>
<Third nav={this} />
</Modal>
)
}
}
Second has a child component, Third which is a modal.
Class Third
export default class Third extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
navigateFirst = () => {
this.props.nav.toggleModal();
this.props.nav.props.navigation.navigate('First');
}
render() {
return (
<View>
<TouchableOpacity
onPress={this.navigateFirst}
>
<Text>Back To First</Text>
</TouchableOpacity>
</View>
);
}
}
In Third, it navigates back to First closing modal.
Problem
I don't want to use componentDidUpdate because First has checkbox.
If I use componentDidUpdate, every time I press checkbox, it triggers Refresh.
That is what I avoid.
I would appreciate it if you could give me any advice.
Is it easier if you call the Refresh function everytime load First.js. Then when you return to First from Third, it auto trigger it.
// First.js
componentDidMount() {
this.Refresh();
this.props.navigation.addListener('willFocus', this.Refresh)
}
You can achieve the desired behavior easily with componentDidUpdate without calling refresh on every props and state changes.
Third
export default class Third extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
navigateFirst = () => {
this.props.nav.toggleModal();
// this.props.nav.props.navigation.navigate('First');
//instead of just .navigate('First') you can pass param
this.props.nav.props.navigation.navigate('First',{isFromThird:true}) //like this
}
render() {
return (
<View>
<TouchableOpacity
onPress={this.navigateFirst}
>
<Text>Back To First</Text>
</TouchableOpacity>
</View>
);
}
}
In First.js just check for the param isFromThird
componentDidUpdate(prevProps,prevState){
// React Navigation < 5
let isFromThird = this.props.navigation.getParam("isFromThird",false)
//React Navigation >= 5
let isFromThird = this.props.route.params?.isFromThird ?? false;
if(isFromThird){
this.Refresh()
}

Is there any signal that parent component's state set and then child component's state set too?

when parent component set state(itemSelected: item) i want child component set state(isShowForm: true) too, so is there any signal or condition let me do that thing?
<pre>
//this is child Component
class HeaderComponent extends Component {
constructor(props) {
super(props);
this.state = {
isShowForm: false,
};
}
handleEdit = () =>{
if(any signal?){
this.setState({isShowForm:true})
}
}
export default HeaderComponent;
//this is parent Component
class Task extends Component {
constructor(props){
super(props);
this.state = {
itemSelected: null,
}
}
handleEdit = (item) => {
this.setState({itemSelected: item})
}
render() {
let {itemSelected} = this.state;
return(
HeaderComponent itemSelected={itemSelected}/>
)
</pre>
You can pass the required state from parent component to child and use componentDidUpdate lifecycle in child to listen to the prop and react accordingly.
class HeaderComponent extends Component {
constructor(props) {
super(props);
this.state = {
isShowForm: false,
};
}
componentDidUpdate(prevProps) =>{
// Any identifying property to see if the itemSelected object has indeed changed. I'm just assuming that it has a unique ID
if(prevProps.itemSelected && prevProps.itemSelected.id !== this.props.itemSelected.id) {
this.setState({ isShowForm: true })
}
}
export default HeaderComponent;
//this is parent Component
class Task extends Component {
constructor(props){
super(props);
this.state = {
itemSelected: null,
}
}
handleEdit = (item) => {
this.setState({itemSelected: item})
}
render() {
let {itemSelected} = this.state;
return(
<HeaderComponent itemSelected={itemSelected}/>
)

React: fire render after promise completed

Please note, that I a fetching data from AWS DynamoDB.
...
class Test extends Component {
constructor(props) {
super(props);
this.state = {
contactList: []
}
}
componentDidMount() {
var getItemsPromise = db.scan({ TableName: "tester" }).promise();
getItemsPromise.then((data) => this.setState({ contactList: data.Items }));
}
render() {
return (
<div>{this.state.contactList[0].link.S}</div>
);
}
}
export default Test;
I am trying to render the returned value, but can't. If I set
render() {
console.log(this.state.contactList[0].link.S);
return (
<div>test</div>
);
}
it works. Why is that? Why is it not working when I set it straight inline?
this.state.contactList[0] is undefined before the promise is resolved, so this.state.contactList[0].link will give rise to an error.
You could e.g. return null from the render method until the array has been filled with your objects:
class Test extends Component {
// ...
render() {
if (this.state.contactList.length === 0) {
return null;
}
return <div>{this.state.contactList[0].link.S}</div>;
}
}

Can't use state.data.parameters in render when setstate({data: this.props.somefunction()}) componentDidUpdate

Please HELP!
I fill data in componentdidupdate
componentDidUpdate(prevProps) {
if(isEmpty(this.props.tofiConstants)) return;
const { doUsers, dpUsers } = this.props.tofiConstants;
if (prevProps.cubeUsers !== this.props.cubeUsers) {
this.setState({
data: somefunc(doing here something)
});
}
console.log(this.state.data);
}
and then i want use the state in render
render() {
return (
<div className="profileScreen">{this.state.fullname}</div>
);
}
constructor is here
constructor(props) {
super (props);
this.state = {
data: []
};
}

setState inside constructor not working properly: ReactJS

I'm trying to run the below code in React+Redux but am running into an unhandled
exception 'NodeInvocationException: Cannot read property 'showText' of
null TypeError: Cannot read property 'showText' of null'
import * as React from 'react';
import { NavMenu } from './NavMenu';
import { Component } from 'react';
export interface BlinkState
{
showText: boolean;
text: '';
}
type BlinkProps = BlinkState;
class Blink extends React.Component<BlinkProps, BlinkState> {
constructor(props: BlinkProps) {
super(props);
//this.state = { showText: true };
this.setState({ showText: true, text: props.text });
// Toggle the state every second
setInterval(() => {
this.setState(previousState => {
return { showText: !previousState.showText };
});
}, 1000);
}
render() {
let display = this.state.showText ? this.props.text : ' ';
return <div>{ display }</div>;
}
}
export class Layout extends React.Component<{}, {}> {
public render() {
return <div className='container-fluid'>
<Blink showText=false text='I love to blink' />
</div>;
}
}
I'm just trying to figure out how to render the Blink copmonent with the props passed in...
You missed the basic thing, use of constructor and setState, use of constructor is to initialize the state value and use of setState is to update the state value, so using setState inside `constructor doesn't makes any sense.
Better way will be, initialise the state in constructor and to run the time use componentDidMount lifecycle method, also don't forgot to stop the time before unmounting the component, to clear it use componentWillUnmount lifecycle method.
Write the component like this:
class Blink extends React.Component<BlinkProps, BlinkState> {
constructor(props: BlinkProps) {
super(props);
this.state = { showText: false };
}
componentDidMount(){
this.timer = setInterval(() => {
this.setState(previousState => {
return { showText: !previousState.showText };
});
}, 1000);
}
componentWillUnmount(){
clearInterval(this.timer)
}
render() {
let display = this.state.showText ? this.props.text : ' ';
return <div>{ display }</div>;
}
}
Working code:
class Blink extends React.Component {
constructor(props) {
super(props);
this.state = { showText: true, text: props.text };
}
componentDidMount(){
this.timer = setInterval(() => {
this.setState(prev => {
return { showText: !prev.showText };
});
}, 1000);
}
componentWillUnmount(){
clearTimer(this.timer)
}
render() {
let display = this.state.showText ? this.props.text : ' ';
return <div>Hello { display }</div>;
}
}
class Layout extends React.Component{
render() {
return <div className='container-fluid'>
<Blink text='I love to blink' />
</div>;
}
}
ReactDOM.render(<Layout/>, document.getElementById('app'))
<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='app'/>
You should not specify actions to be taken in the constructor or use setState there, constructor should be used to simply set an initial state.
Also you might need to update the state text since its being set based on props. Do it in the componentWillReceiveProps.
Also when you are using setInterval, make sure to clearInterval when the componentUnmounts
constructor(props: BlinkProps) {
super(props);
this.state = { showText: true, text: props.text };
}
componentWillReceiveProps(nextProps) {
this.setState({text: nextProps.text});
}
componentDidMount() {
// Toggle the state every second
this.interval = setInterval(() => {
this.setState(previousState => {
return { showText: !previousState.showText };
});
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval)
}

Resources