React issue with rendering data from firebase - javascript

Basically I have an issue with rendering information got from firebase to the screen.
When I'm trying to call the function which gets the information from the database inside componentDidMount(), the function is not even executed, but when I call it inside the render() function, which I know it's now the right thing to do it works, it goes into an infinite loop and it keeps accessing the database over and over again, but it renders the correct information to the screen. So the function itself is not the issue, I guess, since it is able to retrieve the information from the database.
Also a console.log() inside the componentDidMount() seems to work so componentDidMount() does fire.
So how should I go forward with this issue? I've been struggling with this for several hours now. I can't seem to find the issue.
This is my code:
export default class Cars extends Component {
constructor(){
super();
this.state = {
cars: []
}
}
componentDidMount(){
this.loadCarsFromDB();
}
loadCarsFromDB = () => (
<FirebaseContext.Consumer>
{firebase => {
firebase.accessFirebase("cars").get()
.then(snapshot => {
let cars = [];
snapshot.docs.forEach(doc => {
cars.push(doc.data());
})
return cars;
})
.then(cars => {
this.setState({cars: cars});
})
.catch(err => {
console.log(err);
});
}
}
</FirebaseContext.Consumer>
)
renderCars = () => {
return this.state.cars.map(car => <Car
brandName={car.brandName}
model={car.model}
color={car.color}
price={car.price} />)
}
render() {
return (
<div className="car-item">
{this.renderCars()}
</div>
);
}
}
Firebase class except the credentials
export default class Firebase {
constructor() {
app.initializeApp(config);
}
accessFirebase = () => {
let db = app.firestore();
return db.collection("cars");
}
}
This is the Car function
const car = (props) => (
<div className="Car">
<span>{props.brandName ? props.brandName : "Nu exista"}</span>
<span>{props.model ? props.model : "Nu exista"}</span>
<span>{props.color ? props.color : "Nu exista"}</span>
<span>{props.price ? props.price : "Nu exista"}</span>
</div>
)
export default car;
And this is the index.js file. I don't know, maybe it has something to do with the use of contexts. I basically create only one firebase instance which should allow me to query the database from anywhere in the code by using only this very instance.
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
App.jsx file
class App extends Component {
render(){
return(
<div>
<Cars/>
</div>
)
}
}
export default App;

You are not supposed to use the FirebaseContext.Consumer component from loadCarsFromDB. So I would lift up FirebaseContext.Consumer around Cars and pass down the firebase property as a prop.
class App extends Component {
render(){
return(
<div>
<FirebaseContext.Consumer>
{firebase => (
<Cars firebase={firebase}/>
)
}
<FirebaseContext.Consumer />
</div>
)
}
}
loadCarsFromDB = () => (
this.props.firebase.accessFirebase("cars").get()
.then(snapshot => {
let cars = [];
snapshot.docs.forEach(doc => {
cars.push(doc.data());
})
return cars;
})
.then(cars => {
this.setState({cars: cars});
})
.catch(err => {
console.log(err);
});
)

Related

React js fetch API with componentDidMount()

I am learning React.js and trying to fetch API with fetch() and I tried to use componentDidMount() but I have a problem, you can see the pic at the end of the post.
import React, { Component } from 'react'
export default class App extends Component {
state = {
weather: []
};
fetchData() {
fetch('prevision-meteo.ch/services/json/rochelle-17')
.then((response) => response.json())
.then((obj) => {
console.log('javascript object: ', obj)
this.setState({ weather: obj.results});
})
.catch((error) => {
console.error(error)
})
}
componentDidMount() {
console.log('Le composant App est monté sur le DOM !')
this.fetchData();
}
render() {
return (
<div>
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}>{weatherRecord.city_info.name}</div>
))}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me</button>
</div>
)
}
}
I want to get the name of city_info in my page but didn't work!
This is the results in the console, can anyone help me?
Setting state is asynchronous so React is rendering before the state has been set. What you can do is put in a short circuit condition this.state.weather && to check if the weather array exists and only when it is available will the map operation be performed and you shouldn't get any errors.
import React, { Component } from 'react'
export default class App extends Component {
state = {
weather: []
};
fetchData() {
fetch('http://localhost:3000/rochelle-17.json')
.then((response) => response.json())
.then((obj) => {
//console.log('javascript object: ', obj)
this.setState({ weather: obj.results});
})
}
componentDidMount() {
console.log('Le composant App est monté sur le DOM !')
this.fetchData();
}
render() {
return (
<div>
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}></div>
))}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me</button>
</div>
)
}
}
Some Notes:
The newer versions of React support setting initial state like this, which is a bit cleaner:
state = {
weather: []
}
It's also good practice to catch errors in case the API call fails. You can simply use .catch like this after the last .then():
.catch((error) => {
console.error(error)
})
Since ES6 you don't need to use return for the <div> you are rendering. You can simply use map with curved brackets () instead of curly brackets {} to implicitly return
{this.state.weather&& this.state.weather.map((weatherRecord) => (
<div key={weatherRecord.city_info.name}></div>
))}
Try this
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
wheather: null
};
}
fetchData() {
fetch("http://localhost:3000/rochelle-17.json")
.then((response) => {
return response.json();
})
.then((obj) => {
//console.log('javascript object: ', obj)
this.setState({ wheather: obj });
});
}
componentDidMount() {
console.log("Le composant App est monté sur le DOM !");
this.fetchData();
}
render() {
return (
<div>
{this.state.wheather
&& <div key={this.state.wheather.city_info.name}>{this.state.wheather.city_info.name}</div>
}
Hello World !
<button /*onClick={() => this.fetchData()}*/> Click me !</button>
</div>
)
}
}

TypeError: projects.map is not a function (React)

Components
const Pcards = ({ projects }) => {
return (
<div>
<CardColumns>
{projects.map((projects) => (
<Card>
<Card.Img variant="top" src={"http://localhost:8000" + projects.images[0].file_path + projects.images[0].file_name + projects.images[0].file_type} />
Pages
class Projects extends Component {
state = {
projects:[]
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
this.setState({ projects: data })
})
.catch(console.log)
}
render () {
return (
<Pcards projects = {this.state.projects} />
);
}
}
New to react and this code returns
TypeError: projects.map is not a function
This appears to be compiling just fine on my partner's end since he written this code and I'm trying to expand on his work.
I've seen other similar posts but unable to find a fix. Any idea what's going on here?
You have two mistakes in your Projects class.
1- .catch error handling syntax was wrong
2- you were not checking the fetched data
class Projects extends Component {
state = {
projects: []
}
componentDidMount() {
fetch('http://localhost:5000/api/projects')
.then(res => res.json())
.then((data) => {
if (data && data.length) { // checking the data
this.setState({ projects: data })
} else {
console.log("Projects fetch failed, check your api code!")
}
})
.catch(e => console.log(e)); // corrected error catch
}
render() {
return (
<Pcards projects={this.state.projects} />
);
}
}
You can also edit your Pcards component code. You are already using a property called projects and you are mapping it, calling the argument projects too. That is not a good practice. If you are mapping projects name the item as project or projectItem.
projects.map((project) => ...
Try
class Projects extends Component {
constructor(props){
super(props)
this.state = {
projects:[]
}
}

Can't render parent props

I'm making a React app using openweathermap API. Right now I receive the list of weather data. I'm trying to highlight the weather if I click it.
To make this happen, I wrote on App.js to pass a prop to WeatherDetail.js, but so far seems like WeatherDetail.js doesn't recognize props from its parent.
class App extends React.Component {
constructor(props) {
super(props);
}
state = { forecasts: [], selectedWeather: null }
getWeather = async city => {
const response = await weather.get('/forecast', {
params: {
q: city
}
});
this.setState ({
forecasts: response.data.list,
city: response.data.city.name,
selectedWeather: response.data.list[0]
})
}
}
onWeatherSelectFunction = (item) => {
this.setState({ selectedWeather: item });
};
render() {
return (
<div>
<Form loadWeather={this.getWeather} />
<WeatherDetail itemToChild={this.state.selectedWeather} />
<WeatherList
onWeatherSelect={this.onWeatherSelectFunction}
weathers={this.state.forecasts}
city={this.state.city}
/>
</div>
);
}
}
export default App;
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
const WeatherItem = ({item, onWeatherSelectFromList, humidity, city, temp }) => {
return (
<div>
<div onClick={() => onWeatherSelectFromList(item)} >
{city}<br /> <-- Appears on screen
{humidity}<br /> <-- Appears on screen
</div>
</div>
);
};
const WeatherList = ({weathers, onWeatherSelect, city}) => {
const renderedList = weathers.map((item) => {
return (
<div>
<WeatherItem
city={city}
temp={item.main.temp}
humidity={item.main.humidity}
temperature={item.weather.icon}
onWeatherSelectFromList={onWeatherSelect}
/>
</div>
);
});
return (
<div className="flex">
{renderedList}
</div>
);
}
class Form extends React.Component {
state = { term: '' };
onFormSubmit = (event) => {
event.preventDefault();
this.props.loadWeather(this.state.term);
}
render() {
return (
<div>
<form onSubmit={this.onFormSubmit}>
<input
ref="textInput"
type="text"
value={this.state.term}
onChange={event => this.setState({term: event.target.value})}
/>
<button>Get Weather</button>
</form>
</div>
);
}
}
How do I connect App.js and WeatherDetail.js using props?
In your App.js file you are passing only one props called itemToChild
<WeatherDetail itemToChild={this.state.selectedWeather} />
In your WeatherDetail file from where you're getting forecasts? do you get forecasts from redux store?
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
change your code with this.
const WeatherDetail = (props) => {
console.log("props.itemToChild", props.itemToChild) // check this console that do you get data as you want.
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
You have already destructured the props so there is no need to mention props in WeatherDetail component
and also there is an extra parenthesis after the return statement you should remove that also...
Old:
const WeatherDetail = ({forecasts, itemToChild}, props) => {
const weather = props.itemToChild;
if(!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div> <-- This doesn't appear on screen
);
}
New:
const WeatherDetail = ({ forecasts, itemToChild }) => {
const weather = itemToChild;
if (!weather) {
return <div>Loading...</div>;
}
return <div>{weather.humidity}</div>;
};

React append component programmatically

I want to create a react component instance and render it in a static place programmatically.
My use-case is that I open a sequence of dialogs in an unknown length and when I get a response from a dialog I open the next.
I want to do something like:
const DialogExample = () => ({ question, onAnswer }) =>
(<div>
{question}
<button onClick={onAnswer}>answer</button>
</div>);
class SomeComponent extends React.Component {
async start() {
const questions = await getSomeDynamicQuestions();
this.ask(questions);
}
ask(questions) {
if (questions.length === 0) {
// DONE.. (do something here)
return;
}
const current = questions.pop();
React.magicMethod(
// The component I want to append:
<DialogExample
question={current}
onAnswer={() => this.ask(questions)}
/>,
// Where I want to append it:
document.getElementsByTagName('body')[0]);
}
render() {
return (
<div>
<button onClick={this.start}>start</button>
</div>);
}
}
I know that's not very "react-like", and I guess the "right" way of doing it will be storing those questions in state and iterate over them in "someComponent" (or other) render function, but still, I think that this pattern can make sense in my specific need.
Sounds like a case for Portals. I'd recommend doing something like this:
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.body = document.getElementsByTagName('body')[0];
this.state = {
questions: [],
}
}
async start() {
const questions = await getSomeDynamicQuestions();
this.setState({ questions });
}
nextQuestion() {
this.setState(oldState => {
const [first, ...rest] = oldState.questions;
return { questions: rest };
})
}
render() {
const { questions } = this.state;
return (
<div>
<button onClick={this.start}>start</button>
{questions.length > 0 && ReactDOM.createPortal(
<DialogExample
question={questions[0]}
onAnswer={() => this.nextQuestion()}
/>,
this.body,
)}
</div>
);
}
}

Access variable returned from react function within render (in loop)

I have a react class component, where I am passing list of module names and some other attributes. I tried to obtain module code by calling a function getModuleCode() and passing module name as a parameter.
class ModulesListing extends React.Component {
getModuleCode(module){
var moduleCode = 0;
// Do relevant calls and get moduleCode
return moduleCode;
}
render(){
var {modulesList} = this.props;
modulesList.forEach(module => {
//here I need to call getModuleCode as getModuleCode(module.name)
var moduleCode = getModuleCode(module.name)
console.log(moduleCode)
})
return (
//Info to display
);
}
}
If I tried as above mentioned, it prints as undefined
Also tried with setting as state, which is not suitable for looping. Here what I wanted is to get module code wrt certain module.
You could get the codes in componentDidMount and in componentDidUpdate if the modulesList change, and store them in state.
Example
function doCall() {
return new Promise(resolve =>
setTimeout(() => resolve(Math.random().toString()), 1000)
);
}
class ModulesListing extends React.Component {
state = { codes: [] };
componentDidMount() {
this.getModuleCodes();
}
componentDidUpdate(prevProps) {
if (this.props.modulesList !== prevProps.modulesList) {
this.setState({ codes: [] }, this.getModuleCodes);
}
}
getModuleCodes = () => {
Promise.all(this.props.modulesList.map(doCall)).then(codes => {
this.setState({ codes });
});
};
render() {
const { modulesList } = this.props;
const { codes } = this.state;
if (codes.length === 0) {
return null;
}
return (
<div>
{modulesList.map((module, index) => (
<div>
{module}: {codes[index]}
</div>
))}
</div>
);
}
}
ReactDOM.render(
<ModulesListing modulesList={["a", "b", "c"]} />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories