React Redux: Delay between component inner content while updating - reactjs

I have a component that have 2 components inside of it:
MyComp {
render (
html of My Comp..
<Loading show={this.props.isLoading}/>
<ErrorMessage show={this.props.hasError}/>
)
}
When it is receiving data, it shows the Loading.
When the loading is complete, it receive something like:
{
isLoading: false,
hasError: true
}
But in screen, the loading close like 2s before the hasError displays.
Both components are built in the same strategie:
class Loading extends Component {
constructor(props) {
super(props);
this.state = {isLoading : props.show};
}
componentWillReceiveProps(nextProps) {
this.setState({ isLoading: nextProps.show });
}
render() {
if (this.state.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
export default Loading;

Not exactly an answer for this issue, as i can't be sure where the delay can come from.
But according to your code i would suggest to not use a local state and try to sync it with external props.
This can lead to bugs (maybe related to your issue?) as componentWillReceiveProps can get invoked even when no new props received, beside it is in deprecation process since react V16.2.
Instead i would just read directly from this.props:
class Loading extends Component {
render() {
if (this.props.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
Again, not sure it is directly related to your issue but it is a better practice.

Related

Infinite Looping Errorr in ReactJS

Layout.js
import React, {Component} from "react"
import Aircraft from "./Aircraft"
class Layout extends Component {
constructor(props) {
super(props)
this.state = {
test: true,
acn: ""
}
}
WhatIsAircraftName = (acn) => {
this.setState({
acn: acn
})
}
render() {
return (
<div>
<div className="mainD posRel hidO">
<div className="posRel hidO topD">
</div>
<div className="posRel hidO bottomD container">
<Aircraft clk={this.WhatIsAircraftName} />
</div>
</div>
</div>
)
}
}
export default Layout
Aircraft.js
import React, {Component} from "react"
import Loading from "./Loading"
class Aircraft extends Component {
constructor(props) {
super(props)
this.state = {
aircraft: [],
loading: false,
utilized: 0
}
}
componentDidMount() {
let mod_this = this
this.setState({
loading: true
})
fetch("https://some-endpoint")
.then(function(response) {
if (response.ok) {
return response.json()
}
})
.then(function(myJson) {
mod_this.setState({
aircraft: myJson,
loading: false
})
})
}
DisplayAircraft() {
let ac = this.state.aircraft
this.props.clk(ac.data[0].ident)
return (
<div className="acD posRel hidO selected">
{
<h2>{ac.data[0].ident}</h2>
}
</div>
)
}
render() {
const {aircraft} = this.state
return (
<div className="posRel hidO leftD">
<h1>Aircrafts</h1>
{
!aircraft || aircraft.length <= 0 || this.state.loading ?
<Loading /> :
this.DisplayAircraft()
}
</div>
)
}
}
export default Aircraft
When I run my app, I get setState loop error:
Unhandled Rejection (Invariant Violation): Maximum update depth
exceeded. This can happen when a component repeatedly calls setState
inside componentWillUpdate or componentDidUpdate. React limits the
number of nested updates to prevent infinite loops.
The reason I am doing it like this is the Aircraft component will get the Aircraft ID which I want to send to another child component, hence I am sending it to the parent to use it as props for another component.
Section is:
Layout.WhatIsAircraftName [as clk]
The problem is that your render method is not pure and is updating state.
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 directly interact with the browser. - React Docs
In your parent component, you have WhatIsAircraftName which is calling setState. Whenever this method is invoked, it will trigger a state update which will trigger a render. In the parent's render you are passing the Aircraft component the prop clk which is assigned to WhatIsAircraftName. Then, in Aircraft's render, it's calling DisplayAircraft, which is invoking the prop clk which starts us back at the top.
Layout#render -> Aircraft#render -> DisplayAircraft -> this.props.clk -> WhatIsAircraftName -> this.setState -> Layout#render -> etc. (infinite loop).
This loop needs to be broken, setState should never be invoked within render.
Try something like this, I've moved the logic around that you are no longer updating the state from the render function.
import React, {Component} from "react"
import Loading from "./Loading"
class Aircraft extends Component {
constructor(props) {
super(props)
this.state = {
aircraft: [],
loading: false,
utilized: 0
}
}
componentDidMount() {
let mod_this = this
this.setState({
loading: true
})
fetch("https://some-endpoint")
.then(function(response) {
if (response.ok) {
return response.json()
}
})
.then(function(myJson) {
this.props.clk(myJson.data[0].ident)
mod_this.setState({
aircraft: myJson,
loading: false
})
})
}
render() {
const {aircraft} = this.state
return (
<div className="posRel hidO leftD">
<h1>Aircrafts</h1>
{
!aircraft || aircraft.length <= 0 || this.state.loading ?
<Loading /> :
(<div className="acD posRel hidO selected">
<h2>{aircraft.data[0].ident}</h2>
</div>)
}
</div>
)
}
}
export default Aircraft
This should at least get it working for you but based on what I can see in your code I do have another suggestion. It would be much simpler to do the API call from the Layout component and then pass the aircraft information down to the Aircraft component via Props. In your current code you are having to pass the data back up via a function you passed via a prop which makes it all a little more complicated. React tends to be much easier to work with when you are passing data down the chain.
Hope that helps!

Unable to prevent rerender with shouldComponentUpdate() or with PureComponent

In /home/:id I have a <LogButtons/> when its clicked logOn() get called so logsignPopUp variable become a <logForm/> component.
In the same page I have a <IframeYoutubeComponent/>, I want to prevent it to rerender when the <logForm/> pop on the screen so the video isn't reloaded.
home.js :
export default class Home extends Component {
constructor(props) {
super(props);
this.state = { logsign: "" };
this.logOn = this.logOn.bind(this);
this.signOn = this.signOn.bind(this);
}
logOn() {
this.setState({ logsign: "log" });
}
render() {
let logsignPopUp = this.props.logsign === "log" ? <LogForm/> : this.state.logsign;
let homePage =
<div>
{logsignPopUp}
<div>
<LogButtons logOn={this.logOn}/>
</div>
<div>
<IframeYoutubeComponent paramsId={this.props.match.params.paramsId}/>
</div>
</div>;
return (
<div>
<Route exact path="/home/:id" component={() => <div>{homePage}</div> } />
</div>
);
}
}
iframeYoutubeComponent.js :
export class IframYoutubeComponent extends Component {
render() {
//this.props.youtube come from Redux state
let src = this.props.youtube.find(el => el.youtubeId === this.props.paramsId);
return (
<iframe src={"https://www.youtube.com/embed/" + src}></iframe>
);
}
}
I tried to return false in shouldComponentUpdate() but its not even called :
shouldComponentUpdate() {
console.log("test");
return false;
}
I tried to use a PureComponent for <IframeYoutubeComponent/> but the video still reload when the <logForm/> pop.
I tried to add key to my components and also tried to put this.state.logsign in Redux but nothing worked out.
I started react since 2 months so I might miss something obvious but I can't find out what... Any idea ?
That's because you are passing an arrow function in a component prop to the Route. This way everytime you generate a new function.
You should pass a react component in this prop or at least a function that returns JSX but this function should be defined once. For example as a class method.
Something like:
<div>
<Route exact path="/home/:id" component={this.renderHomePage} />
</div>
But then of course you have to refactor your logic regarding this logsign prop.

React State Not Updating with Conditional Statement

I have this really strange issue with React.
The following below WORKS. It calls the action creator fetchUserForm which then fetches an object and sets it to the redux storage called userForm. userForm is then called in step1Component if it is loaded.
class FormEdit extends Component {
constructor(props) {
super(props)
this.nextPage = this.nextPage.bind(this)
this.previousPage = this.previousPage.bind(this)
this.updateComponents = this.updateComponents.bind(this)
this.state = {
page: 1,
}
}
componentWillMount() {
this.props.fetchUserForm(this.props.params.id);
}
render() {
const { page } = this.state
return (
<div>
{page === 1 && <Step1Page nextPage={this.nextPage}/>}
</div>
)
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators({fetchUserForm}, dispatch);
}
function mapStateToProps(state) {
return { userForm: state.newForm.userForm,
};
}
export default connect(null, mapDispatchToProps)(FormEdit);
Reducer:
const INITIAL_STATE = {
userForm:'false'
};
case FETCH_USER_FORM:
console.log("----------------> REDUCER: updating custom form");
console.log(action.payload);
return {...state, userForm: action.payload };
Step1Page Component:
render() {
if(!this.props.userForm){
return(
<div> LOADING </div>
)
}
return(
<div> Actual Content </div>
)
The above works perfectly. However, this is where my strange issue occurs. I want to do something with the userForm in the FormEdit component. I can't use the form until it fully loads so I add this to FormEdit:
if(!this.props.userForm){
return(
<div> LOADING </div>
)
}
return(
<div> "Actual Content </div>
)
EXCEPT when I add this to FormEdit, I'm stuck at the LOADING div forever. When I view the react tools, it says that the userForm is set to false.This makes no sense because when I view the console.log it says:
Which means it was passed to the reducer. However, even when it's passed, looking at react tools it says that the userForm is still "false".
IF I remove the conditional in FormEdit, everything works again and the userForm is filled with the objects. So I'm really confused why the conditional in my FormEdit component is causing such an issue. When it's not added, everything works fine. But when it is added, the reducer state remains false.
In FormEdit you don't have the userform property.
You have to pass mapStateToProps into the connect function.
export default connect(mapStateToProps, mapDispatchToProps)(FormEdit);

React Redux - pass data down to components via props or connect

I'm working on React Redux app and I have quite fundamental question about some kind of best practises.
I have MainComponent (kind of container) where I'm fetching data on componentDidMount:
class MainComponent extends React.Component {
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
render() {
return (
<div>
<ChildComponent1 />
<ChildComponent2 />
</div>
)
}
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
How to pass fetched data to ChildComponents? What is the best practise? Two possible solutions are (IMHO - maybe more?)
First solution:
class MainComponent extends React.Component {
...
render() {
return (
<div>
<ChildComponent1 dataOne={this.props.data.one} />
<ChildComponent2 dataTwo={this.props.data.two} />
</div>
)
}
...
Second solution - connect ChildComponents to store which is updated by fetchDataAction() in MainComponent:
class ChildComponent1 extends React.Component {
render() {
return (
<div>
{this.props.one}
</div>
)
}
}
function mapStateToProps(state){
return (
one: state.one
)
}
export default connect(mapStateToProps,null)(ChildComponent1)
Now I use first solution when ChildComponents do not fire actions which update store and second solution when they do. But I'm not sure if it is proper approach.
If you have multiple child components and you have to pass a part of fetched data to different child components ; I would suggest keep the parent component as single point of source.
You can try something like:-
class MainComponent extends React.Component {
constructor(){
super()
this.state = {
data : {}
}
}
componentDidMount(){
this.fetchData()
}
fetchData() {
this.props.fetchDataAction()
}
componentWillReceiveProps(nextProps){
//once your data is fetched your nextProps would be updated
if(nextProps.data != this.props.data && nextProps.data.length>0){
//sets your state with you data--> render is called again
this.setState({data:nextProps.data})
}
render() {
//return null if not data
if(this.state.data.length === 0){
return null
}
return (
// it should have keys as one and two in api response
<div>
<ChildComponent1 data={data.one}/>
<ChildComponent2 data={data.two}/>
</div>
)
}
}
function mapStateToProps(state){
return (
data: state
)
}
export default connect(mapStateToProps,{fetchDataAction})(MainComponent)
I feel all logic stays at one place this way. Say if you plan to add to add few more child components in future,you only need to add a line of code above and few changes in API. However if you read in each component you have connect that component to store again which makes it more complex.
So if you dont have any other logic in your child components apart from getting data I would keeping this logic in the parent component.

Flux / Flummox: Component stops listening the Store after first router transition

I've just started using flummox and I'm a little bit flummoxed :)
Here is my use case.
App Layout
<section id="layout">
<Header {...this.props} />
<RouteHandler {...this.props} />
<Footer />
<Alert {...this.props} />
</section>
In my App I have Alert Component. When something happens I trigger an AlertAction from some component, it dispatches alert payload to AlertStore, which gets updated, and AlertComponent shows alert ( + hides it after some time).
For example I have a PostEdit Component. After form submit, I send request to API from PostActions and receive response, which is dispatched to PostStore. Store gets updated and PostEdit Component gets notified. In PostEdit's componentWillReceiveProps I check props, received from the store, and trigger AlertAction to show the alert.
2 problems:
I have to use setTimeout to trigger AlertAction from the Post Component to make alert things happen (code below).
And the main problem is that Alert Component stops listening AlertStore after the first react-router transition.
Here is console.log, illustrating the problem:
One more strange thing is that changed-store-notification in console.log printed before dispatch-payload-from-action-notification (which causes this store change).
Here are code snippets:
AlertHandler.jsx
export default class AlertHandler extends React.Component {
// constructor()
render() {
return (
<FluxComponent connectToStores={'alerts'}>
<Alert {...this.props} />
</FluxComponent>
);
}
}
Alert.jsx
export default class Alert extends React.Component {
// constructor()
// _handleShow()
// _handleHide()
componentDidMount() {
this.props.flux.getStore('alerts').addListener('change', function() {
console.log('Changed!', this.state);
});
}
componentWillUnmount() {
console.log('Not gonna happen');
}
// render()
}
PostEdit.jsx
export default class PostEdit extends React.Component {
// constructor()
componentWillReceiveProps(newProps) {
this.setState({
isLoading: false
}, () => {
if (newProps.errors) {
// without `setTimeout` nothing happens
setTimeout(() => {
newProps.flux
.getActions('alerts')
.showErrorAlert(newProps.errors);
}, 1);
} else {
setTimeout(() => {
newProps.flux
.getActions('alerts')
.showSuccessAlert();
}, 1);
}
});
}
_submitPost(e) {
// doing stuff...
// triggering updatePost action
this.props.flux
.getActions('posts')
.updatePost(post);
}
// render()
}
Not sure are these bugs or I missed smth in flux/flummox patterns and do things wrong. Thanks for feedback!

Resources