Getting props from parent component state to render data - reactjs

I am building a weather app.
The behavior would be to have a Button in my main menu. This Button should display the current weather. When clicking this button it should display a card with all the weather informations. This is pretty similar to Momentum
I could successfully create my Weather Card displaying the current Weather and also the forecast.
My issue is I do not know how to display the weather in my button before I click on it to display weather. Not sure how to access my data and render it.
My SideMenu component displaying the Menu
export default class SideMenu extends React.Component {
constructor(props) {
super(props);
}
changeView(e, view) {
e.preventDefault();
this.props.changeView(view);
}
render() {
const { cityName } = this.props;
return (<Menu>
<Button onClick={(e) => this.changeView(e, "weather")}>
</Button>
<Button onClick={(e) => this.changeView(e, "todo")}>
ToDo
</Button>
<Button onClick={(e) => this.changeView(e, "pomodoro")}>
Pomo
</Button>
<Button onClick={(e) => this.changeView(e, "picture")}>
Info
</Button>
</Menu>);
}
}
The Weather Card component where I get the data from the API and render it
class WeatherCard extends Component {
constructor(props) {
super(props);
this.state = {
temperature: "",
latitude: "",
longitude: "",
summary: "",
cityName: "",
numForecastDays: 5,
isLoading: false
};
}
componentDidMount() {
this.getLocation();
}
// Use of APIXU API with latitude and longitude query
getWeather() {
const { latitude, longitude, numForecastDays } = this.state;
const URL = `https://api.apixu.com/v1/forecast.json?key=${KEY}&q=${latitude},${longitude}&days=${numForecastDays}`;
axios
.get(URL)
.then(res => {
const data = res.data;
this.setState({
cityName: data.location.name + ", " + data.location.region,
summary: data.current.condition.text,
temperature: data.current.temp_c,
forecastDays: data.forecast.forecastday,
iconURL: data.current.condition.icon
});
})
.catch(err => {
if (err) console.log(err);
});
}
// function using current longitude and latitude of user
// This requires authorization from user // Could be changed using IP adress instead, but would be less precise
getLocation() {
navigator.geolocation.getCurrentPosition(
position => {
this.setState(
prevState => ({
latitude: position.coords.latitude,
longitude: position.coords.longitude
}),
() => {
this.getWeather();
}
);
},
error => this.setState({ forecast: error.message }),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 }
);
}
render() {
const {
summary,
temperature,
cityName,
iconURL,
forecastDays,
isLoading
} = this.state;
return (
<div>
{isLoading && (
<div>
<Spinner />
<LoaderText>Loading....</LoaderText>
</div>
)}
{!isLoading && (
<Wrapper>
<CurrentWeather
cityName={cityName}
summary={summary}
temperature={temperature}
icon={iconURL}
/>
<Forecast forecastDays={forecastDays} />
</Wrapper>
)}
</div>
);
}
}
export default WeatherCard;

You can control the display of you widget using the state.
You can pass a click handler to your sidemenu as a prop, once you click on an item you emit the click event to the parent component (with some payload if you want).
The parent component will have a handler method which is responsible for displaying your widget.
I've made some adjustments into you index.js and SideMenu.js files.
index.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import WeatherCard from './Weather';
import SideMenu from './SideMenu';
class App extends Component {
constructor() {
super();
this.state = {
showWeather: false
}
}
handleItemClick = (item) => {
if (item === 'weather') {
this.setState({
showWeather: true
});
}
}
render() {
return (
<div>
<SideMenu onItemClick={this.handleItemClick} />
{this.state.showWeather ? <WeatherCard /> : null}
</div>
);
}
}
render(<App />, document.getElementById('root'));
SideMenu.js
export default class SideMenu extends React.Component {
constructor(props) {
super(props);
}
render() {
const { cityName } = this.props;
return (
<Menu>
<Button onClick={() => this.props.onItemClick('weather')}>
Open Weather Widget
</Button>
</Menu>
);
}
}
here is a fully working stacklitz with the adjustments mentioned above, hope that this will help.
If you want a data to be accessible by all the components, then you have these options:
React Context
Redux or MobX which are state management libraries.

Related

react child component not updating after sibling updates

I'm trying to update a sibling from another sibling but for some reasons it does not update in all cases.
I would appreciate if you can help me to find the issue.
So what I'm trying to do is to update InfoBox component from ProductSync component
I would like my output to look like this:
Case1(sync one product):
history:
Sync started ...
Message from Server
Case2 (sync multi products):
history:
Sync started ...
Message from Server
Sync started ...
Message from Server
Sync started ...
Message from Server
.
.
.
What I actually get is this:
Case1(sync one product):
history:
Message from Server
Case2 (sync multi products):
history:
(Dont get any more message here)
export class Admin extends React.Component {
constructor() {
super();
this.syncProduct = this.syncProduct.bind(this);
this.syncMultiProducts = this.syncMultiProducts.bind(this);
this.updateInfoBox = this.updateInfoBox.bind(this);
this.state = {
infoBox: ["history:"]
};
}
updateInfoBox(newText) {
const newStateArray = this.state.infoBox.slice();
newStateArray.push(newText);
this.setState({
infoBox: newStateArray
});
}
syncProduct(item) {
$.ajax({
datatype: "text",
type: "POST",
url: `/Admin/Sync`,
data: { code: item.Code },
async: false,
success: (response) => {
this.updateInfoBox (response.InfoBox);
},
error: (response) => {
this.updateInfoBox (response.InfoBox);
}
});
}
syncMultiProducts(items) {
/*this does not re-render InfoBox component*/
items.map((item, index) => {
this.syncProduct(item);
});
}
render() {
return (
<div>
<InfoBox infoBox={this.state.infoBox}/>
<ProductSync syncProduct={this.syncProduct} syncMultiProducts={this.syncMultiProducts} updateInfoBox={this.updateInfoBox}/>
</div>
);
}
}
ReactDOM.render(
<Admin />,
document.getElementById("admin")
);
First child(ProductSync.jsx):
export class ProductSync extends React.Component {
constructor() {
super();
this.syncProduct= this.syncProduct.bind(this);
}
syncProduct(item) {
this.props.updateInfoBox("Sync started...");/*this does not re-render InfoBox component*/
this.props.syncProduct(getItemFromDB());
}
syncMultiProducts() {
this.props.updateInfoBox("Sync started...");/*this does not re-render InfoBox component*/
this.props.syncMultiProducts(getItemsFromDB());
}
render() {
return (
<div>
<button onClick={() => this.syncMultiProducts()}>Sync all</button>
<button onClick={() => this.syncProduct()}>Sync one</button>
</div>
);
}
}
Second Child(InfoBox.jsx)
export class InfoBox extends React.Component {
constructor(props) {
super(props);
this.state = {
infoBox: props.infoBox
};
}
componentWillReceiveProps(nextProps) {
this.setState({ infoBox: nextProps.infoBox});
}
render() {
const texts =
(<div>
{this.state.infoBox.map((text, index) => (
<p key={index}>{text}</p>
))}
</div>);
return (
<div>
{Texts}
</div>
);
}
}
Thanks in advance

How to keep a new component after Search function is called?

On a React project, I'm trying to link user's spotify playlist to an app page. Until this step, I've created quite a few components to enable user to search for songs, add the ones from the list to his spotify playlist (but user cannot see his previous playlists).
I have added a dummy box to be utilized when I achieve to pull user's playlist info, the problem is when I type a song name and click Search, the box I created for this purpose disappears.
My code previous to adding latest SpotifyPlaylist box can be find in the following: https://github.com/basakulcay/Jammming
This is how it looks like before search:
I cannot find out where I need to make a change to make this happen. I think, it should be related to onSearch, but nothing I tried seemed to be working. *Appreciate the help! :) *
Below is the code from App.js:
import React from 'react';
import './App.css';
import { SearchBar } from '../SearchBar/SearchBar';
import { SearchResults } from '../SearchResults/SearchResults.js';
import { Playlist } from '../Playlist/Playlist.js';
import Spotify from '../../util/Spotify.js';
import { SpotifyPlaylist } from '../spotifyPlaylist/SpotifyPlaylist';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
playlistName: 'My Playlist',
playlistTracks: [],
};
this.addTrack = this.addTrack.bind(this);
this.removeTrack = this.removeTrack.bind(this);
this.updatePlaylistName = this.updatePlaylistName.bind(this);
this.savePlaylist = this.savePlaylist.bind(this);
this.search = this.search.bind(this);
}
addTrack(track) {
let tracks = this.state.playlistTracks;
if (tracks.find((savedTracks) => savedTracks.id === track.id)) {
return;
}
tracks.push(track);
this.setState({ playlistTracks: tracks });
}
removeTrack(track) {
let tracks = this.state.playlistTracks;
tracks = tracks.filter((currentTrack) => currentTrack.id !== track.id);
this.setState({ playlistTracks: tracks });
}
updatePlaylistName(name) {
this.setState({ playlistName: name });
}
savePlaylist() {
const trackUris = this.state.playlistTracks.map((track) => track.uri);
Spotify.savePlaylist(this.state.playlistName, trackUris).then(() => {
this.setState({ playlistName: 'New Playlist', playlistTracks: [] });
});
}
search(term) {
Spotify.search(term).then((searchResults) => {
this.setState({ searchResults: searchResults });
});
}
render() {
return (
<div>
<h1>
Ja<span className="highlight">mmm</span>ing
</h1>
<div className="App">
<SearchBar onSearch={this.search} />
<div className="App-playlist">
<SearchResults
onAdd={this.addTrack}
searchResults={this.state.searchResults}
/>
<Playlist
playlistName={this.state.playlistName}
playlistTracks={this.state.playlistTracks}
onRemove={this.removeTrack}
onNameChange={this.updatePlaylistName}
onSave={this.savePlaylist}
/>
<SpotifyPlaylist />
</div>
</div>
</div>
);
}
}
export default App;

React setState fetch API

I am starting to learn React and creating my second project at the moment. I am trying to usi MovieDb API to create a movie search app. Everything is fine when I get the initial list of movies. But onClick on each of the list items I want to show the details of each movie. I have created a few apps like this using vanilla JS and traditional XHR call. This time I am using fetch API which seems straightforward ans simply to use, however when I map through response data to get id of each movie in order to retrieve details separately for each of them I get the full list of details for all the items, which is not the desired effect. I put the list of objects into an array, because after setState in map I was only getting the details for the last element. I know that I am probably doing something wrong within the API call but it might as well be my whole REACT code. I would appreciate any help.
My code
App.js
import React, { Component } from 'react';
import SearchInput from './Components/SearchInput'
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state =
{
value: '',
showComponent: false,
results: [],
images: {},
};
this.handleSubmit = this.handleSubmit.bind(this);
this.handleOnChange = this.handleOnChange.bind(this);
this.getImages = this.getImages.bind(this);
this.getData = this.getData.bind(this);
}
ComponentWillMount() {
this.getImages();
this.getData();
}
getImages(d) {
let request = 'https://api.themoviedb.org/3/configuration?api_key=70790634913a5fad270423eb23e97259'
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
this.setState({
images: data.images
});
});
}
getData() {
let request = new Request('https://api.themoviedb.org/3/search/movie?api_key=70790634913a5fad270423eb23e97259&query='+this.state.value+'');
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
this.setState({
results: data.results
});
});
}
handleOnChange(e) {
this.setState({value: e.target.value})
}
handleSubmit(e) {
e.preventDefault();
this.getImages();
this.setState({showComponent: true});
this.getData();
}
render() {
return (
<SearchInput handleSubmit={this.handleSubmit} handleOnChange={this.handleOnChange} results={this.state.results} images={this.state.images} value={this.state.value} showComponent={this.state.showComponent}/>
);
}
}
export default App;
SearchInput.js
import React, {Component} from 'react';
import MoviesList from './MoviesList';
class SearchInput extends Component {
render() {
return(
<div className='container'>
<form id='search-form' onSubmit={this.props.handleSubmit}>
<input value={this.props.value} onChange={this.props.handleOnChange} type='text' placeholder='Search movies, tv shows...' name='search-field' id='search-field' />
<button type='submit'>Search</button>
</form>
<ul>
{this.props.showComponent ?
<MoviesList value={this.props.value} results={this.props.results} images={this.props.images}/> : null
}
</ul>
</div>
)
}
}
export default SearchInput;
This is the component where I try to fetch details data
MovieList.js
import React, { Component } from 'react';
import MovieDetails from './MovieDetails';
let details = [];
class MoviesList extends Component {
constructor(props) {
super(props);
this.state = {
showComponent: false,
details: []
}
this.showDetails = this.showDetails.bind(this);
this.getDetails = this.getDetails.bind(this);
}
componentDidMount() {
this.getDetails();
}
getDetails() {
let request = new Request('https://api.themoviedb.org/3/search/movie?api_key=70790634913a5fad270423eb23e97259&query='+this.props.value+'');
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
data.results.forEach((result, i) => {
let url = 'https://api.themoviedb.org/3/movie/'+ result.id +'?api_key=70790634913a5fad270423eb23e97259&append_to_response=videos,images';
return fetch(url)
.then((response) => {
return response.json();
}).then((data) => {
details.push(data)
this.setState({details: details});
});
});
console.log(details);
});
}
showDetails(id) {
this.setState({showComponent: true}, () => {
console.log(this.state.details)
});
console.log(this.props.results)
}
render() {
let results;
let images = this.props.images;
results = this.props.results.map((result, index) => {
return(
<li ref={result.id} id={result.id} key={result.id} onClick={this.showDetails}>
{result.title}{result.id}
<img src={images.base_url +`${images.poster_sizes?images.poster_sizes[0]: 'err'}` + result.backdrop_path} alt=''/>
</li>
)
});
return (
<div>
{results}
<div>
{this.state.showComponent ? <MovieDetails details={this.state.details} results={this.props.results}/> : null}
</div>
</div>
)
}
}
export default MoviesList;
MovieDetails.js
import React, { Component } from 'react';
class MovieDetails extends Component {
render() {
let details;
details = this.props.details.map((detail,index) => {
if (this.props.results[index].id === detail.id) {
return(
<div key={detail.id}>
{this.props.results[index].id} {detail.id}
</div>
)} else {
console.log('err')
}
});
return(
<ul>
{details}
</ul>
)
}
}
export default MovieDetails;
Theres a lot going on here...
//Here you would attach an onclick listener and would fire your "get details about this specific movie function" sending through either, the id, or full result if you wish.
//Then you getDetails, would need to take an argument, (the id) which you could use to fetch one movie.
getDetails(id){
fetch(id)
displayresults, profit
}
results = this.props.results.map((result, index) => {
return(
<li onClick={() => this.getDetails(result.id) ref={result.id} id={result.id} key={result.id} onClick={this.showDetails}>
{result.title}{result.id}
<img src={images.base_url +`${images.poster_sizes?images.poster_sizes[0]: 'err'}` + result.backdrop_path} alt=''/>
</li>
)
});
Thanks for all the answers but I have actually maanged to sort it out with a bit of help from a friend. In my MovieList I returned a new Component called Movie for each component and there I make a call to API fro movie details using each of the movie details from my map function in MovieList component
Movielist
import React, { Component } from 'react';
import Movie from './Movie';
class MoviesList extends Component {
render() {
let results;
if(this.props.results) {
results = this.props.results.map((result, index) => {
return(
<Movie key={result.id} result={result} images={this.props.images}/>
)
});
}
return (
<div>
{results}
</div>
)
}
}
export default MoviesList;
Movie.js
import React, { Component } from 'react';
import MovieDetails from './MovieDetails';
class Movie extends Component {
constructor(props) {
super(props);
this.state = {
showComponent: false,
details: []
}
this.showDetails = this.showDetails.bind(this);
this.getDetails = this.getDetails.bind(this);
}
componentDidMount() {
this.getDetails();
}
getDetails() {
let request = new Request('https://api.themoviedb.org/3/search/movie?api_key=70790634913a5fad270423eb23e97259&query='+this.props.value+'');
fetch(request)
.then((response) => {
return response.json();
}).then((data) => {
let url = 'https://api.themoviedb.org/3/movie/'+ this.props.result.id +'?api_key=70790634913a5fad270423eb23e97259&append_to_response=videos,images';
return fetch(url)
}).then((response) => {
return response.json();
}).then((data) => {
this.setState({details: data});
});
}
showDetails(id) {
this.setState({showComponent: true}, () => {
console.log(this.state.details)
});
}
render() {
return(
<li ref={this.props.result.id} id={this.props.result.id} key={this.props.result.id} onClick={this.showDetails}>
{this.props.result.title}
<img src={this.props.images.base_url +`${this.props.images.poster_sizes?this.props.images.poster_sizes[0]: 'err'}` + this.props.result.backdrop_path} alt=''/>
{this.state.showComponent ? <MovieDetails details={this.state.details}/> : null}
</li>
)
}
}
export default Movie;

Render happens before componentwillmount Meteor API call

Hello I'm having some trouble with async. It seems that the render is called before the api call is done in the componentwillmount
// Framework
import React, { PureComponent } from "react";
// Components
import Page from "../components/Page.jsx";
import Button from "../components/Button.jsx";
class Home extends PureComponent {
constructor(props) {
super(props);
this.state = {
order: null,
error: null
};
}
componentWillMount() {
Meteor.call("orders.getLastOrder", (error, response) => {
if (error) {
this.setState(() => ({ error: error }));
console.log(error);
} else {
this.setState(() => ({ order: response }));
console.log(this.state.order[0].name);
}
});
}
goBack = () => this.props.history.push("/shop");
goCart = () => this.props.history.push("/cart");
render() {
return (
<Page pageTitle="Cart" history goBack={this.goBack} goCart={this.goCart}>
<div className="home-page">
<div>
{this.state.order.map((item, i) => <div key={i}> {item.name}
{item.price} {item.quantity}</div>)}
</div>
<Button
onClick={() => {
this.props.history.push("/shop");
}}
>
Go shopping
</Button>
</div>
</Page>
);
}
}
export default Home;
I am having trouble trying to figure out how to loop through the objects from my state and display them in rows (I'm trying to create a cart)
0:{name: "TEMPOR Top", price: 875.5, quantitiy: 6}
1:{name: "CONSECTETUR Coat", price: 329.8, quantitiy: 3}
_id:"6RNZustHwbKjQDCYa"
You will still get the extra render but I assume you getting an error on the .map() function?
if you only change your constructor into this:
constructor(props) {
super(props);
this.state = {
order: [],
error: null
};
}
you won't get the error, because you can't use .map on a null object but you can use it on a empty array.

save react component and load later

I have react component in react native app and this will return Smth like this:
constructor(){
...
this.Comp1 = <Component1 ..... >
this.Comp2 = <Component2 ..... >
}
render(){
let Show = null
if(X) Show = this.Comp1
else Show = this.Comp1
return(
{X}
)
}
and both of my Components have an API request inside it ,
so my problem is when condition is changed and this toggle between Components , each time the Components sent a request to to that API to get same result ,
I wanna know how to save constructed Component which they wont send request each time
One of the ways do that is to handle the hide and show inside each of the child component comp1 and comp2
So you will still render both comp1 and comp2 from the parent component but you will pass a prop to each one of them to tell them if they need to show or hide inner content, if show then render the correct component content, else just render empty <Text></Text>
This means both child components exist in parent, and they never get removed, but you control which one should show its own content by the parent component.
So your data is fetched only once.
Check Working example in react js: https://codesandbox.io/s/84p302ryp9
If you checked the console log you will find that fetching is done once for comp1 and comp2.
Also check the same example in react native below:
class Parent extends Component {
constructor(props)
{
super(props);
this.state={
show1 : true //by default comp1 will show
}
}
toggleChild= ()=>{
this.setState({
show1 : !this.state.show1
});
}
render(){
return (
<View >
<Button onPress={this.toggleChild} title="Toggle Child" />
<Comp1 show={this.state.show1} />
<Comp2 show={!this.state.show1} />
</View>
)
}
}
Comp1:
class Comp1 extends Component
{
constructor(props) {
super(props);
this.state={
myData : ""
}
}
componentWillMount(){
console.log("fetching data comp1 once");
this.setState({
myData : "comp 1"
})
}
render(){
return (
this.props.show ? <Text>Actual implementation of Comp1</Text> : <Text></Text>
)
}
}
Comp2:
class Comp2 extends Component {
constructor(props) {
super(props);
this.state = {
myData2: ""
}
}
componentWillMount() {
console.log("fetching data in comp2 once");
this.setState({
myData2: "comp 2"
});
}
render() {
return (
this.props.show ? <Text>Actual implementation of Comp2</Text> : <Text></Text>
)
}
}
I think, you should move all your logic to the main component (fetching and saving data, so you component1 and component2 are simple dumb components. In component1 and component2 you can check "does component have some data?", if there isn't any data, you can trigger request for that data in parent component.
Full working example here: https://codesandbox.io/s/7m8qvwr760
class Articles extends React.Component {
componentDidMount() {
const { fetchData, data } = this.props;
if (data && data.length) return;
fetchData && fetchData();
}
render() {
const { data } = this.props;
return (
<div>
{data && data.map((item, key) => <div key={key}>{item.title}</div>)}
</div>
)
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
news: [],
articles: [],
isNews: false
}
}
fetchArticles = () => {
const self = this;
setTimeout( () => {
console.log('articles requested');
self.setState({
articles: [{title: 'article 1'}, {title: 'articles 2'}]
})
}, 1000)
}
fetchNews = () => {
const self = this;
setTimeout(() => {
console.log('news requested');
self.setState({
news: [{ title: 'news 1' }, { title: 'news 2' }]
})
}, 1000)
}
handleToggle = (e) => {
e.preventDefault();
this.setState({
isNews: !this.state.isNews
})
}
render(){
const { news, articles, isNews} = this.state;
return (
<div>
<a href="#" onClick={this.handleToggle}>Toggle</a>
{isNews? (
<News data={news} fetchData={this.fetchNews} />
): (
<Articles data={articles} fetchData={this.fetchArticles} />
)}
</div>
)
}
}

Resources