I've been creating some React Apps using local data to populate Components etc and all is working fine. Im now at the point where i want to host this data externally (Firebase) and consume it within my apps.
My issue may be a general 'Working With React and External Data' kinda thing but in a basic way...
I get my data from Firebase
When component has mounted
I use setState to pass this external data to the component state
I expect my components to rerender with this new external data
Unfortunately my page isnt updating with the new Firebase data, it just uses the Initial state i am setting
I wondered if using componentWillMount() was a better area to assign the Firebase data so it is ready before the first render but it seems this approach is now deprecated
Is there something obviously wrong with the way i am getting, setting or passing my Firebase data from one Parent component to a Child component?
Thanks
Parent component
import './App.scss';
import DataThoughts from "./assets/data/DataThoughts";
import Thoughts from "./components/Thoughts";
import HideHeader from './components/HideHeader';
import HideFooter from './components/HideFooter';
import FireBaseThoughts from './components/firebaseThoughts';
import {onValue, ref} from "firebase/database";
import {Component} from "react";
class AppThoughts extends Component {
constructor(props) {
super(props);
this.state = {
"data": [
{
"Date": "Initial Date",
"Thought": "Initial Thought",
"Author": "Initial Author",
"Location": "Initial Location",
"Photo": "Initial Photo",
"Emotion": "Initial Emotion"
}
]};
};
componentDidMount() {
onValue(ref(FireBaseThoughts, '/'), (snapshot) => {
const data = snapshot.val();
this.setState({data});
});
}
render() {
const data = this.state.data;
return (
<div className="App">
<HideHeader />
<HideFooter />
<main className={'bg-light'}>
<div className={'container-fluid'}>
<Thoughts data={data} />
</div>
</main>
</div>
);
}
}
export default AppThoughts;
Child component
import React, { Component } from 'react';
import FilterButton from "./FilterButton";
class Thoughts extends Component {
constructor(props) {
super(props);
this.state = {...props};
}
handleClick = value => () => {
// set to initial data
this.setState({ ...this.props });
if(value === "All") {
// Do nothing
} else {
// filter the initial data
let filtered = this.props.data.filter(item => item.Emotion === value);
this.setState({ data: filtered });
}
};
render() {
let data = this.state.data;
let numberOfThoughts = data.length;
let dataList = this.state.data.map((thought, i) =>
<div key={'thought'+i} className={`card thought mb-5 ${thought.Photo ? '' : 'bg-transparent shadow-0'} animate__animated animate__fadeIn`} style={{animationDelay:`${(i / 10)}s`}}>
{thought.Photo ? <img src={thought.Photo} className="card-img-top" alt={thought.Emotion}/> : ''}
<div className={`${thought.Photo ? 'p-5' : 'p-5'}`}>
<blockquote className={`blockquote mb-0 ${thought.Photo ? '' : 'text-danger'}`}>
<p className={'small opacity-50'}>{thought.Date}</p>
<p className={`${thought.Photo ? 'display-6' : ' display-4'} mb-4`}>{thought.Thought}</p>
<footer className="small opacity-50">{thought.Author}, <cite title="Source Title">{thought.Location}</cite></footer>
</blockquote>
</div>
</div>
);
return (
<section className="row section-row justify-content-start thoughts py-5">
<div className={'card-columns'}>
{dataList}
</div>
<div className={'appControlsInfo'}>
<span className={'appControlsInfo__items'}>{numberOfThoughts}</span>
<button type="button" className="btn btn-danger appControlsInfo__btn" data-bs-toggle="collapse" data-bs-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
<i className="bi bi-list"></i>
</button>
<div className={'appControlsInfo__more collapse'} id="collapseExample">
<FilterButton buttonText={"All"} buttonType={'btn-dark'} onClick={this.handleClick('All')} />
<FilterButton buttonText={"Happy"} buttonType={'btn-dark'} onClick={this.handleClick('Happy')} />
<FilterButton buttonText={"Sad"} buttonType={'btn-dark'} onClick={this.handleClick('Sad')} />
<FilterButton buttonText={"Thinking"} buttonType={'btn-dark'} onClick={this.handleClick('Thinking')} />
</div>
</div>
</section>
);
}
}
Thoughts.defaultProps = {
Photo: '',
Emotion:'Emotion',
Date:'Date',
Thought:'Thought',
Author:'Author',
Location:'Location'
};
export default Thoughts; // Don’t forget to use export default!
Related
Here's the code for Panel
`
import React from "react";
// import {render} from "react-dom";
import AddInventory from "components/AddInventory";
class Panel extends React.Component{
constructor(props) {
super(props);
this.state = {
activeIndex: ''
}
}
componentDidMount() {
this.activePanel();
}
closePanel=()=>{
this.setState({
activeIndex : false
})
}
activePanel = ()=>{
this.setState({
activeIndex : true
})
}
render(){
return(
<div>
{/*<button className={"button is-primary add-btn"} onClick={this.activePanel}>add</button>*/}
<div className={this.state.activeIndex ? 'panel-wrapper active':'panel-wrapper'}>
<div className={"over-layer"}>
<div className={"panel"}>
<div className={"head"}>
<span onClick={this.closePanel} className={"close"}>x</span>
<AddInventory></AddInventory>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default Panel;
Products:
import React from "react";
import ToolBox from "components/ToolBox";
import Product from "components/Product";
import axios from 'components/axios'
import {CSSTransition , TransitionGroup} from 'react-transition-group'
import Panel from "components/Panel";
class Products extends React.Component{
product =[];
source =[];
state ={
product : [{
id:'1',
name:'Air Jordan1',
tags:'45 colours',
image:'images/1.jpg',
price:'21000',
status:'available'
},
{
id:'2',
name:'Nike Pual George PG 3',
tags:'45 colours',
image:'images/2.jpg',
price:'11000',
status:'available'
},
{
id:'3',
name:'Jordan Why Not Zer0.2',
tags:'10 colours',
image:'images/3.jpg',
price:'15000',
status:'unavailable'
},
]
}
componentDidMount() {
// fetch('http://localhost:3003/products').then(response => response.json()).then( data=>{
// console.log(data)
// this.setState({
// product : data
// })
// })
axios.get('/products').then(response => {
this.setState( {
product : response.data,
source : response.data
})
})
}
search = text=>{
//1.get a new array from product
let _product = [...this.state.source]
//2.filter the array
let res = _product.filter((element)=>{
return element.name.toLowerCase().includes(text.toLowerCase())
})
//set state
this.setState({
product : res
})
}
add = ()=>{
let panel = new Panel(this.props)
panel.activePanel()
}
// add =()=>{
// panel.setState({
// activeIndex : true
// })
// }
render() {
return(
<div>
<ToolBox search={this.search}/>
<div className={'products'}>
<div className="columns is-multiline is-desktop">
<TransitionGroup component={null}>
{
this.state.product.map(p=>{
return (
<CSSTransition
timeout={400}
classNames="product-fade"
key={p.id}
>
<div className="column is-3" key={p.id}>
<Product product={p}/>
</div>
</CSSTransition>
)
})
}</TransitionGroup>
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
{/*<div className="column is-3">*/}
{/* <Product/>*/}
{/*</div>*/}
</div>
<button className={"button is-primary add-btn"} onClick={this.add}></button>
</div>
</div>
)
}
}
export default Products;
I was trynna use activePanel() in Products but it gives me : Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign tothis.statedirectly or define astate = {};` class property with the desired state in the Panel component.
I tried initialize a new panel() but it still gives me the same error.
welcome. I don't think this approach is best practice. Generally, components should only ever be updating their own state (see here) and typically you want data to flow from parent component to child component (see here). Additionally, your design is deceptive. When you render a component, you declare it as JSX in some render (or return) statement. But here, Panel is never formally instantiated in JSX.
In Panel, I would suggest watching a prop such as active via shouldComponentUpdate and updating state based on changes to that prop. Then in Products you can instantiate an instance of Panel in JSX and dynamically set the value of that prop.
I have trouble with simple task of adding elements selected in checkboxes to an array in component state. It seems like the push method for state.toppings (Editor.js) is invoked twice for each checkbox click, even though console.log shows that updateFormValueCheck method is invoked once per click. Can anyone help?
This is App.js
import React, { Component } from "react";
import { Editor } from "./Editor";
import { Display } from "./Display";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
formData: {}
}
}
submitData = (newData) => {
console.log("newData", newData)
this.setState({ formData: newData });
}
render() {
return <div className="container-fluid">
<div className="row p-2">
<div className="col-6">
<Editor submit={this.submitData} />
</div>
<div className="col-6">
<Display data={this.state.formData} />
</div>
</div>
</div>
}
}
This is Editor.js
import React, { Component } from "react";
export class Editor extends Component {
constructor(props) {
super(props);
this.state = {
toppings: ["Strawberries"]
}
this.toppings = ["Sprinkles", "Fudge Sauce",
"Strawberries", "Maple Syrup"]
}
updateFormValueCheck = (event) => {
event.persist();
this.setState(state => {
if (event.target.checked) {
state.toppings.push(event.target.name);
} else {
let index = state.toppings.indexOf(event.target.name);
state.toppings.splice(index, 1);
}
}, () => this.props.submit(this.state));
}
render() {
return <div className="h5 bg-info text-white p-2">
<div className="form-group">
<label>Ice Cream Toppings</label>
{this.toppings.map(top =>
<div className="form-check" key={top}>
<input className="form-check-input"
type="checkbox" name={top}
value={this.state[top]}
checked={this.state.toppings.indexOf(top) > -1}
onChange={this.updateFormValueCheck} />
<label className="form-check-label">{top}</label>
</div>
)}
</div>
</div>
}
}
This is Display.js
import React, { Component } from "react";
export class Display extends Component {
formatValue = (data) => Array.isArray(data)
? data.join(", ") : data.toString();
render() {
let keys = Object.keys(this.props.data);
if (keys.length === 0) {
return <div className="h5 bg-secondary p-2 text-white">
No Data
</div>
} else {
return <div className="container-fluid bg-secondary p-2">
{keys.map(key =>
<div key={key} className="row h5 text-white">
<div className="col">{key}:</div>
<div className="col">
{this.formatValue(this.props.data[key])}
</div>
</div>
)}
</div>
}
}
}
The output is:
You cannot directly mutate this.state, it can only be done using this.setState. For more info. refer this: Why can't I directly modify a component's state, really?
Therefore, you need to update your Editor component as follows.
componentDidMount is used to display the initial state during the initial rendering. Then componentDidUpdate is used to render the state changes through display component whenever it's updated.
import React, { Component } from "react";
export class Editor extends Component {
constructor(props) {
super(props);
this.state = {
toppings: ["Strawberries"],
};
this.toppings = ["Sprinkles", "Fudge Sauce", "Strawberries", "Maple Syrup"];
}
updateFormValueCheck = (event) => {
event.persist();
let data;
if (event.target.checked) {
data = [...this.state.toppings, event.target.name];
} else {
const index = this.state.toppings.indexOf(event.target.name);
const temp = [...this.state.toppings];
temp.splice(index, 1);
data = temp;
}
this.setState({
toppings: data,
});
};
componentDidMount() {
this.props.submit(this.state.toppings);
}
componentDidUpdate(prevPros, prevState) {
if (prevState.toppings !== this.state.toppings) {
this.props.submit(this.state.toppings);
}
}
render() {
console.log(this.state);
return (
<div className="h5 bg-info text-white p-2">
<div className="form-group">
<label>Ice Cream Toppings</label>
{this.toppings.map((top) => (
<div className="form-check" key={top}>
<input
className="form-check-input"
type="checkbox"
name={top}
value={this.state[top]}
checked={this.state.toppings.indexOf(top) > -1}
onChange={this.updateFormValueCheck}
/>
<label className="form-check-label">{top}</label>
</div>
))}
</div>
</div>
);
}
}
Hope this would be helpful to solve your issue.
I have an array containing objects with details for an image (URL, description, likes) I'm trying to clone an Instagram page, and update the "likes" for that 1 image on click.
Attempted to map through the array and return with the "likes" + 1.
Here are 3 separate files starting with the data. The data is stored in the "Main" section in the state Gallery. So to overview, I want to increase the number of likes when I click on that image. But when I setState, I have no idea how I can only target one value in one object of the array. I would rather just update the state rather than create a new state onClick and then change the value that was! I'm looking for the best practice. (as this is the only way I can learn) Thanks in advance.
const images =[
{
url:'./images/img1.jpg',
description:"test1",
likes:0,
index:0
},
{
url:'./images/img2.jpg',
description:"test1",
likes:3,
index:1
},
{
url:'./images/img3.jpg',
description:"test1",
likes:4,
index:2
},
{
url:'./images/img2.jpg',
description:"test1"
},
{
url:'./images/img2.jpg',
description:"test1"
},
{
url:'./images/img2.jpg',
description:"test1"
},
]
export default images
import React from 'react'
const Gallery =(props)=>{
return (
<div className="container">
<div className="main-gallery">
{props.gallery.map((item,index) => (
<div key={index} className='img-container' onClick= {props.increaseLikes}>
<img className='gallery-images' src={item.url}/>
<p className='likes'>likes {item.likes}</p>
</div>
))}
</div>
</div>
)
}
export default Gallery
import React, { Component } from "react";
import ReactDOM from 'react-dom';
import Nav from './Components/Navbar/Nav'
import Header from './Components/Header/Header'
import Carousel from './Components/Carousel/Carousel'
import Data from './Data'
import Gallery from './Components/Gallery/Gallery'
class Main extends Component {
constructor(props) {
super(props);
this.state={
post:100,
gallery:[],
}
}
componentDidMount(){
this.setState({
gallery:Data
})
}
increaseLikes=()=>{
//no idea how to update
}
render() {
return (
<div>
<Gallery gallery={this.state.gallery} increaseLikes= {this.increaseLikes}/>
</div>
)
}
}
export default Main;
Your increaseLikes function needs to get id of the image from the Gallery component.
So the code must be like something like this:
I assumed your data has an unique id property.
increaseLikes = id => {
const updatedData = this.state.gallery.map(image => {
if (image.id === id) {
return { ...image, likes: image.likes ? image.likes + 1 : 1 };
} else {
return image;
}
});
this.setState({
gallery: updatedData
})
};
Gallery component code:
import React from "react";
const Gallery = props => {
return (
<div className="container">
<div className="main-gallery">
{props.gallery.map((item, index) => (
<div
key={item.id}
className="img-container"
onClick={() => props.increaseLikes(item.id)}
>
<img
className="gallery-images"
src={item.url}
alt={item.description}
/>
<p className="likes">likes {item.likes ? item.likes : 0} </p>
<hr />
</div>
))}
</div>
</div>
);
};
export default Gallery;
you could use the url (That seems to be the only unique value) of the images in order to update your array, I've made a StackBlitz where you can see how to do it. Hope this helps.
I was working on a forum project,Ii used firestore as backend database, react, and redux.
I have an issue whenever someone post a comment on new post with no comment, it does not show, but after refresh is appears, all comments after that appears normally.
github https://github.com/nikhilb2/Forum
deployment http://mariosforum.surge.sh/signin
Can anyone please help me.
import React, { Component } from "react";
import { postComment } from "../../store/actions/projectActions";
import { connect } from "react-redux";
import moment from "moment";
class Comment extends Component {
constructor(props) {
super(props);
this.state = {
comment: "",
authorId: "",
projectId: ""
};
this.handleContent = this.handleContent.bind(this);
this.handlePost = this.handlePost.bind(this);
}
handleContent(e) {
this.setState({
comment: e.target.value,
projectId: this.props.projectId,
authorId: this.props.auth.uid
});
}
handlePost() {
this.props.postComment(this.state);
this.refs.comment.value = "";
}
render() {
const { user, project, state } = this.props;
console.log(`user`);
console.log(this.props);
return user ? (
<div className="container">
{project &&
project.comment &&
Array.isArray(project.comment) &&
project.comment.map(comment => {
const authorId = comment.authorId;
//console.log(user[authorId]);
//console.log(authorId)
return (
<div className="container project-details">
<div className="card z-depth-0">
<div className="card-content">
{comment.comment}
<div className="card-action grey lighten-4 grey-text">
{user[authorId] ? user[authorId].firstName : authorId}{" "}
{user[authorId] ? user[authorId].lastName : authorId}
<div>
{comment.time
? moment(comment.time.toDate()).calendar()
: authorId}
</div>
</div>
</div>
</div>
</div>
);
})}
<div className="card z-depth-0">
<div className="card-content">
<div className="input-field">
<label htmlFor="comment">Type Comment</label>
<textarea
id="comment"
ref="comment"
type="text"
className="materialize-textarea"
onChange={this.handleContent}
/>
</div>
<button
className="btn pink lighten-1 z-depth-0"
onClick={this.handlePost}
>
Post
</button>
</div>
</div>
</div>
) : null;
}
}
const mapDispatchToProps = dispatch => {
return {
postComment: project => dispatch(postComment(project))
};
};
const mapStateToProps = state => {
console.log(state);
return {
state: state
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Comment);
It sounds similar to a problem I had with the same setup. Adding following line to the react-redux-firebase settings in your index.js file might solve the problem:
allowMultipleListeners: true
export const postComment = (project) => {
return(dispatch,getState,{getFirestore}) =>{
const firestore = getFirestore()
console.log(getState())
firestore.collection('projects').doc(project.projectId).update({
comment: firestore.FieldValue.arrayUnion({
comment:project.comment,
authorId:project.authorId,
time: new Date()})
}).then(()=>{
dispatch({
type:'POST_COMMENT',
project
})
}).catch((err)=>{
dispatch({type:'POST_COMMENT_ERROR'})
})
}}
You need to add an action to update redux store for comments, so every time you make a comment it will update redux store
I am new to react. In my project, I have a Home Component.
class Home extends Component {
logOut(){
console.log('log out in side home js');
this.props.LogOutAction();
localStorage.setItem('loginFlag', null);
}
render(){
return(
<div>
<Header />
<br/>
<div className="col-md-12 ">
<div className="col-md-3 home-border">
a
</div>
<div className="col-md-6 home-border">
<User/>
</div>
<div className="col-md-3 home-border">
c
</div>
</div>
</div>
)
}
}
And my User component is
class User extends React.Component {
viewProfile(id){
}
render() {
var userdetails = [ {"id":"1", "name":"Abraham", "country": "USA", "age":"29", "image":""},
{"id":"2", "name":"Gregory", "country": "Canada", "age":"23", "image":""},
{"id":"3", "name":"Mathews", "country": "Newzeland", "age":"24", "image":""},
{"id":"4", "name":"Williamson", "country": "China", "age":"27", "image":""},
{"id":"5", "name":"Edwerd", "country": "Germany", "age":"22", "image":""}
];
var myStyle = {
border:"1px solid black",
height:"170px",
}
return(
<div>
<div>
{userdetails.map((data, i) => <div className="col-md-12 div-bottom" style={myStyle} key={i}>
<div className="col-md-12">
<div className="col-md-12">
<img className="img-thumbnail img-margin" width="150" height="236" src = {'profile.jpg'} />
</div>
<div className="col-md-12 div-bottom"><label>{data.name}</label></div>
<div className="col-md-12 div-bottom">
<div className="col-md-6"><label>age:</label> {data.age}</div>
<div className="col-md-3"><label>Country:</label> {data.country}</div>
</div>
<div className="col-md-4 .div-bottom"><button className="btn btn-primary" onClick={ () => { this.viewProfile(data.id) } }>view Profile</button></div>
</div>
</div>)
}
</div>
</div>
)
}
}
I want to show the more user details in place of 'c' in the Home Component on the button click in the User Component.What is the correct method and how it can done?. Thanks in advance..
There are two approaches. First is to use React only. You create a parent component, containing the state of the app. In the parent component you can create a function that changes the state. You can pass the function to your user component and the state to the home component.
Simplified:
const Home = ({ user }) => <div>{user.name}</div>;
const User = ({ changeUser }) => {
const users = [
{ name: 'User1' },
{ name: 'User2' },
];
const renderUsers = () => users.map(
user => <li onClick={() => changeUser(user)}>{user.name}</li>
);
return <ul>{renderUsers()}</ul>;
};
class Parent extends React.Component {
constructor(props) {
super(props);
this.changeUser = this.changeUser.bind(this);
this.state = { user: null };
}
changeUser(user) {
this.setState({ user });
}
render() {
return (
<div>
<Home user={this.state.user} />
<User changeUser={this.changeUser} />
</div>
);
}
}
However, when your app grows it becomes very complicated to manage the state via a parent component. You can use something like a "Flux" architecture for this. "Redux" is the most common way in the React world for building up Flux like architecture.
What does 'a' stand for?
First of all , who does query you user's data?
I think Home must handle server calls, then you need to store that data in the Home component's state, then you need to pass them as props to the User and UserDetails component.
In my opinion Flux or Redux are needed only if your application become really big, and you don't want to have to many server calls during the session.
As a tips, if your User and UserDetails components only need to visualize data you can try to make them 'stateless' (refer to stateless components), in than way you don't need to handle willReceiveProps.
You can Handle 'viewProfile' method in the Home component, calling it from the User component with a callback (passed as a prop)
you can send an event as props and call it from other component.
Say you have a class
Class Home {
handleChange(evt) {
this.setState({ username: evt.target.value });
}
render {
return (
<div>
<Users name={this.state.username} onChange={this.handleChange}/>
<div> {this.state.username} </div>
</div>
);
}
}
Child Component
Class Users {
handleChange() {
//logic
}
render {
return (
<div>
<input type="text" onChange={this.props.onChange}/>
{this.props.name}
</div>
);
}
}
Here in Component Users when you change the input it will call the
method of class Home and update state of Home .
Now getting the updated state as props in component Users will give
you the changed text that you just entered