I have two components: CardContainer and SingleVideoContainer.
CardContainer will contain multiple SingleVideoContainers based on the data in the database.
import React, { Component } from 'react';
import Modal from 'boron/WaveModal';
//Default firebase App
import * as firebase from 'firebase';
import { firebaseApp } from '../firebase/firebase';
import SingleCardContainer from '../cards/SingleCardContainer';
var dataRef = firebaseApp.database();
var dataArray = [];
class CardContainer extends Component {
constructor(props) {
super(props);
this.state = {
userid: "",
videoLink: "",
likes: "",
challenges: "",
videoCat: "",
videoDesc: "",
videoTitle: "",
profilePic: "",
disikes: ""
}
this.showModal = this.showModal.bind(this);
this.hideModal = this.hideModal.bind(this);
this.componentDidMount = this.componentDidMount.bind(this);
}
showModal() {
this.refs.modal.show();
}
hideModal() {
this.refs.modal.hide();
}
componentDidMount() {
}
render() {
function initApp() {
var videosRef = dataRef.ref('posts/');
videosRef.on('value', function (snapshot) {
snapshot.forEach(function (data) {
var userInfo = {};
var userArray = [];
//Set up the next user array group
//9 items for 1 user.
userInfo.userid = data.val().userid;
userInfo.likes = data.val().likes;
userInfo.dislikes = data.val().dislikes;
userInfo.challenges = data.val().challenges;
userInfo.profilePic = data.val().profilePic;
userInfo.videoCategory = data.val().videoCategory;
userInfo.videoDesc = data.val().videoDesc;
userInfo.videoTitle = data.val().videoTitle;
userInfo.videoURL = data.val().videoURL;
//Then save the object in the array
userArray.push(userInfo);
//change the index to next user.
})
});
}
/**
* This loads when the page loads (right before renders)
*/
window.addEventListener('load', function () {
initApp()
});
return (
<div id="bodyType">
{
userArray.map(
data => <SingleCardContainer
userid={data.userid}
title={data.title}
likes={data.likes}
dislikes={data.challenges}
videoURL={data.videoURL}
/>)
}
</div>
)
}
}
export default CardContainer;
I want to render the SingleCardContainer by passing in the information from the dataArray as props. Each user has 9 items that I need to pass in as props.
How would I do that? I can render 1 but not multiple ones.
Here is the SingleCardContainer:
import React, { Component } from 'react';
import {
Player, ControlBar,
ForwardControl, CurrentTimeDisplay,
TimeDivider, VolumeMenuButton, BigPlayButton
} from 'video-react';
import ModalContainer from '../cards/ModalContainer';
class SingleCardContainer extends Component {
constructor(props) {
super(props);
this.likeButton = this.likeButton.bind(this);
this.challengeButton = this.challengeButton.bind(this);
this.dislikeButton = this.dislikeButton.bind(this);
}
likeButton() {
}
challengeButton() {
}
dislikeButton() {
}
//We will have to pass down the states from CardContainer as props to this so that they get updated in real-time *fingers-crossed*
render() {
return (
<div className="container">
<div className="card" id="generalCard">
<div className="card-text">
<div id="singleVideoContainer">
<h3>{this.props.title}</h3><p> {this.props.userid}</p>
<Player poster="" src={this.props.videoURL}></Player>
<div id="videoInfoSection">
<div className="row" id="buttonContainerRow">
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={() => this.likeButton()} role="button" href="#"><i className="fa fa-thumbs-up"></i></a>
<p>{this.props.likes}</p>
</div>
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={() => this.challengeButton()} role="button" href="#"><i className="fa fa-shield"></i></a>
<p>{this.props.challenges}</p>
</div>
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={() => this.dislikeButton()} role="button" href="#"><i className="fa fa-thumbs-down"></i></a>
<p>{this.props.dislikes}</p>
</div>
</div>
<div id="commentSection">
<p>{this.props.videoDesc}</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default SingleCardContainer;
You should put any data calls into componentDidMount and never in render. Also note that you can simply use map to convert the snapshot array to the values. Then call setState which will eventually call render with the new data.
import React from 'react';
import * as firebase from 'firebase';
import { firebaseApp } from '../firebase/firebase';
class CardContainer extends React.Component {
constructor(props) {
super(props);
this.state = { data: [] };
}
componentDidMount() {
var dataRef = firebaseApp.database();
var videosRef = dataRef.ref('posts/');
videosRef.on('value', (snapshot) => {
var data = snapshot.map(o => o.val());
this.setState({ data: data });
});
}
render() {
const { data } = this.state;
return (<div>
{data.map(o => <SingleCardContainer {...o} />)}
</div>);
}
}
class SingleCardContainer extends React.Component {
constructor(props) {
super(props);
}
render() {
const {title, userid, videoURL, videoDesc, likes, dislikes, challenges} = this.props;
return (
<div className="container">
<div className="card" id="generalCard">
<div className="card-text">
<div id="singleVideoContainer">
<h3>{title}</h3>
<p>{userid}</p>
<p>{videoURL}</p>
<div id="videoInfoSection">
<div className="row" id="buttonContainerRow">
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={console.log} role="button" href="#"><i className="fa fa-thumbs-up"></i></a>
<p>{likes}</p>
</div>
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={console.log} role="button" href="#"><i className="fa fa-shield"></i></a>
<p>{challenges}</p>
</div>
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={console.log} role="button" href="#"><i className="fa fa-thumbs-down"></i></a>
<p>{dislikes}</p>
</div>
</div>
<div id="commentSection">
<p>{videoDesc}</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
Below I have a working code snippet where I stripped out Firebase and replaced it with hard-coded data to prove that this will work with React.
class CardContainer extends React.Component {
constructor(props) {
super(props);
this.state = { data: [] };
}
componentDidMount() {
const data = [
{title: 'Title1', userid:'u1', videoURL:'video1.webm', videoDesc:'desc1', likes: 'like1', dislikes: 'dislike1', challenges: 'challenge1'},
{title: 'Title2', userid:'u2', videoURL:'video2.webm', videoDesc:'desc2', likes: 'like2', dislikes: 'dislike2', challenges: 'challenge2'},
{title: 'Title3', userid:'u3', videoURL:'video3.webm', videoDesc:'desc3', likes: 'like3', dislikes: 'dislike3', challenges: 'challenge3'},
];
this.setState({ data: data });
}
render() {
const { data } = this.state;
return (<div>
{data.map(o => <SingleCardContainer {...o} />)}
</div>);
}
}
class SingleCardContainer extends React.Component {
constructor(props) {
super(props);
}
render() {
const {title, userid, videoURL, videoDesc, likes, dislikes, challenges} = this.props;
return (
<div className="container">
<div className="card" id="generalCard">
<div className="card-text">
<div id="singleVideoContainer">
<h3>{title}</h3>
<p>{userid}</p>
<p>{videoURL}</p>
<div id="videoInfoSection">
<div className="row" id="buttonContainerRow">
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={console.log} role="button" href="#"><i className="fa fa-thumbs-up"></i></a>
<p>{likes}</p>
</div>
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={console.log} role="button" href="#"><i className="fa fa-shield"></i></a>
<p>{challenges}</p>
</div>
<div className="col-md-4 col-xs-6 col-sm-4">
<a className="supportButtons" onClick={console.log} role="button" href="#"><i className="fa fa-thumbs-down"></i></a>
<p>{dislikes}</p>
</div>
</div>
<div id="commentSection">
<p>{videoDesc}</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
ReactDOM.render(<CardContainer />, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.min.js"></script>
<div><!--REACT ROOT--></div>
However, I am not as familiar with Firebase so any bugs you run into would be due to the data coming from Firebase which I don't have control over and might require a different question.
Related
I'm receiving JSON object from backend which contains a name category which value is an object. This object contains names _id and name. But when I access the _id using props.category._id it's giving me this error:
TypeError: props.category is undefined
Actually, the JSON object is the document of MongoDB and category is the field that contains the reference to another document.
The backend is written in Node.js and I'm using Mongoose.
I can share the backend as well as the frontend code of the required section.
App.js
<Route path="/blog/:id" exact><SinglePost /></Route>
singlepost.js : React Component for the page (SinglePost)
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import axios from 'axios';
import Comments from '../parts/comments';
import RelatedPosts from '../parts/relatedpost';
import BlogContent from '../parts/blogcontent';
class SinglePost extends Component
{
constructor(props)
{
super(props);
this.state = {
blog: [],
commentCount: []
};
}
componentDidMount()
{
axios.get('http://localhost:5000/blogs/get/' + this.props.match.params.id)
.then(res => {
this.setState({
blog: res.data
});
})
.catch(err => console.log(err));
axios.get('http://localhost:5000/comments/count/' + this.props.match.params.id)
.then(res => {
this.setState({
commentCount: res.data
});
})
.catch(err => console.log(err));
}
render()
{
return (
<div>
<section className="blog-section">
<div className="container">
<div className="single-post no-sidebar">
<div className="title-single-post">
<a className="text-link" href="#"></a>
<h1>{this.state.blog.title}</h1>
<ul className="post-tags">
<li>{new Date(this.state.blog.createdAt).toDateString()}</li>
<li>{this.state.commentCount.count} comments</li>
</ul>
</div>
<div className="single-post-content">
<img src={"../../" + this.state.blog.imageUrl} alt="" />
<div className="post-content">
<div className="post-social">
<span>Share</span>
<ul className="share-post">
<li><i className="fa fa-facebook"></i></li>
<li><i className="fa fa-twitter"></i></li>
<li><i className="fa fa-pinterest"></i></li>
</ul>
</div>
<BlogContent blog = {this.state.blog} />
</div>
<RelatedPosts category = {this.state.blog.category} />
</div>
<Comments />
</div>
</div>
</section>
</div>
);
}
}
export default withRouter(SinglePost);
relatedposts.js : React Component for related post(RelatedPosts)
import React from 'react';
import axios from 'axios';
let RelatedPost = props => (
<div className="col-lg-4 col-md-4">
<div className="news-post standard-post text-left">
<div className="image-holder">
<img src={"../../" + props.currentPost.imageUrl} alt="" />
</div>
<a className="text-link" href="#">{props.currentPost.category.name}</a>
<h2>{props.currentPost.title}</h2>
<ul className="post-tags">
<li>{new Date(this.state.blog.createdAt).toDateString()}</li>
</ul>
</div>
</div>
);
const RelatedPosts = (props) => {
var relatedPosts = [];
axios.get('http://localhost:5000/get/related-posts/' + props.category._id)
.then(res => {
relatedPosts = res.data;
})
.catch(err => console.log(err));
return (
<div className="related-box">
<h2>Related Posts</h2>
<div className="row">
{relatedPosts.map((currentPost, index) => {
return <RelatedPost currentPost = {currentPost} key = {index} />
})}
</div>
</div>
);
}
export default RelatedPosts;
JSON from backend
{"featured":false,"likes":5,"_id":"5f084b96acb1d60bcee3f0fa","title":"Lorem Ipsum","description":"Lorem Ipsum Blog","imageUrl":"upload/blog/f1.jpg","category":{"_id":"5f05ae4b27ef066f94673265","name":"Technology"},"createdAt":"2020-07-10T11:05:58.044Z","updatedAt":"2020-07-10T11:05:58.044Z","__v":0}
change you SinglePost component like this where you'll check if data has arrived and set in your states.
One way you can do it as follows
SinglePost.js
import React, { Component } from 'react';
import { withRouter } from 'react-router';
import axios from 'axios';
import Comments from '../parts/comments';
import RelatedPosts from '../parts/relatedpost';
import BlogContent from '../parts/blogcontent';
class SinglePost extends Component {
constructor(props)
{
super(props);
this.state = {
blog: [],
commentCount: []
};
}
componentDidMount()
{
axios.get('http://localhost:5000/blogs/get/' + this.props.match.params.id)
.then(res => {
this.setState({
blog: res.data
});
})
.catch(err => console.log(err));
axios.get('http://localhost:5000/comments/count/' + this.props.match.params.id)
.then(res => {
this.setState({
commentCount: res.data
});
})
.catch(err => console.log(err));
}
render()
{
return (
<div>
<section className="blog-section">
<div className="container">
{this.state.blog.length > 0 ? // <----- here
<div className="single-post no-sidebar">
<div className="title-single-post">
<a className="text-link" href="#"></a>
<h1>{this.state.blog.title}</h1>
<ul className="post-tags">
<li>{new Date(this.state.blog.createdAt).toDateString()}</li>
<li>{this.state.commentCount.count} comments</li>
</ul>
</div>
<div className="single-post-content">
<img src={"../../" + this.state.blog.imageUrl} alt="" />
<div className="post-content">
<div className="post-social">
<span>Share</span>
<ul className="share-post">
<li><i className="fa fa-facebook"></i></li>
<li><i className="fa fa-twitter"></i></li>
<li><i className="fa fa-pinterest"></i></li>
</ul>
</div>
<BlogContent blog = {this.state.blog} />
</div>
<RelatedPosts category = {this.state.blog.category} />
</div>
<Comments />
</div>
: <h1>Loading</h1> // <----- here
</div>
</section>
</div>
);
}
}
export default withRouter(SinglePost);
I use twitter API with Javascript API for websistes. When I click on anchor tag which should me redirect to /twitter/explore from /twitter I'm redirected to /twitter/explore but immediately back me to /twitter which is Twitter component. I got this error in chrome console:
Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but
it indicates a memory leak in your application. To fix, cancel all subscriptions and
asynchronous tasks in the componentWillUnmount method twitter api.
I try resolve this problem by add global variable and call setState only when that variable is true and later when component is unmounting I change variable to false. Error does not exist but still the app redirect me back to /twitter. I can't render TwitterExplore component because back me. I'm not sure that this solution with global variable is good idea.
Here is my code below:
Twitter component with mapping /twitter
class Twitter extends React.Component {
isMountedTwitter = false;
constructor(props) {
super(props);
this.state = {
accessToken: '',
email: '',
name: '',
userID: '',
pictureUrl: '',
providerId: '',
screenName: '',
tokenSecret: ''
}
this.Auth = new AuthService();
}
componentDidMount() {
this.isMountedTwitter = true;
this.isMountedTwitter && window.twttr.widgets.load(document.getElementsByClassName("feed-container")[0]);
let jwtToken = null;
if(this.Auth.getTwitterToken() !== null) {
jwtToken = this.Auth.getTwitterToken();
}
if(this.Auth.getToken() !== null) {
jwtToken = this.Auth.getToken();
}
fetch(`/getuserdata/${jwtToken}`, {
method: 'GET',
headers: {
'content-type': 'application/json'
}
})
.then(response => response.json())
.then(jsonData => {
if(this.isMountedTwitter) {
this.setState({
accessToken: jsonData.accessToken,
email: jsonData.email,
name: jsonData.name,
userID: jsonData.userID,
pictureUrl: jsonData.pictureUrl,
providerId: jsonData.providerId,
screenName: jsonData.screenName,
tokenSecret: jsonData.tokenSecret
}, () => {
window.twttr.widgets.createTimeline(
{
sourceType: 'likes',
screenName: this.state.screenName
},
document.getElementsByClassName("tweets-likes-container")[0],
{
width: '100%',
height: '100%',
related: 'twitterdev,twitterapi'
});
});
}
});
}
componentWillUnmount() {
this.isMountedTwitter = false;
}
render() {
return (
<div className="twitter-container">
<div className="twitter-grid-container">
<div className="twitter-grid-item-1">
<div className="twitter-left-categories-container">
<div className="twitter-profil-container">
{ this.state.name }
</div>
<TwitterCategoriesCard
pictureUrl={this.state.pictureUrl}
screenName={this.state.screenName}
/>
</div>
</div>
<div className="feed-container">
{/* <div className="twitter-user-profil">
<div className="twitter-header-profile">
</div>
<div className="tweets-profile-container">
</div>
</div> */}
<div className="tweets-likes-container">
</div>
</div>
<div className="twitter-grid-item-3">
<div className="twitter-rl-container">
<div className="twitter-groups-container">
<SearchTwitterPeople />
<AvailableTrends />
</div>
</div>
<div className="twitter-rr-container">
<div className="twitter-friends-container"></div>
</div>
</div>
</div>
</div>
);
}
}
export default withAuth(Twitter);
TwitterCategoriesCard component
class TwitterCategoriesCard extends React.Component {
constructor(props) {
super(props);
this.onExploreClick = this.onExploreClick.bind(this);
}
onExploreClick() {
this.props.history.push("/twitter/explore");
}
render() {
return (
<div className="twitter-categories-container">
<ul className="list-group twitter-categories-list">
<li className="list-group-item list-group-item-hover">
<div className="twitter-categories-icons-box">
<i className="fas fa-home"></i> Home
</div>
</li>
<li onClick={this.onExploreClick} className="list-group-item list-group-item-hover">
<div className="twitter-categories-icons-box">
<span style={{ fontWeight: '900' }}>#</span> Explore
</div>
</li>
<li className="list-group-item list-group-item-hover">
<div className="twitter-categories-icons-box">
<i className="fas fa-clock"></i> Timeline likes
</div>
</li>
<li className="list-group-item list-group-item-hover">
<div className="twitter-categories-icons-box">
<i className="fas fa-bell"></i> Notifications
</div>
</li>
<li className="list-group-item list-group-item-hover">
<div className="twitter-categories-icons-box">
<i className="far fa-envelope"></i> Messages
</div>
</li>
<li
className="list-group-item list-group-item-hover"
>
<div className="twitter-categories-icons-box">
<img
src={this.props.pictureUrl}
alt="Avatar"
className="twitter-categories-avatar"
/> Profile
</div>
</li>
<li className="list-group-item list-group-item-hover add-tweet-button">
<a
className="twitter-share-button"
href="https://twitter.com/intent/tweet"
data-size="large"
>
Tweet
</a>
</li>
</ul>
</div>
);
}
}
export default withRouter(TwitterCategoriesCard);
withAuth HOC:
export default function withAuth(AuthComponent) {
const Auth = new AuthService();
let customAuthComponent = false;
class AuthWrapped extends React.Component {
componentDidMount() {
customAuthComponent = true;
if(!Auth.loggedIn()) {
this.props.history.replace("/login");
} else {
let twitterJwtToken = Auth.getTwitterToken();
let facebookJwtToken = Auth.getToken();
try {
if(twitterJwtToken) {
customAuthComponent && this.props.history.replace("/twitter");
}
if(facebookJwtToken) {
customAuthComponent && this.props.history.replace("/dashboard");
}
} catch(err) {
if(twitterJwtToken) {
Auth.logoutTwitter();
}
if(facebookJwtToken) {
Auth.logout();
}
this.props.history.replace("/login");
}
}
}
componentWillUnmount() {
customAuthComponent = false;
}
render() {
if(Auth.loggedIn()) {
return (
customAuthComponent && <AuthComponent history={this.props.history} />
);
} else {
return null;
}
}
}
return AuthWrapped;
}
App.js
function App() {
return (
<Provider store={store} >
<Router>
<div className="App">
<I18nextProvider i18n={i18next}>
<Header />
<Route exact path="/settings" component={Settings} />
<Route exact path="/twitter" component={Twitter} />
<Route exact path="/twitter/explore" component={TwitterExplore} />
</I18nextProvider>
</div>
</Router>
</Provider>
);
}
export default App;
I create a component of react. and there one array with some values. so I need to display that value or data in any specific div by clicking in a button.
this is my array in component.
constructor(){
super()
this.state = {
notificaion: [
"Notification-1",
"Notification-3",
"Notification-4",
"Notification-5",
]
}
}
this is my button with click event.
<button onClick={this.getNotification}>{this.state.notificaion.length}</button>
this is the function that I have create. and to push data in specific div.
getNotification = () =>{
return(
this.state.notificaion.map(items =>(
<li key={items}>{items}</li>
))
)
}
here I want to display when buttons is clicked
<strong>{this.getNotification()}</strong>
This is my full code that I have been tried.
import React, {Component} from 'react';
class Menu2 extends Component{
constructor(){
super()
this.state = {
notificaion: [
"Notification-1",
"Notification-3",
"Notification-4",
"Notification-5",
]
}
}
getNotification = () =>{
return(
this.state.notificaion.map(items =>(
<li key={items}>{items}</li>
))
)
}
render(){
return(
<div className="header">
<div className="container">
<div className="row">
<div className="col-lg-12 col-sm-12 col-xs-12">
<div className="text-center mb-20">
<h1>Notificaion Status</h1>
<p>Check notificatin read/unread</p>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-12 col-sm-12 col-xs-12">
<div className="card border-dark mb-3">
<div className="card-body text-dark">
<p className="card-text" style={{textAlign: 'center'}}>
{this.state.notificaion.length > 0
?
<span>You Have <button onClick={this.getNotification}>{this.state.notificaion.length}</button> Unread Notifications</span>
:
<span>You Have <button onClick={this.getNotification}>{this.state.notificaion.length}</button> Unread Notifications}</span>}
</p>
<strong>{this.getNotification()}</strong>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Menu2;
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
notificaion: [
"Notification-1",
"Notification-3",
"Notification-4",
"Notification-5",
],
notificationHtml: ""
}
}
getNotification = () => {
this.setState({
notificationHtml: this.state.notificaion.map(items => (
<li key={items}>{items}</li>
))
});
}
render() {
return (
<div className="App">
<button onClick={this.getNotification}>{this.state.notificaion.length}</button>
<div>
{this.state.notificationHtml}
</div>
</div>
);
}
}
export default App;
I would implemented as such:
this.state = {
visible: false,
notifications: ...
}
toggleVisibility() =>{
this.setState({
visibile: true
})
}
Don't forget to bind the "toggleVisibility" function. Then
in your component:
<button onClick={this.toggleVisibility}/>
...
{if(this.state.visible){
<strong>this.state.notifications.map(notification,i) =>
<li key={i}>{notification}</li>
</strong>
}
You can add a property showNotification in state. And based on the value of it, we can show the notification.
Also add a method showNotificationHandler that toggles the showNotification value.
class Menu2 extends Component {
constructor() {
super();
this.state = {
notificaion: [
"Notification-1",
"Notification-3",
"Notification-4",
"Notification-5"
],
// adding a property "showNotification"
showNotification: false
};
}
getNotification = () => {
return this.state.notificaion.map(items => <li key={items}>{items}</li>);
};
// method that toggles the "showNotification" value
showNotificationHandler = () => {
this.setState(({ showNotification }) => ({
showNotification: !showNotification
}));
};
render() {
return (
<div className="header">
<div className="container">
<div className="row">
<div className="col-lg-12 col-sm-12 col-xs-12">
<div className="text-center mb-20">
<h1>Notificaion Status</h1>
<p>Check notificatin read/unread</p>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-12 col-sm-12 col-xs-12">
<div className="card border-dark mb-3">
<div className="card-body text-dark">
<p className="card-text" style={{ textAlign: "center" }}>
{this.state.notificaion.length > 0 ? (
<span>
You Have{" "}
<button onClick={this.showNotificationHandler}>
{this.state.notificaion.length}
</button>{" "}
Unread Notifications
</span>
) : (
<span>
You Have{" "}
<button onClick={this.showNotificationHandler}>
{this.state.notificaion.length}
</button>{" "}
Unread Notifications}
</span>
)}
</p>
<strong>
// Depending on the value of "showNotification" we get notification
// if "showNotification" is true then get the notification
{this.state.showNotification && this.getNotification()}
</strong>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default Menu2;
I'm using redux for state management in my app. But after I dispatch an action only the parent component updates the children doesn't receive the new props or updates. I'm using react 16.2.0, Redux 3.7.2, react-redux 5.0.6. These are my components:
Parent component
class ConnectingItems extends React.Component{
constructor(props){
super(props);
this.afterProds = this.afterProds.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.data = this.props.data.products;
}
componentDidUpdate(){
this.data = this.props.data.products;
}
afterProds(){
this.data = this.props.data.products;
this.forceUpdate();
}
handleSearch(data){
this.data = data;
this.forceUpdate();
}
render(){
const products = this.props.data.products;
const searched_products = this.data;
console.log('rerendering in main')
return(
<div>
<Navbar/>
<div className="container">
<h5 className="center-align">
<Searchform data={products} new_data={this.handleSearch}/>
</h5>
</div>
<ProductsList user_data={searched_products}/>
<Modal
header='Add Product'
modalOptions={{
opacity: .0
}}
trigger={
<div className='fixed-action-btn action-button'>
<a className="btn-floating btn-large yellow darken-1">
<i className="fa fa-plus"></i>
</a>
</div>
}>
<AddProducts afterProdAdd={this.afterProds}/>
</Modal>
</div>
);
}
}
Child componenet:
class ConnectingProductListing extends React.Component{
constructor(props){
super(props);
this.handleDelete = this.handleDelete.bind(this);
this.afterEdit = this.afterEdit.bind(this);
}
handleDelete(id){
this.props.deleteProduct(id);
console.log('delete dispatched');
this.forceUpdate();
}
componentWillReceiveProps(newprops){
console.log(newprops);
}
afterEdit(){
this.forceUpdate();
}
render(){
let data = this.props.user_data;
console.log('im re rendering')
console.log(data);
return(
<div className="container section">
<div className="row">
<div className="col s12">
{data.length < 1 ?
<div className="col s12 center-align">
<p>
<b>No Product here.</b>
</p>
</div>:
data.map(product => {
const name = product.name;
const quantity = product.quantity;
const price = product.price;
return(
<div key={product.id} className="card center grey lighten-5 z-depth-1">
<div className='card-content left-align'>
<span className='card-title'>
{name}
</span>
<span>
Price: ${price}<br/>
</span><span>
Quantity: {quantity}
</span>
</div>
<div className='card-action center'>
<div className='row'>
<div className='col s12'>
<Modal
header='Edit Product'
modalOptions={{
opacity: 0.0
}}
trigger={
<button className='btn yellow accent-3 center'>Edit</button>
}
actions={
<div>
<Modal
header='Delete Product'
modalOptions={{
opacity: 0.0
}}
trigger={
<a className="modal-action modal-close waves-effect waves-yellow btn-flat">Delete</a>
}
actions={
<div>
<a className='modal-action modal-close waves-effect waves-yellow btn-flat'
onClick={() => this.handleDelete(product.id)}>Yes</a>
<a className='modal-action modal-close waves-effect waves-yellow btn-flat'>No</a>
</div>
}>
<p>Are you sure you want to delete this product? It can't be undone</p>
</Modal>
<a className="modal-action modal-close waves-effect waves-yellow btn-flat">Close</a>
</div>
}>
<EditProducts product={product} afterEdit={this.afterEdit}/>
</Modal>
</div>
</div>
</div>
</div>
);
})
}
</div>
</div>
</div>
);
}
}
My reducer:
const rootReducer = (state = initialState, action) => {
switch (action.type){
case constants.ADD_PRODUCT:
return {
...state,
data: {
...state.data,
products: [
...state.data.products,
action.payload
]
}
}
default:
return state;
}
};
export default rootReducer;
Initial state:
initialState = {
data: {
id: 0,
name: 'John Doe',
email: 'johndoe#gmail.com',
products: [
{
id: 0,
name: 'product name',
price: 10,
quantity: 10,
}
],
}
};
store:
import { createStore } from "redux";
import rootReducer from "../reducer/index";
const store = createStore(
rootReducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);
export default store;
also i'm only focusing on the add product action
Please guys help me
UPDATE: I see you added some code, but you're still missing the container? And your action (constant) definition code?
If you're using redux, then you should include the source code for your store and containers.
Most likely the issue is the first one at https://web.archive.org/web/20180304224831/https://redux.js.org/troubleshooting#nothing-happens-when-i-dispatch-an-action:
Redux assumes that you never mutate the objects it gives to you in the reducer. Every single time, you must return the new state object.
There are various suggestions there. If you include more code, I might be able to help more.
Building a modal component that opens up a bootstrap modal from any part of the app then sets custom states for that component outside of it. It works fine but i always just get this error once i open the modal and I cant seem to figure out why:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.` Doesnt really break anything but error keeps showing up.
My code:
layout.js
import React from "react";
import {Link} from 'react-router';
import NotificationSystem from 'react-notification-system';
import AppHeader from "#/ui/header/AppHeader";
import AppFooter from "#/ui/footer/AppFooter";
import Modal from "#/ui/modals/modal/Modal";
import "#/main.scss";
import './layout.scss';
export default class Layout extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
app.notify.clear = this.refs.notificationSystem.clearNotifications;
app.notify = this.refs.notificationSystem.addNotification;
app.modal = this.refs.modal.updateProps;
}
render() {
return (
<div class="app">
<div class="header">
<AppHeader page={this.props.location.pathname.replace('/', '')}/>
</div>
<div class="body">
{this.props.children}
</div>
<div class="footer">
<AppFooter />
</div>
<NotificationSystem ref="notificationSystem" style={false} />
<Modal ref="modal" />
</div>
);
};
}
Modal.js
import React from "react";
import ReactDOM from 'react-dom';
import SVGInline from "react-svg-inline";
import {closeSvg} from '#/utils/Svg';
export default class Modal extends React.Component {
constructor(props) {
super(props);
this.state = {
showHeader: true,
showFooter: false,
title: "",
size: '',
className: '',
id: '',
footerContent: null,
showSubmitBtn: true,
showCancelBtn: true,
cancelBtnText: "Cancel",
successBtnText: "Save Changes",
onModalClose: () => {},
showModal: false,
html: () => {}
}
this.updateProps = this.updateProps.bind(this);
this.hideModal = this.hideModal.bind(this);
}
componentWillMount() {
var self = this;
var $modal = $(ReactDOM.findDOMNode(this));
}
componentDidUpdate(prevProps, prevState) {
if(this.state.showModal) {
$('body').addClass('modal-open');
} else {
$('body').removeClass('modal-open');
}
}
componentWillUnmount() {
// $('body').removeClass("modal-open");
}
componentWillReceiveProps(nextProps) {
console.log(nextProps);
}
updateProps(args) {
let merged = {...this.state, ...args};
this.setState(merged);
}
hideModal() {
this.setState({
showModal: false
});
this.state.onModalClose();
}
buildFooter() {
if(this.props.footerContent) {
return (
<div class="content">
{this.props.footerContent}
</div>
)
} else if(this.props.showCancelBtn && this.props.showSubmitBtn) {
return (
<div class="buttons">
<button type="button" class="btn btn-default" data-dismiss="modal" onClick={this.props.onModalClose}>{this.props.cancelBtnText}</button>
<button type="button" class="btn btn-success">{this.props.successBtnText}</button>
</div>
);
} else if(this.props.showCancelBtn) {
return (<button type="button" class="btn btn-default" data-dismiss="modal" onClick={this.props.onModalClose}>Close</button>);
} else if(this.props.showSubmitBtn) {
return (<button type="button" class="btn btn-success">Save changes</button>);
}
}
render() {
let {
id,
className,
onModalClose,
size,
showHeader,
title,
children,
showFooter,
showModal,
html
} = this.state;
return (
<div class={`modal-wrapper`} >
{
showModal ?
<div class={`modal fade in ${className}`} role="dialog">
<div class="bg" ></div>
<div class={`modal-dialog ${size}`}>
<div class="modal-content">
{ showHeader ?
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">
<SVGInline svg={closeSvg} />
</button>
<h4 class="modal-title">{ title }</h4>
</div> : '' }
<div class="modal-body" >
{html()}
</div>
{ showFooter ?
<div class="modal-footer">
{ this.buildFooter() }
</div> : ''
}
</div>
</div>
</div>
: ''
}
</div>
);
}
}
SelectDefaultImage.js
import React from "react";
import sass from "./selectdefaultimage.scss";
import FullScreenImageModal from "#/ui/modals/fullscreenimagemodal/FullScreenImageModal";
export default class SelectDefaultImage extends React.Component {
constructor() {
super();
this.state = {
showModal: false,
imgUrl: false,
}
}
showImageModal(image) {
this.setState({
showModal: true,
imgUrl: image
});
}
hideImageModal() {
this.setState({
showModal: false,
imgUrl: false
})
}
onSelectImageClick(e, image) {
$('.select-image-widget .active').removeClass('active');
$(e.target).parent().addClass('active');
// this.props.selectedImage(image)
}
render() {
let {listingManager, images, selectedImage} = this.props;
let {imgUrl} = this.state;
return (
<div class="content">
<div class="row">
<div class="col-sm-12">
<label class="control-label" for="description">Select an Image</label>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<div class="select-image-widget">
{
images.map((image, idx) => {
return (
<div class="selecter" key={idx}>
<div class="img" style={{backgroundImage: `url(${listingManager.LISTINGS_PATH + image})` }} onClick={(e) => { this.onSelectImageClick(e, image) }}></div>
<i class="fa fa-search-plus" aria-hidden="true" onClick={()=> {this.showImageModal(image)}}></i>
</div>
)
})
}
</div>
</div>
</div>
{
this.state.showModal ?
app.modal({
showModal: true,
className: "fullscreen-image-modal",
size: "modal-lg",
html: () => {
return (<img src={listingManager.LISTINGS_PATH + imgUrl} />);
}
})
: ''
}
</div>
)
}
}
The reason for the error is most likely that in SelectDefaultImage, you call app.modal from within the render method, and app.modal is this.refs.modal.updateProps, which does a setState. If you put the app.modal call in showImageModal, I expect the error to go away. However, setting the state of a another component by means of refs and globals is a bit of a React antipattern, so I would recommend to do some refactoring and use props to pass the data.