I am working on a MERN application where people can add user created content. I have incorporated a basic loading functionality to give the user feedback when fetching data from the server. However I am hoping for some improvements for better performance and reducing loading times.
My current approach:
As an example I will explain my PostComponent which will request user created posts from the database through a dispatched redux action (getPosts). The posts are rendered in a child component PostFeed and subsequently mapped into individual PostItem components.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import PostForm from './PostForm';
import PostFeed from './PostFeed';
import Spinner from '../common/Spinner';
import { getPosts } from '../../actions/postActions';
class Posts extends Component {
componentDidMount() {
this.props.getPosts();
}
render() {
const { posts, loading } = this.props.post;
let postContent;
if (posts === null || loading) {
postContent = <Spinner />;
} else {
postContent = <PostFeed posts={posts} />;
}
return (
<div className="feed">
<div className="container">
<div className="row">
<div className="col-md-12">
<PostForm />
{postContent}
</div>
</div>
</div>
</div>
);
}
}
Posts.propTypes = {
getPosts: PropTypes.func.isRequired,
post: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post,
});
export default connect(mapStateToProps, { getPosts })(Posts);
Before the posts are loaded, I am setting a boolean loading to true. After getting all the posts, loading is set to false again. The if statement in the component above is rendering a spinner which is a .gif file or the PostFeed component depending on the state of the loading boolean.
postActions:
...
export const setPostLoading = () => {
return {
type: POST_LOADING,
};
};
export const getPosts = () => (dispatch) => {
dispatch(setPostLoading());
axios
.get('/api/posts')
.then(res => dispatch({
type: GET_POSTS,
payload: res.data,
}))
.catch(err => dispatch({
type: GET_POSTS,
payload: null,
}));
};
...
postReducer:
...
export default function (state = initialState, action) {
switch (action.type) {
case POST_LOADING:
return {
...state,
loading: true,
};
case GET_POSTS:
return {
...state,
posts: action.payload,
loading: false,
};
...
This all works fine this way but I have two concerns about this method:
First, using spinners while loading components makes things jump around on the page a bit. While not necessarily a bad thing, I like to create the most seamless user experience and find implementing placeholders a more elegant solution.
Placeholder Loading Example
How do I achieve implementing placeholder items for each separate component so content does not jump around and stays nicely postioned while loading?
Second, When loading the posts, it is loading all available posts that are stored in the database. I can imagine when having excessive amounts of posts, loading all the individual PostItems might take too long and take a lot of unnecessary bandwidth.
How could I add the functionality to only load the content displayed in the browser window so more are loaded when the user scrolls down? Probably by scroll eventhandler? But how does it decide which PostItem to render and which to keep as placeholders?
Hope I explained it clear enough, still relatively new to react/redux so there could some mistakes here. Any tips, suggestions or best practices are welcome.
Thanks a lot!
You are asking a number of questions here. First, it sounds like you've come to grips with needing your "skeleton" to display for each component, and the good news is there are several solutions available such as react-content-loader which uses customizable SVG, react-placeholder, (or you can roll your own using react-transition-group. The documentation will explain how you manage the transition per component as you requested.
With respect to the "excessive amount" of items coming back from your axios API call, it's hard to say without a knowing a rough number (100s, 1000s, more, etc.) and if you've found a performance issue with how much data you're returning. If the number ends up being problematic, what you are looking for is called "virtual (or infinite) scrolling" and pagination where you're not requesting ALL of the data at once, but rather in chunks. For example, StackOverflow uses paging, the new Reddit uses infinite scroll. Again, there are NPM packages for that too such as react-tiny-virtual-list.
Related
Below is a snippet of code to fetch data from url by axios,
import React, { useState, setEffect, useEffect } from 'react';
import axios from "axios";
import LoadingPage from "./LoadingPage";
import Posts from "./Posts";
const url = "https://api-post*****";
function App() {
const [posts, setPosts] = useState([]);
const fetchPost = async() => {
try {
const response = await axios(url);
return response.data;
} catch (error) {
console.error(error);
}
};
let data = fetchPost();
setPosts(data);
return (
<main>
<div className="title">
<h2> Users Posts </h2>
{posts.length
? <Posts posts={posts} />
: <Loading posts={posts} />
}
</div>
</main>
);
}
export default App;
However, it got the error of
uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite
Question 1: How could this be of too many re-render, there is no loop or something?
To solve this bug, we can use below changes:
const [posts, setPosts] = useState([]);
const fetchPost = async () => {
try {
const response = await axios(url);
setPosts(response.data);
} catch (err) {
console.error(err);
}
};
useEffect(()=> {
fetchPost();
}, [posts])
Question 2: how the useEffect work to avoid too many calls?
Question 3: I always treat react hooks under hood as web socket communications, etc. If that is the case?
When you call setPosts the component will render again, and fetch the data again, at which point you set state with the data forcing a new render which fetches the data...etc.
By using useEffect you can fetch the data, and set state once when the component is first rendered using an empty dependency array.
useEffect(() => {
// Fetch the data
setposts(data);
}, []);
You probably don't want to watch for posts in this useEffect (you can have many) like you're doing in your updated example because you may run into the same issue.
I will only answer number one.
Caveat: the answer is a bit long.
I really hope it will help you to understand a topic that took me a long time to grasp.
Answer one:
To answer this question we should ask our selves two things, a) what is a side effect? and b) how the life cycle of components works?
so, we know that React functional component are pure functions and they should stay that way, you can pass props as parameters and the function will do stuff and return JSX, so far so good.
and for the second part we cannot control React virtual DOM, so the component will render many times during it's lifecycle, so imagine that the virtual DOM decided to check the code and compare between the virtual DOM and the real DOM and in order to do that he will have to check and "run" the code that resides inside that specific component.
the virtual DOM will run the API call, he will find a different result which will cause a new render to that specific component and this process will go on and on as an infinite loop.
when you are using usEffect you can control when this API call will take place and useEffect under the hood makes sure that the this API call ran only one your specific change take place and not the virtual DOM V.S real DOM change.
to summarize, useEffect basically helps you to control the LifeCycle of the component
Please first check your state like this.
useEffect(()=> {
fetchPost();
}, [posts]);
The Problem:
Every time I navigate between the list page (home) and detail page my site starts to slow down drastically. With this I mean, after like 3 times going back and forth I start noticing it having hiccups, after 5-6 times my whole pc starts to freeze.
My Project:
It's a Vue Project, with currently only 2 routes. The homepage is a list of items and a detail page for every item on the list. The detail page (specifically the tree component) is probably where the issue is because when I remove this, the problem is gone. I put some code at the bottom of this post with the basic structure of the project.
What I'm looking for:
Since I'm not getting any errors, I'm not sure where the problem is here. There is probably something I can do better in the way my project is set up, the way I load/show things. So I'm looking for ways to find out where the problem is at.
What I tried:
Stay Alive
While searching for solutions I came across the <stay-alive> tag. I tried putting this around my <router-view>. This does get rid of the slowing down, but I also lose all my dynamic content. The data on all pages is now the same when I navigate between different detail pages.
Data Fetching (https://router.vuejs.org/guide/advanced/data-fetching.html#fetching-after-navigation)
I was thinking, maybe it helps if I load all the data before someone enters a route or do some kind of loading before I show the page. This did not help.
It's possible that one of these things is the right direction, and I just didn't implement it right. Not 100% confident with my coding yet :)
views/home.vue
Just a simple page with a list of items that link to the detail page
<template>
// list with items that link to their corresponding detail page
</template>
import { mapState } from 'vuex'
export default {
name: 'Home',
computed: {
...mapState([
'builds'
])
}
}
views/details.vue
Now, this page is a little more complex. The big thing on this page is a canvas that is generated with Pixi Js, this canvas changes while the user is scrolling through the page. The Canvas element is its own component, so I pass some data with a prop.
<template>
<div class='page-wrapper'>
<div class="content-container">
<section class="level milestone" v-for="level in build.guide" :key="level.id" :id="level.level">
// Some stuff to display
</section>
<div class="sidebar">
<tree :myprop="current"></tree>
</div>
</div>
</div>
</template>
import { mapState } from 'vuex'
import Tree from '#/components/tree'
export default {
name: 'Build',
watch: {
currentActive: {
// To know where to user currently is
// Pass this data to my tree component
}
},
computed: {
...mapState([
'builds'
])
}
}
components/tree.vue
This is where my canvas is drawn with the help of data from a JSON file.
<template>
<div id="tree">
</div>
</template>
import axios from 'axios'
import * as PIXI from 'pixi.js'
import { Viewport } from 'pixi-viewport'
export default {
name: 'Tree',
props: ['myprop'],
data () {
return {
app: new PIXI.Application({ transparent: true, antialias: true }),
treeData: {},
// Some more
}
},
watch: {
myprop: function (newVal) {
// Some stuff with my prop
}
},
created () {
axios.get('/data/data.json', {
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
this.treeData = response.data
this.loadTree()
})
.catch(function (error) {
console.log(error)
})
},
methods: {
loadTree () {
// Load images and draw the canvas
PIXI.loader
.add('Image', require('#/assets/image.png'))
.load(this.drawTree)
},
drawTree () {
// Do all the drawing on the canvas
}
}
}
Oke, so i've kind of solved it for now. I'm still new to all this, but i started to look more into working with dev tools so i can find out where issues like this come from. I think i still can win a lot with this, but for now it helped to destroy my PIXI app from data when i'm done with it.
beforeDestroy () {
this.app.destroy()
}
Maybe some usefull links if someone ever finds this thread that has similar issues:
Using dev tools to find memory leaks:
https://www.youtube.com/watch?v=Hr2vrhrNaRo
https://www.youtube.com/watch?v=RJRbZdtKmxU
Avoiding memory leaks in vue:
https://v2.vuejs.org/v2/cookbook/avoiding-memory-leaks.html
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.
In my App I use several Container components. In each Container, there are Buttons.
Depending on the state of the App, the Buttons are clickable (or not). Whether a Button is disabled or not, is managed in the local state of each Container.
The results and state of the App can be saved and loaded.
And here comes the problem:
When I save (or load) the App, its rather hard to "extract" the state of the Buttons from each Container. Saving in the global state (Redux)is rather easy.
But how can I save the local state from each Container and how can I feed it back to each Container?
Reading the local state is managed through a parent Component which calls methods from a child Component. I am aware that this is an antipattern, but it works.
export class SomeConmponent {
....
onClickSaveProjecthandler(event) {
const localStateProjectSettings = this.childProjectSettings.getLocalState();
const localStateLayerFilter = this.childLayerFilter.getLocalState();
return {
"ProjectSettings": localStateProjectSettings,
"Filter": localFilter
};
}
render() {
return(
<ProjectSettingsContainer onRef={ref => (this.childProjectSettings = ref)}/>
)
}
}
Any better suggestions?
As you already mentioned, using redux to have a single point of truth is a great ideia. And to "feed" the state back to containers, you have to map state and props to your components.
This is a container example brought from the oficial doc:
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
}
}
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(Link)
export default FilterLink
The connect does all the magic.
Sounds like you could use Redux and you somehow miscomprehended its architecture as being synonymous with global state only. One of the reasons Redux was made was to address the issue of saving states of multiple independent components.
However, here's an entirely different take on the answer: why not use a global state serializer and connect each component to it? If you don't like the idea of referring to a global variable, a better alternative would be to create some sort of dependency injection (DI) framework that works with React. I've created a library a while back, called AntidoteJS, that does exactly this. You don't need to use it, but it shows how you how it can be done. Just take a look at the source.
Here is something quick and dirty. I haven't tested it, but it shows the basic idea:
import { inject } from "antidotejs"
export class MyComponent extends React.Component {
#inject("StateSerializer")
serializer
constuctor(props, children) {
super(props, children);
this.serializer.load(props.id);
}
setState(newState) {
super.setState(newState);
this.serializer.save(newState);
}
}
I understand the concept of Redux's actions, reducers, and mapping to stores.
I have been able to successfully execute Redux into my app.
I was going along merrily using React's contextTypes for child components that needed data from Redux that had been called before.
Then I ran into a strange situation where the data was mutated by a child. When I posted the problem on SO, a member told me I should be using contextTypes sparingly anyway.
So the only way to overcome my problem was map to stores, AGAIN, in the child's parent, like a higher component of the parent had done earlier, and pass that data to the child as props.
But that seems all wrong to me. Mapping to the same store again? Why? What am I not understanding? Why do I have to write this on every component that needs the same data another component mapped to?
export default class Foo extends Component {
.....
// I DID THIS STUFF IN A HIGHER COMPONENT.
// WHY MUST I REPEAT MYSELF AGAIN?
// WHAT AM I NOT UNDERSTANDING?
static propTypes = {
children: PropTypes.node.isRequired,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchProductsIfNeeded());
}
.....
}
const mapStateToProps = (state) => {
const {productsReducer } = state;
if (!productsReducer) {
return {
isFetching: false,
didInvalidate: false,
error: null,
products: []
};
}
return {
error: productsReducer.error,
isFetching: productsReducer.isFetching,
didInvalidate: productsReducer.didInvalidate,
products: productsReducer.products
};
};
export default connect(mapStateToProps)(Foo);
I looked at containers, but it appears to me that containers wrap all dumb components in them at once as such ...
<ProductsContainer>
<ProductsComponent />
<ProductSpecialsComponent />
<ProductsDiscountedComponent />
</ProductsContainer>
And that is not what I want. I thought, like a service I could use that container in each respective dumb component as a such ....
<ProductsContainer>
<ProductsDiscountedComponent />
</ProductsContainer>
<ProductsContainer>
<ProductSpecialsComponent />
</ProductsContainer>
<ProductsContainer>
<ProductsComponent />
</ProductsContainer>
Right now in order to get my 3 sub components illustrated above, each one of them has to map to stores and that just seems all wrong.
I cannot find anything that I can grasp as a solution.
Question:
Is there a way I can map to a particular store just once, and call on that "service" for those components that need that data?
If so, examples would be appreciated.
Post Script:
I though perhaps if I could perform the 'mapping service' as a pure JavaScript function o/s of react, and just import that function in the components that need it, that would solve the problem, but I have not seen any examples of Redux stores being mapped o/s React.
UPDATE:
I posted the solution here ......
React-Redux - Reuseable Container/Connector
First, an aside about your past problems. It's true that context is not appropriate for something like this. You should also be worried about the mutation you mentioned. If you're using a Redux store, the data that exits it should always be immutable. Perhaps a library like Immutable.js would help there.
Now let's turn to the matter at hand. Perhaps what you aren't fully grokking is what a "dumb" component is. A dumb component should be stateless and a pure:
const Product = ({ name, comments }) => (
<div>
<h1>{name}</h1>
<CommentsList comments={comments} />
</div>
);
The component gets everything it needs from props. Now there are a number of ways to get data into this component, but they are all based on props. For example, the following is the most straightforward:
const ProductList = ({ products }) => (
<div>
{products.map( p => <Product product={product} /> )}
</div>
);
class App extends Component {
getInitialState () {
return { products: [] };
}
componentDidMount () {
// connect to store, blah blah...
}
render () {
return (
<div>
{/* blah blah */}
<ProductsList products={this.state.products} />
{/* blah blah */}
</div>
);
}
}
As you can see from the example, the entire components tree will get its state from props that are simple passed down from one connection to the store. Aside from App, all components are dumb, stateless, and predictable.
But there are also cases where connecting the entire tree through props is impractical and where we need localized connections to our stores. That's where HOCs can be hugely helpful:
const WithProducts = Comp => class WrappedComponent extends Component {
getInitialState () {
return { products: [] };
}
componentDidMount () {
// connect to store, blah blah...
}
render () {
return (
<Comp products={this.state.products} {...this.props} />
);
}
}
const ProductListWithProducts = WithProducts( ProductList );
Now any component we so wrap will receive the list of products from the store as a prop - no code duplication required. No repeating yourself. Notice how I did not alter the ProductList or Product components to make this work: those components are too dumb to care.
The majority of the components in any React app you create should be so dumb.
As another aside, you should not be worried about calling your store more than once. If you are worried about that, there's something wrong with the store implementation because calls to stores should be idempotent. You can use actions and so forth to populate the stores, but that should be wholly independent from getting values from stores. There should be no performance or network penalty form well-design store retrievals (and, again, using libraries like Immutable can help here too).