Updated state not rendering in child component - reactjs

I am trying to pull a picture from NASA's API to get their astronomy picture of the day. When I update the state in my API call and console.log this.state.picture, I can see that picture has been set to the data object. But when I try to log picture in render, it logs an empty array. I used a similar approach in previous assignments and never had this issue, so I really can't figure out what I'm doing wrong. I'm new to React, so sorry if the answer is really obvious.
Parent component:
class App extends Component {
state = {
picture: [],
asteroids: [],
}
render() {
return (
<div className="App">
<Route exact path="/" component={Homepage}/>
<Route path="/apod" render={() =>
<APOD picture={this.state.picture}/>}/>
<Route path="/asteroids" render={() =>
<Asteroids asteroids={this.state.asteroids} />} />
</div>
);
}
}
export default App;
Child component:
class Apod extends React.Component{
getApodPicture = e => {
e.preventDefault();
let url = `https://api.nasa.gov/planetary/apod?api_key=v1sEi9chxFYzrf1uXei0J1GvXaemhnQXiDjEcnK2`;
fetch(url)
.then(res => {
if (!res.ok){
throw new Error (res.status)
}
return res.json();
})
.then(data => {
this.setState({picture: data})
console.log(this.state.picture) // logs the data object from NASA
})
.catch(err => console.log(err))
}
render(){
console.log(this.props.picture) // logs []
// console.log(this.state.picture) // returns "Cannot read property 'picture' of null"
return(
<div>
<h1>Astronomy picture of the day!</h1>
<Link to="/">Back to homepage</Link>
<section>
<button onClick={e => this.getApodPicture(e)}>Click to see picture</button>
</section>
</div>
)
}
}
export default Apod;

This.setState will set the state of the component, not the parent component.
you will need a changeHandler to get the desired result.
something along the lines of
class App extends Component {
state = {
picture: '',
asteroids: [],
}
pictureChangeHandler = value => {
this.setState(value);
}
render() {
return (
<div className="App">
<Route exact path="/" component={Homepage}/>
<Route path="/apod" render={() =>
<APOD pictureChangeHandler={this.pictureChangeHandler}
picture={this.state.picture}/>}/>
<Route path="/asteroids" render={() =>
<Asteroids asteroids={this.state.asteroids} />} />
</div>
);
}
}
export default App;
class Apod extends React.Component{
getApodPicture = e => {
e.preventDefault();
let url = `https://api.nasa.gov/planetary/apod?api_key=v1sEi9chxFYzrf1uXei0J1GvXaemhnQXiDjEcnK2`;
fetch(url)
.then(res => {
if (!res.ok){
throw new Error (res.status)
}
return res.json();
})
.then(data => {
this.props.pictureChangeHandler(data)
})
.catch(err => console.log(err))
}
render(){
console.log(this.props.picture) // logs []
// console.log(this.state.picture) // returns "Cannot read property 'picture' of null"
return(
<div>
<h1>Astronomy picture of the day!</h1>
<Link to="/">Back to homepage</Link>
<section>
<button onClick={e => this.getApodPicture(e)}>Click to see picture</button>
</section>
</div>
)
}
}

Related

componentDidMount getting called multiple times in redux connected component

I have a reactjs+redux app in which app.js, the first component to be mounted is given below:
//all imports here
class App extends React.Component {
constructor(){
super();
this.state = {
loginState : false,
}
}
componentDidMount() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log(user);
if(user.displayName&&user.photoURL&&user.email)
this.props.dispatch(login(user.displayName, user.photoURL, user.email));
else
this.props.dispatch(login(user.email.split("#")[0], "", ""));
this.setState({
loginState: true
});
}
else{
this.props.dispatch(changeLoading());
}
});
}
logout = () => {
firebase
.auth()
.signOut()
.then(() => {
this.setState({
loginState : false,
});
this.props.dispatch(logout());
this.props.history.push('/login');
})
.catch((err) => {
console.log(err);
});
};
render() {
return (
<>
<Switch>
{this.props.userName?(<Route
exact
path="/"
component={() => <Homepage userName={this.props.userName} />}
/>):(<Route exact path="/" component={Loading} />)}
<Route exact path="/login" component={Login} />
<Redirect to="/" />
</Switch>
</>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.userState.isLoggedIn,
userName: state.userState.userName,
email: state.userState.email,
photoURL: state.userState.photoURL
};
};
export default withRouter(connect(mapStateToProps)(App));
Below is Homepage.js:
class Homepage extends React.Component{
componentDidMount(){
console.log("component mounted");
this.props.dispatch(fetchPosts());
}
render(){
console.log("Homepage reached");
if(this.props.userName==='') return <Redirect to="/login" />
return(
<div className="container-fluid m-0" style={{paddingTop: '100px',paddingLeft: '0',paddingRight: '0'}}>
<div className="row m-0">
<div class="col-md-1"></div>
<Main userName={this.props.userName} />
<Leftside userName={this.props.userName} />
<div class="col-md-1"></div>
</div>
</div>
);
}
}
const mapStateToProps=(state)=>({})
export default connect(mapStateToProps)(Homepage);
And below is the reducer:
export const userState=(state={isLoading: true,isLoggedIn: false,userName: '', photoURL: ''},action)=>{
switch(action.type){
case 'LOGIN': {console.log("reached");return {...state,isLoading: false,isLoggedIn: true, userName: action.payload.userName, photoURL: action.payload.photoURL, email: action.payload.email}}
case 'LOGOUT': return {...state,isLoading: false,isLoggedIn:false,userName: '',photoUrl: ''}
case 'CHANGE': return {...state,isLoading: false}
default: return {...state}
}
}
Basically what is happening is that initially when the app opens, this.props.userName is empty and hence Loading component is loaded. Once the firebase returns the user details, they are dispatched to redux reducer. When this happens, state.userState.userName becomes available and Homepage is mounted. As expected, its componentDidMount method runs and posts are fetched and dispatched to the redux store( posts are stored in redux store). But then suddenly Homepage unmounts and mounted again and consequently, componntDidMount runs again. So, in total, there are two fetchPost requests.
I do not understand this behaviour. I have read that componentDidMount runs only a single time.
Please help me to remove this bug.
Thank You!

React) "this.props" while sharing the "state" using router

Somebody help me :(
I couldn't find "this.props.Epoint" on the result page.
It's just like "Epoint : " "IPoint : ". Empty, Empty.
I do have to receive "Epoint : 0", "IPoint : ", don't I?
Here is the code. Please save me.
<App.js>
class App extends Component {
state = {
EPoint: 0,
IPoint: 0,
};
upEPoint = async () => {
this.setState({
Epoint: this.state.EPoint ++
})
};
upIPoint = async () => {
this.setState({
Ipoint: this.state.IPoint ++
})
};
render() {
return (
<>
<Router>
<Route exact path="/" component={Home} />
<Route path="/question1" component={() => <Question1 EPoint={this.state.EPoint} IPoint={this.state.IPoint} upEPoint={this.upEPoint} upIPoint={this.upIPoint}/>} />
<Route path="/question2" component={() => <Question2 EPoint={this.state.EPoint} IPoint={this.state.IPoint} upEPoint={this.upEPoint} upIPoint={this.upIPoint}/>} />
<Route path="/question3" component={() => <Question3 EPoint={this.state.EPoint} IPoint={this.state.IPoint} upEPoint={this.upEPoint} upIPoint={this.upIPoint}/>} />
<Route path="/result" component={() => <Result EPoint={this.state.EPoint} IPoint={this.state.IPoint}/>} />
<Router/>
</>
export default App;
<Result.js>
class Result extends Component {
render() {
return (
<div>
<header>
<h1> Result </h1>
<h5> Epoint : {this.props.Epoint}</h5>
<h5> Ipoint : {this.props.Ipoint}</h5>
</header>
</div>)
}
}
export default Result;
I think the first issue here is that you are trying to access Epoint from props, but the variable in state that you are passing down in props is actually EPoint (notice the capital P there). Same goes for IPoint.
Your Result.js should look like this:
import React from "react";
class Result extends React.Component {
render() {
return (
<div>
<header>
<h1> Result </h1>
<h5> Epoint : {this.props.EPoint}</h5>
<h5> Ipoint : {this.props.IPoint}</h5>
</header>
</div>
);
}
}
export default Result;
As the other answers have also mentioned, you cannot set your state as you have.
I am not so good with class components, but I believe you must set it something like the following:
constructor(props) {
super(props);
this.state = { EPoint: 0, IPoint: 0 };
}
u cant use this.state inside setState just get prev state from arrow function then assign it to new object and return it into setState
upIPoint = async () => {
this.setState(prev => ({
Ipoint: prev.IPoint + 1
})
};

Why do 3 different component instances seem to be sharing state?

I have a mind boggling issue where all three of these <RecordAdmin> component instances seem to be using the state from whichever component is loaded first on page load.
I have no clue how it's happening or why, and weirdly, it was working before.
<Switch>
<Route path="/admin/books">
<RecordAdmin singular="book" plural="books" table={BookTable} form={BookForm} />
</Route>
<Route path="/admin/authors">
<RecordAdmin singular="author" plural="authors" table={AuthorTable} form={AuthorForm} />
</Route>
<Route path="/admin/branches">
<RecordAdmin singular="branch" plural="branches" table={BranchTable} form={BranchForm} />
</Route>
</Switch>
Using console.log, it seems as though all 3 of these components will have the same this.state.records object. Shouldn't each component instance have its own state?
Here is the source for the <RecordAdmin> component:
import React from "react";
import Axios from "axios";
import {
Switch,
Route,
NavLink,
Redirect
} from "react-router-dom";
class NewRecordForm extends React.Component {
constructor(props) {
super(props);
this.state = {
redirect: false,
};
}
handleSubmit = (event, formFields, multipart = false) => {
event.preventDefault();
let formData = null;
let config = null;
if (multipart) {
formData = new FormData();
for (let [key, value] of Object.entries(formFields)) {
formData.append(key, value)
}
config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
} else {
formData = formFields;
}
Axios.post(`${process.env.REACT_APP_API_URL}/${this.props.plural}`, formData, config)
.then(response => {
this.setState({redirect: true})
}).catch(error => {
console.log(error)
})
}
render() {
if (this.state.redirect) {
this.props.redirectCallback();
}
const Form = this.props.form
return (
<div>
{this.state.redirect ? <Redirect to={`/admin/${this.props.plural}`} /> : null}
<Form handleSubmit={this.handleSubmit} />
</div>
)
}
}
function errorMessage(props) {
return (
<div class="alert alert-danger" role="alert">
{props.msg}
</div>
)
}
export default class RecordAdmin extends React.Component {
constructor(props) {
super(props)
this.state = {
records: []
}
}
componentDidMount() {
this.loadRecords();
}
loadRecords = () => {
Axios.get(process.env.REACT_APP_API_URL + '/' + this.props.plural)
.then(response => {
this.setState({records: response.data})
}).catch(error => {
console.log(error)
})
}
deleteRecord = (event, recordId) => {
event.preventDefault();
Axios.delete(process.env.REACT_APP_API_URL + '/' + this.props.plural + '/' + recordId).then(response => {
this.loadRecords();
})
}
render() {
// this allows us to pass props to children that are loaded via {this.props.children}
// more on that here: https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356
const TableComponent = this.props.table
return (
<div className="admin-body">
{this.state.errorMessage ? <errorMessage msg={this.state.errorMessage} /> : null}
<Switch>
<Route exact path={`/admin/${this.props.plural}`}>
<div className="admin-menu">
<NavLink className="btn btn-primary" to={`/admin/${this.props.plural}/new`}>New {this.props.singular.charAt(0).toUpperCase() + this.props.singular.slice(1)}</NavLink>
</div>
<TableComponent records={this.state.records} deleteRecord={this.deleteRecord} />
</Route>
<Route exact path={`/admin/${this.props.plural}/new`}>
<NewRecordForm plural={this.props.plural} form={this.props.form} redirectCallback={this.loadRecords}/>
</Route>
</Switch>
</div>
);
}
}
EDIT:
When I throw in a console.log I see that the first <RecordAdmin> that is loaded on page load, is having its records output to the console no matter which <RecordAdmin> instance is currently selected.
render() {
// this allows us to pass props to children that are loaded via {this.props.children}
// more on that here: https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356
const TableComponent = this.props.table
console.log(this.records) // No matter which <RecordAdmin> is currently being displayed, the records will be the records from whichever <RecordComponent was first loaded on page load.
return (
<div className="admin-body">
{this.state.errorMessage ? <errorMessage msg={this.state.errorMessage} /> : null}
<Switch>
<Route exact path={`/admin/${this.props.plural}`}>
<div className="admin-menu">
<NavLink className="btn btn-primary" to={`/admin/${this.props.plural}/new`}>New {this.props.singular.charAt(0).toUpperCase() + this.props.singular.slice(1)}</NavLink>
</div>
{console.log(this.state.records)}
<TableComponent records={this.state.records} deleteRecord={this.deleteRecord} />
</Route>
<Route exact path={`/admin/${this.props.plural}/new`}>
<NewRecordForm plural={this.props.plural} form={this.props.form} redirectCallback={this.loadRecords}/>
</Route>
</Switch>
</div>
);
}
No matter which <RecordAdmin> instance is being displayed, using console.log shows that state is being shared between all 3 <RecordAdmin> instances.
You can use different key for each instance of RecordAdmin and maybe pass exact={true} just to be sure.

Undefined prop value in child component

I'm attempting to read an array item in a child component via props. Logging the array in the child component works. But if I try to access a property of one of the array items by indexing it with the :id from match.params, it tells me that I can't access a property of 'undefined'.
Any guidance would be greatly appreciated.
tours.js
import React, { Component } from "react";
import { Route, Switch } from "react-router-dom";
// Page Imports
import Summary from "../pages/summary";
import Details from "../pages/details";
// Component Imports
import Homebutton from "../components/homebutton";
class Tours extends Component {
state = {
tours: []
};
componentDidMount() {
window.scrollTo(0, 0);
fetch("/tours")
.then(res => res.json())
.then(res => this.setState({ tours: res }));
}
render() {
const tours = this.state.tours;
return (
<section className="tours-page">
<div className="center-box">
<h2>Tours</h2>
</div>
<Switch>
<Route
exact
path={this.props.match.url}
render={props => <Summary {...props} tours={tours} />}
/>
<Route
path={this.props.match.url + "/:id"}
render={props => <Details {...props} tours={tours} />}
/>
</Switch>
<Homebutton />
</section>
);
}
}
export default Tours;
details.js
import React from "react";
const Details = ({
tours,
match: {
params: { id }
}
}) => (
<section className="details">
<h2>{tours[id]["name"]}</h2>
</section>
);
export default Details;
To be sure that tours[id] is not undefined you should check it first
<section className="details">
<h2>{tours[id] && tours[id]["name"]}</h2>
</section>
As componentDidMountalways gets called after first render, you must validate your props to avoid app crashes:
const Details = ({
tours,
match: {
params: { id }
}
}) => (
<section className="details">
<h2>{tours.length && tours[id]["name"]}</h2>
</section>
);

search component with react/redux/router flow

I'm trying to complete my app, have learned react, redux, react router all in one, now I'm just confused a bit when it comes to putting it all together.
Say I have a Nav component that's included in a header that's included globally on all pages and it calls a redux action which then runs a reducer and returns some search results.
When one searches from the navigation bar, how do I get it to redirect a search page that then returns the search results?
Nav component
class Nav extends React.Component {
render() {
const { search } = this.props;
return (
<header>
<SearchBox search={search} />
</header>
)
}
}
that includes a search component
class SearchBox extends React.Component {
constructor() {
super();
this.state = {
name: ''
}
}
handleChange = event => {
this.setState({
[event.target.id]: event.target.value
});
}
handleSubmit = event => {
event.preventDefault();
this.props.search(JSON.stringify({name: this.state.name}))
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" id="name" onChange={this.handleChange} placeholder="Search" />
<button type="submit">Search</button>
</form>
)
}
}
my layouts are like
index.js
const Index = () => {
return (
<Nav />
... Home Content
)
}
profile.js
const Profile = () => {
return (
<Nav />
... Profile Content
)
}
search.js
const Users = (props) => {
let list = null;
list = props.users.map((user, index)=> {
const { name } = user.profile;
const { username } = user;
return (
<li key={index}>
<h3><a href={'/'+username}>{name}</a></h3>
</li>
)
});
return <section id="search"><ul>{list}</ul></section>;
}
class Search extends React.Component {
render() {
const { searchResults } = this.props;
return (
<Nav />
<div>
{
/* show 'No results when no results found' */
searchResults !== ''
? seachResults.length == 0
? 'No results found'
: <Users users={searchResults} />
: null
}
</div>
)
}
}
const mapStateToProps = state => ({
searchResults: state.store.searchResults,
});
the user action is
export const search = (name) => dispatch => {
...
dispatch({
type: SEARCH_USER,
payload: res
})
the reducer is
const initialState = {
searchResults: ''
};
case SEARCH_USER:
return {
...state,
searchResults: action.payload.search
}
}
index.js
class App extends React.Component {
render() {
return (
<Router>
<Switch>
<Route path="/" exact={true} component={Index} />
<Route path="/profile" component={Profile} />
<Route path="/search" component={Search} />
</Switch>
</Router>
)
}
}

Resources