I'm trying to pass some Firebase data down from one component via props to another component, but it doesn't seem to be letting me iterate over the Firebase data in the child component.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
games: []
};
}
componentDidMount() {
const gamesRef = firebase.database().ref('games').orderByKey();
gamesRef.once('value', snap => {
snap.forEach((childSnapshot) => {
this.state.games.push(childSnapshot.val());
})
})
}
render() {
return (
<div className="App">
<Games data={ this.state.games } />
</div>
);
}
}
Games.js
class Games extends Component {
componentDidMount() {
console.log(this.props.data); // this logs successfully
}
render() {
return (
<div className="container">
<div className="Games flex flex-end flex-wrap">
{ this.props.data.map(function (game, i) {
return (
<h1>{ game.title }</h1>
)
}) }
</div>
</div>
);
}
}
For some reason I'm having a problem when trying to map() over my props.data. It's definitely being passed down to my Games component, because it's printing the console.log(this.props.data) to the console with the data it gets back from Firebase.
Do I have to wait for my Firebase data to resolve before mapping over it, and if so how do I do this?
Any help is appreciated. Thanks in advance!
I think the problem lies with your componentDidMount in your App class. You're updating state with
this.state.games.push(childSnapshot.val());
You shouldn't do that. State should only be updated with this.setState (or at the very least you should use this.forceUpdate()), as otherwise it will not re-render. I would instead advise doing
componentDidMount() {
const gamesRef = firebase.database().ref('games').orderByKey();
let newGames;
gamesRef.once('value', snap => {
snap.forEach((childSnapshot) => {
newGames.push(childSnapshot.val());
})
})
this.setState({games: newGames});
}
This will cause a re-render of the App component, causing the new data to be passed as a prop to the Games component.
Related
So I'm a beginner with react and I was wondering how to re-render the child after setting the state in the parent (from the child). Here's a code sample. I have a function that calls a GET request using Axios and when I press the button in the child component ideally it will update the state in the parent and also re-render the child but it only does the former.
Parent:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
fetchData = () => {
axios
.get(url)
.then(res => this.setState({data: res.data}))
}
Render() {
return (<Child data={this.state.data} fetchData={this.fecthData}/>)
}
// ...
Child:
class Child extends Component {
// ...
render() {
const { data, fetchData } = this.props
// render data
return <button onClick={fetchData}>Change data then fetch</button>
}
}
Also, are you supposed to make a local state in the Child and set it as a copy of the Parent's state or just passing it down as a prop is okay?
Your parent component holds the data and the child uses it. It seems to me you're doing it the right way. Here is a fully working example:
Codesandbox
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
this.updateData = this.updateData.bind(this);
}
async fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
return response.json();
}
updateData() {
this.setState({ data: [] }) // Creates a flicker, just so you see it does refresh the child
this.fetchData().then((res) => this.setState({ data: res }));
}
render() {
return <Child data={this.state.data} onAction={this.updateData} />;
}
}
Note I renamed your child prop fetchData into onAction (I don't know what's the name of the action that triggers a refresh, could be onRefresh). It's always best to see components props with separation between data attributes and event attributes.
Even standard components have it this way: <input value={user.firstname} onChange={doSomething} />. So, better to prefix events by on, then the parent decides what to do with it. It's not the child's concern.
class Child extends Component {
render() {
const { data, onAction } = this.props;
return (
<>
<button onClick={onAction}>Change data then fetch</button>
{data.map((item) => (
<div key={item.id}>
{item.id} - {item.title}
</div>
))}
</>
);
}
}
I am having a bit of an issue rendering components before the state is set to the data from a returned asynchronous API request. I have a fetch() method that fires off, returns data from an API, and then sets the state to this data. Here is that block of code that handles this:
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi.USD.rate);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
You will notice the console.log('API CALL', currentPrice.bpi.USD.rate) that I use to check if the API data is being returned, and it absolutely is. currentPrice.bpi.USD.rate returns an integer (2345.55 for example) right in the console as expected.
Great, so then I assumed that
this.setState = ({ currentPrice: currentPrice.bpi.USD.rate }) should set the state without an issue, since this data was received back successfully.
So I now render the components like so:
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
With this, I was expecting to be able to access this data in my PriceOverview.js component like so: this.props.data
I have used console.log() to check this.props.data inside my PriceOverview.js component, and I am getting 'null' back as that is the default I set intially. The issue I am having is that the components render before the API fetch has ran it's course and updated the state with the returned data. So when App.js renders the PriceOverview.js component, it only passes currentPrice: null to it, because the asynchronous fetch() has not returned the data prior to rendering.
My confusion lies with this.setState. I have read that React will call render any time this.setState is called. So in my mind, once the fetch() request comes back, it calls this.setState and changes the state to the returned data. This in turn should cause a re-render and the new state data should be available. I would be lying if I didn't say I was confused here. I was assuming that once the fetch() returned, it would update the state with the requested data, and then that would trigger a re-render.
There has to be something obvious that I am missing here, but my inexperience leaves me alone.. cold.. in the dark throws of despair. I don't have an issue working with 'hard coded' data, as I can pass that around just fine without worry of when it returns. For example, if I set the state in App.js to this.state = { currentPrice: [254.55] }, then I can access it in PriceOverview.js via this.props.data with zero issue. It's the async API request that is getting me here, and I am afraid it has gotten the best of me tonight.
Here App.js in full:
import React, { Component } from 'react';
import './components/css/App.css';
import NavigationBar from './components/NavigationBar';
import PriceOverview from './components/PriceOverview';
class App extends Component {
constructor() {
super();
this.state = {
currentPrice: null,
};
}
componentDidMount() {
const getCurrentPrice = () => {
const url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
fetch(url).then(data => data.json())
.then(currentPrice => {
this.setState = ({
currentPrice: currentPrice.bpi.USD.rate
})
console.log('API CALL', currentPrice.bpi);
}).catch((error) => {
console.log(error);
})
}
getCurrentPrice();
}
render() {
return (
<div>
<NavigationBar />
<PriceOverview data={this.state.currentPrice}/>
</div>
);
}
}
export default App;
Here is PriceOverview.js in full:
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
currentPrice: this.props.data
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Thank you in advance to any help, it's much appreciated.
this.setState ({
currentPrice: currentPrice.bpi.USD.rate
})
Do not put an = in this.setState
Ok First thing, when you're writting code on React the components that hold state are the class base components so ... What I see here is that you're creating two class base components so when you pass down props from your app class component to your PriceOverview wich is another class base component you're essentially doing nothing... Because when your constructor on your PriceOverview get call you're creating a new state on that Component and the previous state ( that's is the one you want to pass down) is being overwritten and that's why you're seem null when you want to display it. So it should work if you just change your PriveOverview component to a function base component ( or a dumb component). So this way when you pass down the state via props, you're displaying the correct state inside of your div. This is how would look like.
import React from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
const PriceOverview = (data) => {
return (
<div className="overviewBar">
<div className="currentPrice panel">
//Im calling data here because that's the name you gave it as ref
//No need to use 'this.props' you only use that to pass down props
{data != null ? <div className="price">
{data}</div> : <div className="price">Loading...</div>
}
</div>
</div>
)
}
}
export default PriceOverview;
Whenever you're writing new components start always with function base components if you component is just returning markup in it and you need to pass some data go to his parent component update it (making the api calls there or setting the state there) and pass down the props you want to render via ref. Read the React docs as much as you can, hope this explanation was useful (my apologies in advance if you don't understand quite well 'cause of my grammar I've to work on that)
The thing is constructor of any JS class is called only once. It is the render method that is called whenever you call this.setState.
So basically you are setting currentPrice to null for once and all in constructor and then accessing it using state so it will always be null.
Better approch would be using props.
You can do something like this in your PriceOverview.js.
import React, { Component } from 'react';
import './css/PriceOverview.css';
import bitcoinLogo from './assets/bitcoin.svg';
class PriceOverview extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.props.data!= null ? <div className="price">{this.props.data}</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
export default PriceOverview;
Or you can use react lifecycle method componentWillReceiveProps to update the state of PriceOverview.js
componentWillReceiveProps(nextProps) {
this.setState({
currentPrice:nextProps.data
});
}
render() {
return (
<div className="overviewBar">
<div className="currentPrice panel">
{ this.state.currentPrice != null ? <div className="price">{this.state.currentPrice }</div> : <div className="price">Loading...</div> }
</div>
</div>
)
}
}
I want to fetch JSON data, store it in state and then pass it to component through props. In the component I want to use the map function but it shows me this error :
TypeError: this.props.dataQueries.map is not a function.
This is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataQueries: ''
}
}
fetchData() {
fetch('https://jsonplaceholder.typicode.com/posts', {method: "GET"}).
then(res => res.json()).
then(result => this.setState({ dataQueries: result }));
}
componentWillMount() {
this.fetchData();
}
render() {
return (
<div>
<ShowPosts dataQueries={ this.state.dataQueries } />
</div>
);
}
}
And this is my component :
class ShowPosts extends Component {
render() {
return (
<div>
{
this.props.dataQueries.map((query, index) => {
return index;
})
}
</div>
);
}
}
Initially, you're setting dataQueries to ''. Which has no map, as it's a string. It's only an array later, when your fetchData async call has completed. Nothing prevents render from being called before the async call completes.
Initialize it to [] instead, or modify render to avoid trying to use it as an array when it's not an array.
You should call this.fetchData() inside componentDidMount() lifecycle method. So when component is mounted only then you update the state with response from API. Also, you should render ShowPosts component when there are posts to render using conditional rendering.
render() {
return (
<div>
{this.state.dataQueries.length && <ShowPosts dataQueries={ this.state.dataQueries } />}
</div>
);
}
And your initial dataQueries should be an empty array. dataQueries = []
I'm learning react, and have an application that used to work with static data stored in a javascript object. I am now loading that data via ajax using axios.
This works, and I am currently storing that data in the app's state, then passing it down to the components once the data is loaded, but the way I'm passing the data as a prop to each component doesn't feel right.
How can each component access the parent app's data without passing it as a prop to each component?
Here's my code
class App extends Component {
constructor(props) {
super(props);
this.state = {appData: {}};
}
componentDidMount() {
axios.get('/data/appData.json')
.then((result)=> {
const thisData = result.data;
this.setState({
appData: result.data
});
})
}
componentWillUnmount() {
this.serverRequest.abort();
}
render() {
const theData = this.state.appData;
if (Object.keys(theData).length > 0 && theData.constructor === Object){ //if the object is not empty
return (
<div className="App">
<AppHeader appData={theData} />
<AppMenu appData={theData} />
<MainCarousel appData={theData} />
<HomeDetails appData={theData} />
<Model3D appData={theData} />
<AppMaps appData={theData} />
<AppContact appData={theData} />
</div>
);
} else {
return (
<div className="App"></div>
)
}
}
}
And a component that would use the data looks like:
function AppHeader(props) {
return (
<div className="App-header">
<h2 className="App-title">{props.appData.copy.title}</h2>
<h4 className="App-subtitle">{props.appData.copy.subtitle}</h4>
</div>
);
}
for a function, or
class MainCarousel extends Component {
mixins: [Carousel.ControllerMixin];
constructor(props) {
super(props);
}
render() {
const carouselItems = this.props.appData.carouselItems.map((carouselItem) =>
<AppCarouselItem key={carouselItem.name.toLowerCase()} name={carouselItem.name} image={carouselItem.image} />
);
return (
<div className="App-carousel">
<Carousel autoplay={true} wrapAround={true}>
{carouselItems}
</Carousel>
</div>
);
}
}
for a class.
For your purposes what you are doing is completely acceptable, the only thing I would change is to split out the state into an object for each component. This will stop every component from updating each time you update a single one of them.
Where things get messy is when your child components are updating the parent's state. This is where a library like Flux or Redux comes in handy.
If you are just creating a simple app with static data keep sending state to your component as props.
My node.js server sends with socket.io new data each 10s. In my web application I update this.state each time that my server sends data and force to update with forceUpdate()
However, my react component doesn't refresh, I don't know why. I followed the doc but I missed something...
Parent :
class DataAnalytics extends React.Component {
constructor(props) {
super(props);
socket = this.props.socket;
this.state = {data: []};
socket.on('dataCharts', (res) => {
console.log("new data charts : "+res);
var data = JSON.parse(res);
this.setState({data: data});
this.forceUpdate();
});
}
componentWillUnmount() {
socket.off('dataCharts');
}
render() {
return (
<div id="dataAnalytics">
<Stats data={this.state.data}></Stats>
</div>
);
}
}
export default DataAnalytics;
Child :
class Stats extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="stats" style={{textAlign:'center'}}>
<h4>Number: </h4>
</div>
);
}
componentDidUpdate() {
var data = this.props.data;
if(!jQuery.isEmptyObject(data)) {
$( ".stats" ).html("<h4>Number : data['nb']['counterIn']</h4>");
}
}
}
export default Stats;
Anyone know how to refresh automatically my React component.
The React component doesn't update because it doesn't realize that it's state changes. You can force an update on a React component by creating it each time with a different key attribute.
render() {
return (
<div id="dataAnalytics">
<Stats key={this.uniqueId()} data={this.state.data}></Stats>
</div>
);
}
// Example of a function that generates a unique ID each time
uniqueId: function () {
return new Date().getTime();
}
I usually do it like -
function MyComponent() {
const [_, refresh] = useState()
useEffect(() => {
// Code that's supposed to run on refresh
}, [refresh])
return
<>
{/* Rest of the code */}
<button onclick={() => refresh(true)}>Refresh</button>
</>
}
The idea is to define a state and use it as a dependency of useEffects (or useMemos and useCallbacks).
If there are multiple effect hooks, add refresh to all of them as a dependency.