I am trying to make a React component that displays multiple renders based on props and state. So, while I wait for the promise to be resolved, I want to display a spinner Component
Main Renders:
NoResource Component => When the user is not valid
Spinner Component => When is loading on all renders
BasicRender Component => When data are fetched and is not loading
Below is my component:
/* eslint-disable react/prefer-stateless-function */
import React, { Component, Fragment } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { getUser, listUsers } from '../../config/service';
export class UserDetailsScreen extends Component {
static propTypes = {
match: PropTypes.shape({
isExact: PropTypes.bool,
params: PropTypes.object,
path: PropTypes.string,
url: PropTypes.string
}),
// eslint-disable-next-line react/forbid-prop-types
history: PropTypes.object,
label: PropTypes.string,
actualValue: PropTypes.string,
callBack: PropTypes.func
};
state = {
user: {},
error: '',
isloading: false
};
componentDidMount() {
this.fetchUser();
this.setState({ isLoading: true})
}
getUserUsername = () => {
const { match } = this.props;
const { params } = match;
return params.username;
};
fetchUser = () => {
getUser(this.getUserUsername())
.then(username => {
this.setState({
user: username.data,
isloading: false
});
})
.catch(({ message = 'Could not retrieve data from server.' }) => {
this.setState({
user: null,
error: message,
isLoading: false
});
});
};
validateUsername = () =>
listUsers().then(({ data }) => {
const { match } = this.props;
if (data.includes(match.params.username)) {
return true;
}
return false;
});
// eslint-disable-next-line no-restricted-globals
redirectToUsers = async () => {
const { history } = this.props;
await history.push('/management/users');
};
renderUserDetails() {
const { user, error } = this.state;
const { callBack, actualValue, label, match } = this.props;
return (
<div className="lenses-container-fluid container-fluid">
<div className="row">
.. More Content ..
{user && <HeaderMenuButton data-test="header-menu-button" />}
</div>
{user && this.validateUsername() ? (
<Fragment>
.. Content ..
</Fragment>
) : (
<div className="container-fluid">
{this.renderNoResourceComponent()}
</div>
)}
<ToolTip id="loggedIn" place="right">
{user.loggedIn ? <span>Online</span> : <span>Oflline</span>}
</ToolTip>
</div>
);
}
renderNoResourceComponent = () => {
const { match } = this.props;
return (
<div className="center-block">
<NoResource
icon="exclamation-triangle"
title="Ooops.."
primaryBtn="« Back to Users"
primaryCallback={this.redirectToUsers}
>
<h5>404: USER NOT FOUND</h5>
<p>
Sorry, but the User with username:
<strong>{match.params.username}</strong> does not exists
</p>
</NoResource>
</div>
);
};
renderSpinner = () => {
const { isLoading, error } = this.state;
if (isLoading && error === null) {
return <ContentSpinner />;
}
return null;
};
render() {
return (
<div className="container-fluid mt-2">
{this.renderSpinner()}
{this.renderUserDetails()}
</div>
);
}
}
export default withRouter(UserDetailsScreen);
The problem is:
I get the spinner along with the main component, and I am getting this error:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.. Can you please tell me what I am doing wrong.
The error is because you are running the renderUserDetailsComponent even when your API call is in loading state. You must only render the spinner on loading state
renderUserDetails() {
const { user, error, isLoading } = this.state;
if(isLoading) {
return null;
}
const { callBack, actualValue, label, match } = this.props;
return (
<div className="lenses-container-fluid container-fluid">
<div className="row">
.. More Content ..
{user && <HeaderMenuButton data-test="header-menu-button" />}
</div>
{user && this.validateUsername() ? (
<Fragment>
.. Content ..
</Fragment>
) : (
<div className="container-fluid">
{this.renderNoResourceComponent()}
</div>
)}
<ToolTip id="loggedIn" place="right">
{user.loggedIn ? <span>Online</span> : <span>Oflline</span>}
</ToolTip>
</div>
);
}
Related
after onclick event occurs in backpackList.js, fetch data in context.js and then through setState I want to update noneUserCart . After that i want to get data from context.js to backpackList.js to show web page. but the data is inital data []. How can I solve this problem?!
I think this is a Asynchronous problem, but I'm new react, so I don't know how to write code for this. or do I use async, await.
Help me please!
import React, { Component } from 'react';
const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;
class ProductProvider extends Component {
constructor() {
super();
this.state = {
totalProducts: 0,
isLogin: false,
cartList: [],
isNavOpen: false,
isCartOpen: false,
noneUserCart: [],
};
}
noneUserAddCart = bagId => {
fetch('/data/getdata.json', {
method: 'GET',
})
.then(res => res.json())
.catch(err => console.log(err))
.then(data => {
this.setState(
{
noneUserCart: [...this.state.noneUserCart, data],
},
() => console.log(this.state.noneUserCart)
);
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
handleCart: this.handleCart,
getToken: this.getToken,
addNoneUserCart: this.addNoneUserCart,
hanldeCheckout: this.hanldeCheckout,
openNav: this.openNav,
showCart: this.showCart,
habdleCartLsit: this.habdleCartLsit,
deleteCart: this.deleteCart,
noneUserAddCart: this.noneUserAddCart,
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
export { ProductProvider, ProductConsumer };
import React, { Component } from 'react';
import { ProductConsumer } from '../../context';
export default class BackpackList extends Component {
render() {
const {
backpackdata,
backdescdata,
isdescOpen,
showDesc,
descClose,
rangenumone,
rangenumtwo,
} = this.props;
return (
<div>
{backdescdata.map((bag, inx) => {
return (
<>
{isdescOpen && bag.id > rangenumone && bag.id < rangenumtwo && (
<div className="listDescContainer" key={inx}>
<div className="listDescBox">
<ProductConsumer>
{value => (
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
</div>
)}
</ProductConsumer>
<span className="descClosebtn" onClick={descClose}>
X
</span>
</div>
</div>
</div>
)}
</>
);
})}
</div>
);
}
}
fetch is asynchronous, this.setState is yet called when console.log
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
{value.noneUserCart}
{/* when finished, result should show here */}
</div>
I have the following code that passing leadsBuilder props to lead in the LeadBuilderSingle componenet. It has an array in a object and I access that array and try to map over it but it returns undefined. The data is being waited on and I am using isLoading, so I am not sure what is causing this error. It loads on first loading, but on page refresh gives me undefined.
import React, { useState, useEffect } from "react";
import Dasboard from "./Dashboard";
import { Container } from "../styles/Main";
import { LeadsBuilderCollection } from "../../api/LeadsCollection";
import { LeadBuilderSingle } from "../leads/LeadBuilderSingle";
import { useTracker } from "meteor/react-meteor-data";
const LeadCategoriesAdd = ({ params }) => {
const { leadsBuilder, isLoading } = useTracker(() => {
const noDataAvailable = { leadsBuilder: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leadsBuilder");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leadsBuilder = LeadsBuilderCollection.findOne({ _id: params._id });
return { leadsBuilder };
});
return (
<Container>
<Dasboard />
<main className="">
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<LeadBuilderSingle key={params._id} lead={leadsBuilder} />
</>
)}
</main>
</Container>
);
};
export default LeadCategoriesAdd;
import React from "react";
export const LeadBuilderSingle = ({ lead, onDeleteClick }) => {
console.log(lead);
return (
<>
<li>{lead.type}</li>
{lead.inputs.map((input, i) => {
return <p key={i}>{input.inputType}</p>;
})}
</>
);
};
FlowRouter.route("/leadCategories/:_id", {
name: "leadeBuilder",
action(params) {
mount(App, {
content: <LeadCategoriesAdd params={params} />,
});
},
});
try this :
lead.inputs && lead.inputs.map ((input, i) => {...}
I have 2 components OptinPage (parent) and TermsOfServices (child). Optin Page is only used for rendering the TermsOfServices component, which can be reused elsewhere in the application. I want to use my onSucceeded () function from my child component to my parent component. I don't see how to do it at all. Currently the result is such that when I click on the button that validates the TermsOfServices it seems to be an infinite loop, it goes on and on without closing my popup. Before I split my TermsOfServices component into a reusable component it worked fine. Before, all content was gathered in OptinPage. Any ideas? Thanks in advance
my TermsOfServices component:
import API from 'api';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
Block,
BlockTitle,
Col,
Fab,
Icon,
Preloader,
} from 'framework7-react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-refetch';
import ReactHtmlParser from 'react-html-parser';
class TermsOfServices extends PureComponent {
static propTypes = {
agreeTosFunc: PropTypes.func.isRequired,
agreeTos: PropTypes.object,
onSucceeded: PropTypes.func,
tos: PropTypes.object.isRequired,
};
static contextTypes = {
apiURL: PropTypes.string,
loginToken: PropTypes.string,
userId: PropTypes.string,
};
static defaultProps = {
agreeTos: {},
onSucceeded: () => {},
};
state = {
currentTos: -1,
};
componentDidUpdate(prevProps) {
const {
agreeTos,
onSucceeded,
tos,
} = this.props;
const { currentTos } = this.state;
/* Prepare for first tos after receiving all of them */
if (
prevProps.tos.pending &&
tos.fulfilled &&
tos.value.length &&
currentTos < 0
) {
this.setState({ currentTos: 0 });
}
/* When sending ToS agreement is done */
if (
prevProps.agreeTos.pending &&
agreeTos.fulfilled
) {
onSucceeded();
}
}
handleNext = () => {
const { agreeTosFunc, tos } = this.props;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const done = currentTosId + 1 === termsOfServices.length;
this.setState({ currentTos: currentTosId + 1 });
if (done) {
agreeTosFunc(termsOfServices.map((v) => v._id));
}
};
render() {
const { tos } = this.props;
const { currentTos: currentTosId } = this.state;
const termsOfServices = tos.value;
const currentTermsOfServices = termsOfServices && termsOfServices[currentTosId];
const loaded = termsOfServices && !tos.pending && tos.fulfilled;
const htmlTransformCallback = (node) => {
if (node.type === 'tag' && node.name === 'a') {
// eslint-disable-next-line no-param-reassign
node.attribs.class = 'external';
}
return undefined;
};
return (
<div>
{ (!loaded || !currentTermsOfServices) && (
<div id="
optin_page_content" className="text-align-center">
<Block className="row align-items-stretch text-align-center">
<Col><Preloader size={50} /></Col>
</Block>
</div>
)}
{ loaded && currentTermsOfServices && (
<div id="optin_page_content" className="text-align-center">
<h1>
<FormattedMessage id="press_yui_tos_subtitle" values={{ from: currentTosId + 1, to: termsOfServices.length }} />
</h1>
<BlockTitle>
{ReactHtmlParser(
currentTermsOfServices.title,
{ transform: htmlTransformCallback },
)}
</BlockTitle>
<Block strong inset>
<div className="tos_content">
{ReactHtmlParser(
currentTermsOfServices.html,
{ transform: htmlTransformCallback },
)}
</div>
</Block>
<Fab position="right-bottom" slot="fixed" color="pink" onClick={() => this.handleNext()}>
{currentTosId + 1 === termsOfServices.length &&
<Icon ios="f7:check" aurora="f7:check" md="material:check" />}
{currentTosId !== termsOfServices.length &&
<Icon ios="f7:chevron_right" aurora="f7:chevron_right" md="material:chevron_right" />}
</Fab>
{currentTosId > 0 && (
<Fab position="left-bottom" slot="fixed" color="pink" onClick={() => this.setState({ currentTos: currentTosId - 1 })}>
<Icon ios="f7:chevron_left" aurora="f7:chevron_left" md="material:chevron_left" />
</Fab>
)}
</div>
)}
</div>
);
}
}
export default connect.defaults(new API())((props, context) => {
const { apiURL, userId } = context;
return {
tos: {
url: new URL(`${apiURL}/tos?outdated=false&required=true`),
},
agreeTosFunc: (tos) => ({
agreeTos: {
body: JSON.stringify({ optIn: tos }),
context,
force: true,
method: 'PUT',
url: new URL(`${apiURL}/users/${userId}/optin`),
},
}),
};
})(TermsOfServices);
My OptIn Page :
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import {
Link,
NavRight,
Navbar,
Page,
Popup,
} from 'framework7-react';
import { FormattedMessage, intlShape } from 'react-intl';
import './OptInPage.scss';
import TermsOfServices from '../components/TermsOfServices';
class OptinPage extends PureComponent {
static propTypes = {
logout: PropTypes.func.isRequired,
opened: PropTypes.bool.isRequired,
};
static contextTypes = {
intl: intlShape,
logout: PropTypes.func,
};
render() {
const { opened, logout } = this.props;
const { intl } = this.context;
const { formatMessage } = intl;
return (
<Popup opened={opened} className="demo-popup-swipe" tabletFullscreen>
<Page id="optin_page">
<Navbar title={formatMessage({ id: 'press_yui_tos_title' })}>
<NavRight>
<Link onClick={() => logout()}>
<FormattedMessage id="press_yui_comments_popup_edit_close" />
</Link>
</NavRight>
</Navbar>
</Page>
<TermsOfServices onSucceeded={this.onSuceeded} />
</Popup>
);
}
}
export default OptinPage;
Just add the data you want the parent to be supplied with in the child component (when it is hit) and then handle the data passed to the parent in the function that you pass in onSuccess.
This will roughly look like this:
const {useState, useEffect} = React;
function App(){
return <Child onSuccess={(data)=>{console.log(data)}}/>;
}
function Child({onSuccess}){
return <div>
<button
onClick={()=>onSuccess("this is the data from the child component")}>
Click to pass data to parent
</button>
</div>;
}
ReactDOM.render(<App/>,document.getElementById('app'));
#element {
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id='app'></div>
<div id="element">
<div>node 1</div>
<div>node 2</div>
</div>
to access to parent method or attribute you should use super,
for call to the parent constructor
super([arguments]);
for call parent method
super.parentMethod(arguments);
I recommend create a method on child class and then call the parent method, not directly
for more information take a look on this
https://www.w3schools.com/jsref/jsref_class_super.asp
I am using Next.js and React and struck on this problem for days. So in Next.js pages i have dynamic page [postid].js, as follow,
import Layout from "../../components/layout";
import { useRouter } from "next/router";
import Singlepost from "../../components/post/singlepost";
export default function Post() {
const router = useRouter();
const postId = router.query.postid
return (
<Layout>
{console.log(router.query.postid)}
<h1>{router.query.postid}</h1>
<p>This is the post content.</p>
<Singlepost postId={postId}/>
</Layout>
);
}
Here I am sending query params as props(postId) to singlepost component.
But in the Singlepost component i am trying use this props inside componentDidMount which then makes api call to get data, but postId shows undefined so api call fails to fetch data.
componentDidMount() {
const postId = this.props.postId;
console.log("postId:", postId);
singlePost(postId).then(data => {
if (data.error) {
console.log(data.error);
} else {
this.setState({
post: data,
likes: data.likes.length,
like: this.checkLike(data.likes),
comments: data.comments
});
}
});
}
So how do i get the prop value in componentDidMount? Or is there any other way should i approach this problem?
also here's my complete singlepost.js for Reference,
import React, { Component } from "react";
import {
singlePost,
remove,
like,
unlike
} from "../../components/post/apiPost";
import Link from "next/link";
import { isAuthenticated } from "../../components/auth";
import DefaultPost from "../../public/images/courses.png";
import Router, { withRouter } from "next/router";
class SinglePost extends Component {
constructor(props) {
super(props);
this.state = {
post: "",
redirectToHome: false,
redirectToBack: false,
redirectToSignin: false,
like: false,
likes: 0,
comments: [],
};
}
checkLike = likes => {
const userId = isAuthenticated() && isAuthenticated().user._id;
let match = likes.indexOf(userId) !== -1;
return match;
};
componentDidMount() {
const postId = this.props.postId;
console.log("postId:", postId);
singlePost(postId).then(data => {
if (data.error) {
console.log(data.error);
} else {
this.setState({
post: data,
likes: data.likes.length,
like: this.checkLike(data.likes),
comments: data.comments
});
}
});
}
updateComments = comments => {
this.setState({ comments });
};
likeToggle = () => {
if (!isAuthenticated()) {
this.setState({ redirectToSignin: true });
return false;
}
let callApi = this.state.like ? unlike : like;
const userId = isAuthenticated().user._id;
const postId = this.state.post._id;
const token = isAuthenticated().token;
callApi(userId, token, postId).then(data => {
if (data.error) {
console.log(data.error);
} else {
this.setState({
like: !this.state.like,
likes: data.likes.length
});
}
});
};
deletePost = () => {
const postId = this.props.quota;
const token = isAuthenticated().token;
remove(postId, token).then(data => {
if (data.error) {
console.log(data.error);
} else {
this.setState({ redirectToBack: true });
}
});
};
deleteConfirmed = () => {
let answer = window.confirm("Are you sure you want to delete your post?");
if (answer) {
this.deletePost();
}
};
renderPost = post => {
console.log(post);
const posterId = post.postedBy ? `/user/${post.postedBy._id}` : "";
const posterName = post.postedBy ? post.postedBy.name : " Unknown";
const { like, likes, comments } = this.state;
return (
<div className="column">
<img
src={`${process.env.REACT_APP_API_URL}/post/photo/${post._id}`}
alt={post.title}
onError={i => (i.target.src = `${DefaultPost}`)}
className="img-thunbnail"
style={{
height: "300px",
width: "100%",
objectFit: "cover"
}}
/>
<button onClick={this.likeToggle}>
<i className="far fa-thumbs-up text-success bg-dark" />
{likes} Like
</button>{" "}
<span className="button is-primary" onClick={() => Router.back()}>
<strong> Back to posts </strong>
</span>
{isAuthenticated().user &&
isAuthenticated().user._id === post.postedBy._id && (
<>
<span className="button is-warning">
<Link href={`/post/edit/${post._id}`}>
<strong> Update Post </strong>
</Link>
</span>
<button
onClick={this.deleteConfirmed}
className="button is-danger"
>
Delete Post
</button>
</>
)}
<div>
{isAuthenticated().user && isAuthenticated().user.role === "admin" && (
<div class="column">
<div className="columns">
<h5 className="column">Admin</h5>
<p>Edit/Delete as an Admin</p>
<span className="button is-warning">
<Link href={`/post/edit/${post._id}`}>
<a> Update Post </a>
</Link>
</span>
<button
onClick={this.deleteConfirmed}
className="button is-raised is-danger"
>
Delete Post
</button>
</div>
</div>
)}
</div>
<div>
<h4 className="raw"> Description: </h4>
<p className="column">{post.body}</p>
</div>
<br />
</div>
);
};
render() {
console.log("Render Quota:", this.props.postId, this.state.ispost);
const { postId } = this.props;
const {
post,
redirectToHome,
redirectToSignin,
redirectToBack
} = this.state;
if (redirectToHome) {
Router.push("/");
} else if (redirectToSignin) {
Router.push("/signin");
} else if (redirectToBack) {
Router.back();
}
return (
<section className="section">
<div className="container">
<h2 className="title">{post.title}</h2>
{!post ? (
<div className="hero">
<h2>Loading...</h2>
</div>
) : (
this.renderPost(post)
)}
</div>
</section>
);
}
}
export default SinglePost;
UPDATE- SOLVED
After Ayèch Hamza suggestion added getInitialProps method and thats exactly what needed.
So In [postid].js,
import React from "react";
import Layout from "../../components/layout";
import SinglePost from "../../components/post/singlePost";
Post.getInitialProps = async ctx => {
const PostId = ctx.query.postid;
return { PostId };
};
function Post({ PostId }) {
return (
<Layout>
<SinglePost PostId={PostId}/>
</Layout>
);
}
export default Post;
and in SinglePost.js,
componentDidMount() {
const postId = this.props.PostId;
singlePost(postId).then(data => {
if (data.error) {
console.log(data.error);
} else {
this.setState({
post: data,
likes: data.likes.length,
like: this.checkLike(data.likes),
comments: data.comments
});
}
});
}
I have a simple app which fetches some weather JSON and displays it. The user can either enter a location or they can hit a "Get lucky" button, which fetches a random city. the initial state is set in App.js
this.state = {
error: '',
status: '',
queryString: 'london,gb',
queryID: '',
queryType: 'q',
cityData: cityData,
weatherData: {},
isLoaded: false
}
Next, I have my main App class, then I have a child component called that contains the form gubbins. I call it in app render as follows:
<SearchForm
queryString={this.state.queryString}
handleChange={this.handleChange}
setQueryType={this.setQueryType}
setQueryID={this.setQueryID}
getWeatherData={this.getWeatherData}
/>
I use callback functions in there to set the query type (location or ID). An example of one of the call back functions in App.js is:
setQueryType = (queryType) => {
this.setState({
queryType: queryType
})
}
This is called in the form JS using:
props.setQueryType(e.target.attributes.query.value)
Now, here is the crux of the issue: the state doesn't update the first time, but DOES on the second click? In fact, other vars like queryString set in the fetch are not set until the second click.
App.js
import React, { Component } from 'react';
import './css/App.css';
import WeatherCard from './components/WeatherCard'
import Header from './components/Header'
import SearchForm from './components/SearchForm'
import cityData from './json/city.list'
const config = {
API: 'https://api.openweathermap.org/data/2.5/forecast',
API_KEY: process.env.REACT_APP_OPEN_WEATHER_MAP_API_KEY
}
class App extends Component {
constructor() {
super()
this.state = {
error: '',
status: '',
queryString: 'london,gb',
queryID: '',
queryType: 'q',
cityData: cityData,
weatherData: {},
isLoaded: false
}
this.getWeatherData()
}
getWeatherData = (searchValue="london,gb") => {
let URL
URL = config.API + '?' + this.state.queryType + '='
URL += this.state.queryType === 'q' ? searchValue : this.state.queryID
URL += '&units=metric&APPID=' + config.API_KEY
console.log(URL)
fetch(URL)
.then( result => result.json() )
.then (
(result) => {
if ( result.cod === '200') {
this.setState({
status: result.cod,
weatherData: result,
queryString: result.city.name,
isLoaded: true
})
} else {
this.setState({
status: result.cod,
error: result.message,
isLoaded: false
})
}
},
(error) => {
this.setState({
isLoaded: false,
error: error
})
}
)
console.log(this.state.queryString)
}
handleChange = (event) => {
const { name, value } = event.target
this.setState({
[name]: value
})
}
getWeatherCards = () => {
let cards = []
for (let i = 0; i < this.state.weatherData.cnt; i++) {
cards.push(
<WeatherCard
key={i}
weatherList={this.state.weatherData.list[i]}
/>
)
}
return cards
}
setQueryType = (queryType) => {
this.setState({
queryType: queryType
})
}
setQueryID = () => {
let randomID = Math.floor(Math.random() * this.state.cityData.length)
let randomCityID = this.state.cityData[randomID].id
this.setState({
queryID: randomCityID
})
}
getlocationForm = () => {
return(
<SearchForm
queryString={this.state.queryString}
handleChange={this.handleChange}
setQueryType={this.setQueryType}
setQueryID={this.setQueryID}
getWeatherData={this.getWeatherData}
/>
)
}
render = () => {
if (this.state.status !== '200') {
return (
<div className='App'>
<Header
status={this.state.status}
error={this.state.error}
/>
{this.getlocationForm()}
</div>
)
} else {
return (
<div className='App'>
{
this.state.isLoaded && (
<Header
cityName={this.state.weatherData.city.name}
countryName={this.state.weatherData.city.country}
status={this.state.status}
error={this.state.error}
/>
)
}
{this.getlocationForm()}
{
this.state.isLoaded && (
<div className='weather-cards'>
{this.getWeatherCards()}
</div>
)
}
</div>
)
}
}
}
export default App;
SearchForm.js
import React from 'react'
const SearchForm = (props) => {
let handleChange = function(e) {
props.handleChange(e)
}
let handleClick = function(e) {
e.preventDefault()
props.setQueryType(e.target.attributes.query.value)
if (e.target.attributes.query.value === 'id') {
props.setQueryID()
}
props.getWeatherData()
}
return (
<div>
<form className="search-form">
<input
type="text"
id="query"
name="query"
placeholder="Enter a location..."
onChange={handleChange}
/>
<button
type="submit"
query="q"
onClick={handleClick}
>
Submit
</button>
<button
type="submit"
query="id"
onClick={handleClick}
>
I'm feeling lucky...
</button>
</form>
</div>
)
}
export default SearchForm
In your App.js constructor add this.setQueryType = this.setQueryType.bind(this)
That line will bind the context of this to the current component, so when called from a child, will update parent state.
I think the problem comes from the fact that when you call getWeatherData,
you don't know if the setState will be over as it is an asynchronous method. (as you can see in the documentation)
So the best way, to ensure that the setState is done before calling your method without being certain of the state of your component, would be to use the callBack parameter of the setState to ensure it runs after the setState method has been finished.
try to put your this.getWeatherData() into the componentDidMount and remove it from the constructor
componentDidMount() {
this.getWeatherData()
}