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;
}
}
Hi I am trying to change the state of a component during the render. The state should change the classname depending on the list passed to it as props. I have tried but it does not seem to work. I can pass props but not change the state.
class Square extends React.Component {
constructor(props) {
super(props);
this.state = {alive: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
alive: !state.alive
}));
};
render() {
return <div className = { this.state.alive ? "square--green" : "square--grey" } onClick = { this.handleClick } />;
};
}
function SquareList(props) {
const oxGrid = props.oxGrid;
const listItems = [];
oxGrid.forEach((item, i) => {
if(item === 'O'){
listItems.push(<Square key= {i}/>)
}
else {
listItems.push(<Square key = {i} />)
}
});
return listItems;
};
let printer = (function () {
let print = function (oXGrid) {
return ReactDOM.render(<SquareList oxGrid ={oXGrid} />, grid);
};
return { print: print };
})();
I have made the following changes in Square and SquareList Component. You need to pass a prop item to the Square Component.
class Square extends React.PureComponent {
constructor(props) {
super(props);
const isALive = this.props.item === 'O';
this.state = {
alive: isALive
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
alive: !state.alive
}));
};
componentWillReceiveProps(nextProps) {
if(this.props.item !== nextProps.item) {
this.setState({
alive: nextProps.item === '0'
});
}
}
render() {
return <div className = { this.state.alive ? "square--green" : "square--grey" } onClick = { this.handleClick } />;
};
}
function SquareList(props) {
const oxGrid = props.oxGrid;
const listItems = oxGrid.map((item, i) => {
return(
<Square item={item} key= {i}/>
)
});
return listItems;
};
let printer = (function () {
let print = function (oXGrid) {
return ReactDOM.render(<SquareList oxGrid ={oXGrid} />, grid);
};
return { print: print };
})();
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: []
};
}
I am intending to get snapshot val from Firebase within my React component. I want to get the values based on init of the component and attach a listener for changes.
class ChatMessages extends Component {
constructor(props) {
super(props);
this.state = {
messages: [],
};
this.getMessages = this.getMessages.bind(this);
}
getMessages(event) {
const messagesRef = firebase.database().ref('messages');
messagesRef.on('value', function(snapshot) {
this.setState({ messages: snapshot.val() });
});
}
componentDidMount() {
this.getMessages();
}
render() {
return (
<div className="container">
<ul>
<li>Default Chat Message</li>
{ this.state.messages }
</ul>
</div>
);
}
}
This is because 'this' is losing its context. So that, 'this.setState' is being undefined. You can have a reference for the actual 'this' via a variable called 'that'.
class ChatMessages extends Component {
constructor(props) {
super(props);
this.state = {
messages: [],
};
this.getMessages = this.getMessages.bind(this);
}
getMessages(event) {
const messagesRef = firebase.database().ref('messages');
let that = this
messagesRef.on('value', function(snapshot) {
// here
that.setState({ messages: snapshot.val() });
});
}
componentDidMount() {
this.getMessages();
}
render() {
return (
<div className="container">
<ul>
<li>Default Chat Message</li>
{ this.state.messages }
</ul>
</div>
);
}
}
Or if possible, you can use arrow function, which keeps its context.
getMessages(event) {
const messagesRef = firebase.database().ref('messages');
// here
messagesRef.on('value', snapshot => {
// here
that.setState({ messages: snapshot.val() });
});
}
I have the following:
import React from 'react';
import axios from 'axios';
class FirstName extends React.Component {
constructor(props) {
super(props);
this.state = {
submitted: false
};
}
getName () {
var name = this.refs.firstName.value;
this.setState(function() {
this.props.action(name);
});
}
handleSubmit (e) {
e.preventDefault();
this.setState({ submitted: true }, function() {
this.props.actionID(2);
this.props.activeNav('color');
});
}
render () {
return (
<div>
<h2>tell us your first name</h2>
<form>
<input
type="text"
ref="firstName"
onChange={this.getName.bind(this)}
/>
<div className="buttons-wrapper">
<button href="#">back</button>
<button onClick={this.handleSubmit.bind(this)}>continue</button>
</div>
</form>
</div>
);
}
};
class PickColor extends React.Component {
backToPrevious (e) {
e.preventDefault();
this.props.actionID(1);
this.props.activeNav('name');
}
goToNext (e) {
e.preventDefault();
this.props.actionID(3);
this.props.activeNav('design');
this.props.displayIconsHolder(true);
}
getColorValue(event) {
this.props.color(event.target.getAttribute("data-color"));
}
render () {
var colors = ['red', 'purple', 'yellow', 'green', 'blue'],
colorsLink = [];
colors.forEach(el => {
colorsLink.push(<li
data-color={el}
key={el}
onClick={this.getColorValue.bind(this)}
ref={el}>
{el}
</li>
);
});
return (
<section>
<ul>
{colorsLink}
</ul>
<button onClick={this.backToPrevious.bind(this)}>back</button>
<button onClick={this.goToNext.bind(this)}>continue</button>
</section>
);
}
}
class ConfirmSingleIcon extends React.Component {
goBack () {
this.props.goBack();
}
confirmCaptionandIcon (event) {
var optionID = event.target.getAttribute("data-option-id"),
name = event.target.getAttribute("data-option-name");
this.props.setOptionID(optionID);
this.props.setIcon(1, name, optionID, false);
}
goNext () {
this.props.goNext();
}
render () {
console.log(this.props.currentState);
var options = [],
that = this;
this.props.iconOptionsList.forEach(function(el){
options.push(<li onClick={that.confirmCaptionandIcon.bind(that)} key={el.option} data-option-name={el.option} data-option-id={el.id}>{el.option}</li>);
});
return (
<div>
<h2>Choose your caption</h2>
<h3>
{this.props.selectedIcon}
</h3>
<ul>
{options}
</ul>
<button onClick={this.goBack.bind(this)} >back</button>
<button onClick={this.goNext.bind(this)} >confirm</button>
</div>
);
}
}
class ConfirmCaption extends React.Component {
handleClick () {
var currentState = this.props.currentState;
this.props.setIcon(currentState.icon_ID, currentState.selectedIcon, currentState.option_ID, true);
this.props.setIconVisiblity(true);
this.props.setIconListVisiblity(false);
}
render () {
console.log(this.props.currentState);
return (
<div>
<p onClick={this.handleClick.bind(this)}>confirm icon and caption</p>
</div>
);
}
}
class ChooseIcon extends React.Component {
constructor(props) {
super(props);
this.state = {
icons: [],
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
setOptionID (id) {
this.setState({ option_ID: id })
}
setIconVisiblity (onOff) {
this.setState({ confirmIcon: onOff })
}
setIconListVisiblity (onOff) {
this.setState({ iconList: onOff })
}
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
handleClick (event) {
var iconId = event.target.getAttribute("data-icon-id"),
that = this;
this.state.icons.forEach(function(el){
if(el.id == iconId){
that.setState(
{
confirmIcon: true,
iconList: false,
selectedIcon: el.name,
icon_ID: iconId,
selectedIconOptions: el.option
}
);
}
});
}
goBack () {
this.setState(
{
confirmIcon: false,
iconList: true
}
);
}
goNext () {
this.setState(
{
confirmIcon: false,
iconList: false,
confirmCaption: true
}
);
}
render () {
var icons = [];
this.state.icons.forEach(el => {
icons.push(<li data-icon-id={el.id} onClick={this.handleClick.bind(this)} key={el.name}>{el.name}</li>);
});
return (
<div>
{this.state.iconList ? <IconList icons={icons} /> : ''}
{this.state.confirmIcon ? <ConfirmSingleIcon goBack={this.goBack.bind(this)}
goNext={this.goNext.bind(this)}
setIcon={this.props.setIcon}
selectedIcon={this.state.selectedIcon}
iconOptionsList ={this.state.selectedIconOptions}
setOptionID={this.setOptionID}
currentState={this.state} /> : ''}
{this.state.confirmCaption ? <ConfirmCaption currentState={this.state}
setIcon={this.props.setIcon}
setIconVisiblity={this.setIconVisiblity}
setIconListVisiblity={this.setIconListVisiblity} /> : ''}
</div>
);
}
}
class IconList extends React.Component {
render () {
return (
<div>
<h2>Pick your icon</h2>
<ul>
{this.props.icons}
</ul>
</div>
);
}
}
class Forms extends React.Component {
render () {
var form;
switch(this.props.formID) {
case 1:
form = <FirstName action={this.props.action} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 2:
form = <PickColor displayIconsHolder={this.props.seticonsHolder} color={this.props.colorVal} actionID={this.props.switchComponent} activeNav={this.props.activeNav} />
break;
case 3:
form = <ChooseIcon setIcon={this.props.setOptionA} />
break;
}
return (
<section>
{form}
</section>
);
}
}
export default Forms;
"ChooseIcon" is a component that will get used 3 times therefore everytime I get to it I need to bring its state back as if it was the first time.
Ideally I would need to make this ajax call everytime:
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
is there a way to manually call componentDidMount perhaps from a parent component?
React handles component lifecycle through key attribute. For example:
<ChooseIcon key={this.props.formID} setIcon={this.props.setOptionA} />
So every time your key (it can be anything you like, but unique) is changed component will unmount and mount again, with this you can easily control componentDidMount callback.
If you are using the ChooseIcon component 3 times inside the same parent component, I would suggest you to do the ajax in componentDidMount of the parent component like this (exaclty how you have in your example, in terms of code)
componentDidMount() {
var url = `http://local.tshirt.net/get-options`;
axios.get(url)
.then(res => {
this.setState({ icons:res.data.icons });
});
}
and then pass this data down to the ChooseIcon component
render() {
return (
//do your stuff
<ChooseIcon icons={this.state.icons}/>
)
}
after this you will only need to set the received props in your ChooseIconcomponent, for that you only need to change one line in it's constructor:
constructor(props) {
super(props);
this.state = {
icons: props.icons, // Changed here!
iconList: true,
confirmIcon: false,
confirmCaption: false,
selectedIconOptions: '',
icon_ID: '',
option_ID: '',
selectedIcon: ''
};
this.setOptionID = this.setOptionID.bind(this);
this.setIconVisiblity = this.setIconVisiblity.bind(this);
this.setIconListVisiblity = this.setIconListVisiblity.bind(this);
}
The parent component can use a ref to call the function directly.
However, trying to force this function feels like a smell. Perhaps lifting the state higher up the component tree would solve this problem. This way, the parent component will tell ChooseIcon what to show, and there will not be a need to call componentDidMount again. Also, I assume the Ajax call can also occur once.