React JS component renders multiple times in Meteor - javascript

I using Meteor 1.3 for this application together with react js and Tracker React.
I have a page to view all available users in the application. This page require user to login to view the data. If user not logged in, it will shows the login form and once logged-in the component will render the user's data.
Main component for the logic.
export default class MainLayout extends TrackerReact(React.Component) {
isLogin() {
return Meteor.userId() ? true : false
}
render() {
if(!this.isLogin()){
return (<Login />)
}else{
return (
<div className="container">
<AllUserdata />
</div>
)
}
}
}
And in the AllUserdata component:
export default class Users extends TrackerReact(React.Component) {
constructor() {
super();
this.state ={
subscription: {
Allusers : Meteor.subscribe("AllUsers")
}
}
}
componentWillUnmount(){
this.state.subscription.Allusers.stop();
}
allusers() {
return Meteor.users.find().fetch();
}
render() {
console.log('User objects ' + this.allusers());
return (
<div className="row">
{
this.allusers().map( (user, index)=> {
return <UserSinlge key={user._id} user={user} index={index + 1}/>
})
}
</div>
)
}
};
The problem is when logged in, it only shows the current user's data. All other user objects are not rendered. If I check on the console, console.log('User objects ' + this.allusers()); show objects being rendered 3 times: the first render only shows the current user's data, the second one renders data for all users (the desired result), and the third one again renders only the current user's data.
If I refresh the page, the user data will be rendered properly.
Any idea why?

React calls the render() method of components many times when it's running. If you're experiencing unexpected calls, it's usually the case that something is triggering changes to your component and initiating a re-render. It seems like something might be overwriting the call to Meteor.users.find().fetch(), which is probably happening because you're calling that function on each render. Try inspecting the value outside of the render method or, better yet, rely on tests to ensure that your component is doing what it should be :)
From https://facebook.github.io/react/docs/component-specs.html#render
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it's invoked, and it does not read from or write to the DOM or otherwise interact with the browser (e.g., by using setTimeout). If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes server rendering more practical and makes components easier to think about.
See also:
https://facebook.github.io/react/docs/advanced-performance.html
https://facebook.github.io/react/docs/top-level-api.html#reactdom
https://ifelse.io/2016/04/04/testing-react-components-with-enzyme-and-mocha/

Related

Pass React State Down As Prop, Without Defined Children Components

We're working on an admin portal. We've got a user account page, where the user will be able to edit their personal info and eventually stuff like security, privacy, notifications, etc.
Note: We're using classes and not functions. Let's skip the questions on "why" or trying to "sell me this pen" on using functions over classes.
Note: Also, for reference, we're using https://mui.com. Just in case you need to know.
We've got a page template for all portal pages, called <PortalTemplate/>.
On the PortalTemplate, we are using a cookie, to execute a getUser() function that returns user details. This is captured in this.state.account.
The user's account page is injected as this.props.children.
// PORTAL TEMPLATE (basic):
export interface PageProps {
children: any;
pageTitle: string;
}
export interface PageState {
account: AccountDTO;
anchorEl: HTMLElement | null;
isLoginActive: boolean;
isMenuOpen: boolean;
}
export default class PortalTemplate extends React.Component<PageProps, PageState> {
render() {
return(
<div>
{this.props.children}
</div>
);
}
}
This portal template is used for ALL portal pages, as sort of like a wrapper that contains the global menu stuff. We're trying to determine how to actually leverage the state variables, but also sending this down to children component pages so they can use the same details to render dynamic data.
// ACCOUNT PAGE (basic):
export default class AccountPage extends React.Component<PageProps> {
constructor(props: PageProps) {
super(props);
}
render() {
return (
<PortalTemplate pageTitle='Account'>
{ page content here }
</PortalTemplate>
)};
}
Our problem is that we're having trouble sending the this.state.account data down from PortalTemplate to AccountPage so that the user's account page can use these details to render the data, but also have a form where they can edit their details.
React Ref:
https://beta.reactjs.org/learn/sharing-state-between-components#step-3-add-state-to-the-common-parent
We tried using this reference, but are finding it hard to follow. Since our PageTemplate component uses { this.props.children } to allow components to be used "under" it, we're not able to figure out where this.state.account is supposed to go in order to be utilized by child pages/components.
We've also tried to clone the child, like other posts have depicted, but was unsuccessful.
{React.cloneElement(this.props.children)}

React api only fetches if data is not being used

I have two state items inside redux store, both of which fetches data from the API.
componentDidMount() {
this.props.postMDBConfig(`https://api.themoviedb.org/3/configuration?api_key=${this.props.apiKey}`);
this.props.postMoviePopular(`https://api.themoviedb.org/3/movie/popular?api_key=${this.props.apiKey}&language=en-US&page=1&region=US`)
}
But as soon as I pass (or even use in current component) the information down to a child and use the data, it does not fetch.
render() {
return (
<ItemCarousel MDBConfig={this.props.config} items={this.props.moviesPopular}/>
);
}
class ItemCarousel extends React.Component {
render() {
const slider = (
<AwesomeSlider cssModule={AwesomeSliderStyles}>
<div data-src={`${this.props.config.images.secure_base_url}original${this.props.items[0].poster_path}`}> </div>
</AwesomeSlider>
);
return (
<div>{slider}</div>
);
}
}
Here's what the API fetch looks like when I use vs. don't use the data:
https://ibb.co/G00jzHW (with data not using them)
https://ibb.co/xzkF89R (no data when using the state variables)
I suspect it has something to do with the rendering lifecycle order, but I have already tried componentWillMount and it still does not work compared to componentDidMount.
I think you can have one state variable such as loading initially true. Once your api calls get resolved then you can update the state variable as false.
Now meanwhile you can add this loading check on your component. It will help you out to render the component using the API data.
render() {
const itemCarousel = (this.state.loading) ? '' : <ItemCarousel MDBConfig=
{this.props.config} items={this.props.moviesPopular}/>
return (
{itemCarousel}
)
}
Hope this helps.

React js state and lifecycle

componentDidMount() {
const user = auth.getCurrentUser();
this.setState({ user });
}
I have this code, I think this.setState({ user });takes a little time, if I want to do some checks immidetly like
<Route
path="/foo"
render={props =>
this.state.user ? (
<Bar {...props} />
) : (
<Redirect to="/login" />
)
}
/>
when refreshing the page the user at the beginning is always null. what is the proper solution? do I need to set state at constructor? or did I do something wrong?
My account got blocked by some down votes questions, the funny thing is I have to re-edit them, even though I already have the accepted answer.I do not understand what's the point to do this.I am so frustrated by this stackoverflow system.
Now, I basically can do nothing but keep editing my questions, and they have all been answered. This is ridiculous !!!
Yes, you should initialise your state in the constructor.
See the React docs example:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
when refreshing the page the user at the beginning is always null
#Shubham Khatri did explain it really well, in short, just because the render() function is called before the componentDidMount(), hence, the user is always null.
Take a look at this: React lifecycle methods diagram
And as you can see that, the proper place for setState should be the contructor() cuz it's called before the render().
However, for api calls why componentDidMount is the better place? why
we do not do all set up in constructor?
I know you're talking about this: https://reactjs.org/docs/faq-ajax.html . The document does say that: You should populate data with AJAX calls in the componentDidMount lifecycle method. This is so you can use setState to update your component when the data is retrieved.
However, in another place, they say:
You may call setState() immediately in componentDidMount(). It will
trigger an extra rendering, but it will happen before the browser
updates the screen. This guarantees that even though the render() will
be called twice in this case, the user won’t see the intermediate
state. Use this pattern with caution because it often causes
performance issues. In most cases, you should be able to assign the
initial state in the constructor() instead. It can, however, be
necessary for cases like modals and tooltips when you need to measure
a DOM node before rendering something that depends on its size or
position.
...and also for the case that you need to authenticate because this process depends on the value of the user ( as your design).
The problem in your code is that, componentDidMount is called after render and by the time your user details are fetched and stored in state, your component is ready to redirect to /login since user wan't available. To solve this issue, you need to fetch user details before the initial render and hence constructor is the ideal place to do it
constructor(props) {
super(props);
this.state = {
user: auth.getCurrentUser()
}
}
The state goes inside the constructor, but only if you need a constructor (e.g.:to initialize flags). If you don't need a constructor you can just initialize the state outside:
class MyComponent extends Component {
state = { myState = [] }
render() {
const { myState } = this.state
return (...)
}
}
You should use the constructor() to initiate state, componentDidMount() for call functions and componentWillReceiveProps() for setState.
constructor(props) {
super(props);
this.state = {
firstName: "",
lastName: ""
};
}
componentDidMount() {
this.props.userProfile();
}
componentWillReceiveProps(nextProps, prevProps) {
this.setState({
firstName: nextProps.user.firstName,
lastName: nextProps.user.lastName
});
}

React-redux : listening to state changes to trigger action

I have my redux state like this:
{
parks: [
{
_id:"ad1esdad",
fullName : "Some Name"
},
{
_id:"ad1es3s",
fullName : "Some Name2"
}
],
parkInfo: {
id : "search Id",
start_time : "Some Time",
end_time : "Some Time"
}
}
I have a parkSelector component from which a user selects parkId and start_time and end_time
import React, { Component } from 'react';
import { changeParkInfo } from '../../Actions';
class ParkSelector extends Component {
constructor(props) {
super(props);
this.handleApply = this.handleApply.bind(this);
this.rederOptions = this.rederOptions.bind(this);
this.state = {
startDate: moment().subtract(1, 'days'),
endDate: moment(),
parkId : this.props.parks[0]
};
}
handleApply(event) {
this.setState({
parkId : event.target.parkId.value
startDate: event.target.start_time.value,
endDate: event.target.end_time.value,
});
this.props.changeParkInfo(this.state.parkId,this.state.startDate,this.state.endDate);
}
rederOptions(){
return _.map(this.props.parks,(park,index)=>{
return(
<option value={park._id} key={park._id}>{park.profile.fullName}</option>
);
});
}
render() {
return (
<div className="row">
<div className="pb-4 col-sm-3">
<form onSubmit={this.handleApply}>
<select name="parkId" value={this.state.parkId} className="form-control input-sm">
{this.rederOptions()}
</select>
<input name="start_time" type="date" />
<input name="end_time" type="date" />
<button type="submit">Apply</button>
</form>
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
parks : state.parks
};
}
export default connect(mapStateToProps,{ changeParkInfo })(ParkSelector);
I have another component 'stats' which needs to displays information related with parkInfo which will be loaded my api request.
import React, { Component } from 'react';
import StatsCard from '../../components/StatsCard';
import { getDashboardStats } from '../../Actions';
class Dashboard extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="animated fadeIn">
<div className="row">
<StatsCard text="Revenue Collected" value={9999} cardStyle="card-success" />
<StatsCard text="Total Checkins" value={39} cardStyle="card-info" />
<StatsCard text="Total Checkouts" value={29} cardStyle="card-danger" />
<StatsCard text="Passes Issued" value={119} cardStyle="card-warning" />
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
parkInfo : state.parkInfo,
dashboardStats : state.dashboardStats
};
}
export default connect(mapStateToProps,{ getDashboardStats })(Dashboard);
I need to call getDashboardStats action (which makes api call and stores in results in dashboardStats of the redux state) whenever the redux state of parkInfo changes.
What is the best way to call this action, I have tried componentWillUpdate but it keeps on updating infinitely. What is best practice for this scenario ?
I had a similar problem but found a suitable approach. I believe the problem has to do with how the responsibilities of reducers and subscribers in a Redux app are often interpreted. My project did not use React, it was Redux-only, but the underlying problem was the same. To put emphasis on answering the underlying problem, I approach the subject from a general perspective without directly referring to your React-specific code.
Problem re-cap: In the app, multiple actions P,Q,R can cause a state change C. You want this state change C to trigger an asynchronous action X regardless of the action that originally caused the state change C. In other words, the async action X is coupled to the state change but intentionally decoupled from the wide range of actions (P,Q,R) that could cause the change C. Such situation does not happen in simple hello-todo examples but does happen in real-world applications.
Naïve answer 1: You cannot trigger another action, it is going to cause infinite loop.
Naïve answer 2: You cannot trigger another action, reducer must not trigger actions or cause any side effects.
Although both naïve answers are true, their base assumptions are wrong. The first wrongly assumes the action X is triggered synchronously and without any stopping condition. The second wrongly assumes the action X is triggered in a reducer.
Answer:
Trigger the action X in a subscriber (aka renderer) and in asynchronous manner. It might sound weird at first but it is not. Even the simplest Redux applications do it. They listen state changes and act based on the change. Let me explain.
Subscribers, in addition to rendering HTML elements, define how actions are triggered in a response to user behaviour. As well as dealing with user behaviour, they can define how actions are triggered in a response to any other change in the world. There is little difference between a user clicking a button after a five seconds and a setTimeout triggering an action after five seconds. As well as we let subscribers to bind an action to a click event or, say, found GPS location, we can let them bind an action to a timeout event. After init, these bindings are allowed to be modified at each state change just like how we can re-render a button or the whole page at a state change.
An action triggered by setTimeout will cause a loop-like structure. Timeout triggers an action, reducers update the state, redux calls subscribers, subscribers set a new timeout, timeout triggers action etc. But again, there is little difference to the loop-like structure caused by normal rendering and binding of events with user behaviour. They are both asynchronous and intended cyclic processes that allow the app to communicate with the world and behave as we like.
Therefore, detect your state change of interest in a subscriber and freely trigger the action if the change happened. Use of setTimeout can be recommended, even with the delay of zero, just to keep the event loop execution order clear to you.
A busy loop is of course a problem and must be avoided. If the action X itself causes such state change that will immediately trigger it again, then we have a busy loop and the app will stop to respond or become sluggish. Thus, make sure the triggered action does not cause such a state change.
If you like to implement a repeating refresh mechanism, for example to update a timer each second, it could be done in same manner. However, such simple repetition does not need to listen state changes. Therefore in those cases it is better to use redux-thunk or write an asynchronous action creator otherwise. That way the intention of the code becomes easier to understand.
If my understanding is correct, you need to make the API call to get the dashboardStats in your Dashboard component, whenever the parkInfo changes.
The correct life-cycle hook in this scenario would be the componentWillReceiveProps
componentWillReceiveProps(nextProps){
// this check makes sure that the getDashboardStats action is not getting called for other prop changes
if(this.props.parkInfo !== nextProps.parkInfo){
this.props.getDashboardStats()
}
}
Also note that, componentWillReceiveProps will not be called for the first time, so you may have to call the this.props.getDashboardStats() in componentDidMount too.
Goal: A change in parkInfo redux-state should prompt Dashboard to dispatch getDashboardInfo and re-render. (This behavior will also be similar in other components).
I use babel transform-class-properties, syntax is slightly different.
example:
// SomeLayout.js
import ParkSelector from 'containers/ParkSelector'
import Dashboard from 'containers/Dashboard'
const SomeLayout = () => {
return (
<div>
<ParkSelector />
<Dashboard />
</div>
)
}
export default SomeLayout
-
// Dashboard.js
// connect maps redux-state to *props* not state, so a new park selection
// will not trigger this component to re-render, so no infinite loop there
#connect((store) => ({ currentParkId: store.parkInfo.id }, //decorator syntax
{ getDashboardStats })
)
class Dashboard extends Component {
state = {
currentId: this.props.currentParkID,
parkInfoFoo: '',
parkInfoBar: ''
}
// using null for when no park has been selected, in which case nothing runs
// here.
componentWillReceiveProps(nextProps) {
// when Dashboard receives new id via props make API call
// assumes you are setting initial state of id to null in your reducer
if (nextProps.currentParkId !== null) {
getDashboardStats(`someurl/info/${nextProps.id}`).then((data) => {
// update state of Dashboard, triggering a re-render
this.setState({
currentId: nextProps.id
parkInfoFoo: data.foo,
parkInfoBar: data.bar
})
})
}
}
render() {
const { currentId, parkInfoFoo } = this.state
if (currentId !== null) {
return <span>{parkInfoFoo}</span>
}
return null
}
}
export default Dashboard
I think it should be this way:
Your component ParkSelector changes, you trigger the action via dispatch to changeParkInfo action.
This action does the AJAX and on it's success
1) you update the store state of parkInfo via another action.
2) you send getDashboardStats.
Now when point (2) is success, it will update the store state dashboardStats.
Next in your Dashboard you should not connect with parkInfo, reason: you are not using parkInfo in the dashboard.
Inside the dashboard component you should call action getDashboardStats in componentDidMount() for loading up the dashboardStats for first time when component loads up.
The idea in nutshell is when data changes action should call the facade of actions and make the state change.
What you trying is trigger an action which changes state that goes into component via props that triggers another action so on.. thus there is a infinite loop as you have written the component action in componentWillUpdate.
Hope this clarifies your question.

Clearing specific redux state before rendering component

I have the following "Buy" button for a shopping cart.
I also have a component called Tooltip, which will display itself for error/success messages. It uses the button's width to determine it's centre point. Hence, I use a `ref since I need to access it's physical size within the DOM. I've read that it's bad news to use a ref attribute, but I'm not sure how else to go about doing the positioning of a child component that is based off the physical DOM. But that's another question... ;)
I am persisting the app's state in localStorage. As seen here:
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
The issue I'm running into is that I have to clear the state's success property before rendering. Otherwise, if I have a success message in the state, on the initial render() the Tooltip will attempt to render as well. This won't be possible since the button it relies on is not yet in the DOM.
I thought that clearing the success state via Redux action in componentWillMount would clear up the success state and therefore clear up the issue, but it appears that the render() method doesn't recognize that the state has been changed and will still show the old value in console.log().
My work-around is to check if the button exists as well as the success message: showSuccessTooltip && this.addBtn
Why does render() not recognize the componentWillMount() state change?
Here is the ProductBuyBtn.js class:
import React, { Component } from 'react';
import { connect } from 'react-redux'
// Components
import Tooltip from './../utils/Tooltip'
// CSS
import './../../css/button.css'
// State
import { addToCart, clearSuccess } from './../../store/actions/cart'
class ProductBuyBtn extends Component {
componentWillMount(){
this.props.clearSuccess()
}
addToCart(){
this.props.addToCart(process.env.REACT_APP_SITE_KEY, this.props.product.id, this.props.quantity)
}
render() {
let showErrorTooltip = this.props.error !== undefined
let showSuccessTooltip = this.props.success !== undefined
console.log(this.props.success)
return (
<div className="btn_container">
<button className="btn buy_btn" ref={(addBtn) => this.addBtn = addBtn } onClick={() => this.addToCart()}>Add</button>
{showErrorTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--error'} messageObjects={this.props.error} />
}
{showSuccessTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--success'} messageObjects={{ success: this.props.success }} />
}
</div>
);
}
}
function mapStateToProps(state){
return {
inProcess: state.cart.inProcess,
error: state.cart.error,
success: state.cart.success
}
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (siteKey, product_id, quantity) => dispatch(addToCart(siteKey, product_id, quantity)),
clearSuccess: () => dispatch(clearSuccess())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductBuyBtn)
Well, it seems to be a known problem that's easy to get into (harder to get out of, especially in a nice / non-hacky way. See this super-long thread).
The problem is that dispatching an action in componentWillMount that (eventually) changes the props going in to a component does not guarantee that the action has taken place before the first render.
So basically the render() doesn't wait for your dispatched action to take effect, it renders once (with the old props), then the action takes effect and changes the props and then the component re-renders with the new props.
So you either have to do what you already do, or use the components internal state to keep track of whether it's the first render or not, something like this comment. There are more suggestions outlined, but I can't list them all.

Categories

Resources