Why do i have a problem rendering this 'Loading' component in react? - reactjs

I have 2 components which use the same data which I am fetching from a json-server.
One component uses the data as shown below:
function Home(props) {
return (
<div className='container'>
<div className='row align-items-start'>
<div className='col-12 col-md m-1'>
<RenderCard item={props.dish}
isLoading={props.dishesLoading}
errMess={props.dishErrMess} />
</div>
<div className='col-12 col-md m-1'>
<RenderCard item={props.promotion}
isLoading={props.promoLoading}
errMess={props.promoErrMess} />
</div>
<div className='col-12 col-md m-1'>
<RenderCard item={props.leader}
isLoading={props.leaderLoading}
errMess={props.leaderErrMess} />
</div>
</div>
</div>
);}
The other component uses the data as shown below:
const leaders = props.leaders.map((leader) => {
return (
<RenderLeader leader={leader} isLoading={props.isLoading}
errMess={props.errMess} />
);
});
Both the RenderLeader and RenderCard components have a similar structure with an if-else loop to display loading animation or error messages along with the actual content.
function RenderLeader({ leader, isLoading, errMess }) {
if (isLoading) {
return (
<Loading />
);
}
else if (errMess) {
return (
<h4>{errMess}</h4>
);
}
else
return (CONTENT)
The problem is that the loading animation and the error message are being displayed for the Home component but NOT for the other component which has exactly similar structure. Moreover, the data is being actually fetched for the second component, its just that it wont display the loading animation and error messages. What is wrong with this ?
Edit
This is how i invoke them both:
<Home
dish={this.props.dishes.dishes.filter((dish) => dish.featured)[0]}
dishesLoading={this.props.dishes.isLoading}
dishErrMess={this.props.dishes.errMess}
promotion={this.props.promotions.promotions.filter((promo) => promo.featured)[0]}
promoLoading={this.props.promotions.isLoading}
promoErrMess={this.props.promotions.errMess}
leader={this.props.leaders.leaders.filter((leader) => leader.featured)[0]}
leaderLoading={this.props.leaders.isLoading}
leaderErrMess={this.props.leaders.errMess}
/>
<Route path='/aboutus' component={() => <About leaders={this.props.leaders.leaders}
isLoading={this.props.leaders.isLoading}
errMess={this.props.leaders.errMess} />} />

Home Component works fine since it renders individual cards. Whereas for the below structure,
const leaders = props.leaders.map((leader) => {
return (
<RenderLeader leader={leader} isLoading={props.isLoading}
errMess={props.errMess} />
);
});
if props.leaders is empty, it cannot iterate and hence RenderLeader will never get called. Hence when props.isLoading is true, the props.leaders will be empty and the above arrow function will return nothing. And once the props.leaders is populated, the above function gets called, but props.isLoading will already be set to false.
That is why the loading icon or the error message was not displayed.
You can modify the calling as,
<div className="col-12">
<Media list>
<RenderLeaderList leaders={props.leaders} isLoading=
{props.leadersLoading} errMess={props.leadersErrMess}/>
</Media>
</div>
and create a new function called RenderLeaderList like;
function RenderLeaderList({leaders, isLoading, errMess})
{
if(isLoading)
{
return(
<Loading />
);
}
else if(errMess)
{
return(
<h4>{errMess}</h4>
);
}
else{
if(leaders!=null)
{
const leaders_map = leaders.map((leader) => {
return(
<div key={leader.id} className="col-12 mt-5">
<RenderLeader leader={leader}/>
</div>
);
});
return (
<div>
{leaders_map}
</div>
);
}
}
}
to make it work.
Hope it helps! :D

Related

In React 18, is useEffect's function guaranteed to run AFTER a Suspense promise completes?

I have a simple master-detail scenario where on the left side, I load a list of cities using useSwr with a REST service, then on the right side I have a city detail windows that also uses useSwr to load a single city (either from clicked on left, or first load).
In the code below, I'm calling the useEffect function, and then using the data retrieved from useSwr in a state setting call (setSelectedCityId).
This works, and there has always been data associated with the cities array, but I'm wondering if it is guaranteed that useEffect's function will run AFTER the Suspense promise is completed (like it seems to be).
Here is my simple code:
import { Suspense, useEffect, useState, useTransition } from "react";
import useSwr from "swr";
const fetcher = (...args) => fetch(...args).then((res) => res.json());
function CityDetailFallback() {
return <div>Loading (CityDetail)</div>;
}
function CityDetail({ selectedCityId }) {
function CityDetailUI({ selectedCityId }) {
const { data: city } = useSwr(
selectedCityId
? `http://localhost:3000/api/city/${selectedCityId}`
: null,
fetcher,
{
suspense: true,
}
);
if (!city) {
return <div>loading city...</div>
}
return (
<div className="row">
<div className="col-9">
<div>{JSON.stringify(city)} </div>
</div>
</div>
);
}
return (
<Suspense fallback={<CityDetailFallback />}>
<CityDetailUI selectedCityId={selectedCityId}></CityDetailUI>
</Suspense>
);
}
function CityList({
setSelectedCityId
}) {
//
const { data: cities } = useSwr("http://localhost:3000/api/city", fetcher, {
suspense: true,
});
useEffect(() => {
setSelectedCityId(cities[0].id);
}, []);
return (
<div className="col-3">
{cities.map((city) => {
return (
<div key={city.id}>
<button
onClick={(e) => {
setSelectedCityId(city.id);
}}
>
{city.city}
</button>
</div>
);
})}
</div>
);
}
export default function App() {
const [selectedCityId, setSelectedCityId] = useState();
return (
<div className="container">
Site Root
<hr />
<Suspense fallback={<div>Loading...</div>}>
<div className="row">
<div className="col-3">
<b>CITY LIST</b>
<hr />
<CityList
setSelectedCityId={setSelectedCityId}
selectedCityId={selectedCityId}
/>
</div>
<div className="col-9">
<div>
<b>CITY DETAIL (TOP ROW SELECTED AUTOMATICALLY)</b>
<hr />
<CityDetail selectedCityId={selectedCityId} />
</div>
</div>
</div>
</Suspense>
</div>
);
}
Note: I can't create a code sandbox because of a current bug in useSwr around suspense. https://github.com/vercel/swr/issues/1906 I'm testing currently with Create React App and using a dummy api endpoint for the REST calls. sorry :(
Yes, in React 18 useEffect always runs when the tree is consistent. So effects fire only after the tree is ready and not suspended.

How to pass Mobx store as props to react compoent

I have this app that uses mobx, in it there is a component called "Listings" that uses some state from mobx to render a list of items.
The way it is right now, is that the Listings component gets the data it needs(store.restaurantResults[store.selectedFood]) from inside of it by using the mobx store like so:
const Listings = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="pa2">
{store.restaurantResults[store.selectedFood] &&
store.restaurantResults[store.selectedFood].map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<p>{rest.name}</p>
</div>
);
})}
</div>
));
};
But i think this is wrong, as it couples the component with the data, I want instead to pass that data via props so it can be reusable.
What is the correct way to do this? Right now my App looks like this, where it's being wrapped around a storeProvider:
function App() {
return (
<StoreProvider>
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way"/>
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings />
</div>
</StoreProvider>
);
}
My idea is to extract everrything inside the StoreProvider into another component that has a store and returns the jsx via useObserver so that I can acces the store and then pass what i need as props to the other components. like this:
const Wapper = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way" />
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings listings={store.restaurantResults[store.selectedFood]} />
</div>
))
}
And then on the listings component change the hard coded store.restaurantResults[store.selectedFood] inside to use the props that is being passes now, that is called listigs like so:
const Listings = ({listings}) => {
const store = React.useContext(StoreContext);
return useObserver(() => (
store.loading
? <Loading />
: <div className="pa2">
<div className="flex flex-wrap">
{listings &&
listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
));
};
And this works, but is this the right way to go about this?
As <Listings/> can be provided with listing and loading you can:
const Listings = ({listings, loading}) => {
if(loading) return <Loading />
return (
<div className="pa2">
<div className="flex flex-wrap">
{listings && listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
);
}
No observables used, no useObservable required.
You want to useObservables on store for listings then no reason to wrap all components with useObservable. You should wrap <Listings/> only.
I usually define my store as a global, so every component has visibility of it:
class Store {
#observable myVar
}
global.store = new Store()
And in my components i just use it:
#observer
export default class MyComponent extends React.Component {
constructor () {
super()
store.myVar = 0
}
setMyVar (a) {
store.myVar += 1
}
render () {
return <button onClick={this.setMyVar}>
Clicked {store.myVar} times
</button>
}
}

Nextjs - getInitialProps make reload and delete store

I built a site with nextjs (with server express & API) and Reactjs.
I would like to create dynamic paginations because there is far too much result for statically generated, So I added server endpoint /publiations/page/:id, I put a getInitialsProps for keep the id in query
But actually, when I click on my main page /publications where my store is not empty to go to the next page (publications/page/1), the page reloads and the store is empty. How I can keep my store when I change route?
And here my publications/page/[id].js
const PublicationsPage = ({id}) => {
return (
<>
<MainMenu/>
<Search/>
<div className="flex">
<Sidebar fallback={<Loader/>}/>
<Cards type={'publications'} idPage={id} />
</div>
</>
)
}
PublicationsPage.getInitialProps = async function({ query: { id } }) {
return {
id: id
};
};
export default withAuthSync(PublicationsPage);
The cards components where i use the data of store :
components/Cards.js
const Cards = ({ idPage, cards, type }) => {
console.log(cards)
return (
<div className="cards">
<div className="content-filter-search">
<div className="content-newsearchresult">
<div className="global-name">Result: {cards.cards.length} articles found</div>
<div className="content-button-blue">
<Link href="/explorer">
<a>
<div className="button-blue">New search</div>
</a>
</Link>
</div>
</div>
<div className="content-filter">
{filters[idPage].map((item) => {
return <InputFilter key={item.id} data={item} callback={keepItems} />;
})}
</div>
</div>
<div className="wrapper-card">
<div className="cards-container">
{
!cards.loading ? cards.cards.slice(idPage * 9, idPage * 9 + 9).map((card) => (
<Card key={card.PMCID} data={card} />
)) : <Spinner color="black" size="100px" thickness={3} gap={5} speed="fast" />
}
</div>
</div>
<div className="text">
<Link href={'/publications/page/1'}><a>Next page</a></Link>
</div>
{
!cards.loading ? (
<div className="center">
<Pagination type={type} page={parseInt(idPage)} totalElement={cards.cards.length} numberPerPage={9} />
</div>
) : null
}
</div>
);
};
const mapStateToProps = state => ({
cards: state.cards,
})
export default connect(mapStateToProps)(Cards);
I use the same code for the route /publications and /publications/page/:id just I add the getInitialProps to keep the id of page. And I use connect redux in my Cards component
I have no error in the console just my store is reset because the page reload. I don't understand how I can make pagination with my store where is my data if when I change page the store is empty
Thanks

Rendering Parameterized Function Results

I have a function that I am using to check if there are results from a page load and if there are, then map the array and return a component and if there aren't then return a string stating there are no results. At the moment I have been able to write the function without any issue, but I can't seem to get return statement to load. Am I following the right path to returning the components or is there a better method?
The console logs return the correct info, but everything in the return() isn't appearing in the view.
export default class BlogKanbanLayout extends React.Component {
constructor(props) {
super(props);
this.resultsCheck = this.resultsCheck.bind(this);
}
resultsCheck(blogs, user) {
console.log("resultsCheck")
console.log(blogs)
console.log(blogs.length)
if(blogs.length === 0) {
<p>There are no results for your filter criteria.</p>
} else {
console.log("There are blog results")
console.log(blogs)
console.log(user)
blogs.map((blog, index) => {
console.log("blog map")
console.log(blog)
return (
<div className="row">
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
})
}
}
render() {
return (
<div className="col-md-12">
{this.resultsCheck(this.props.negativeBlogs, this.props.user)}
</div>
)
}
}
In your resultsCheck you forgot to return the result of your mapping.
Also, the key used in the map function needs to be given to parent element, which here is your div.
And using conditional rendering you can reduce your entire component to the following code for te exact same result :
export default class BlogKanbanLayout extends React.Component {
render() {
const { negativeBlogs, user } = this.props
return (
<div className="col-md-12">
{negativeBlogs.length ?
negativeBlogs.map(blog =>
<div className="row" key={blog.blogIdHash}>
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
:
<p>There are no results for your filter criteria.</p>
}
</div>
)
}
}
And since you are not using the state of your component you could even optimize it to a stateless one :
const BlogKanbanLayout = ({ negativeBlogs, user }) =>
<div className="col-md-12">
{negativeBlogs.length ?
negativeBlogs.map(blog =>
<div className="row" key={blog.blogIdHash}>
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
:
<p>There are no results for your filter criteria.</p>
}
</div>
You resultsCheck method need to return something, so you need to add the return statement before the two results
resultsCheck(blogs, user) {
console.log("resultsCheck")
console.log(blogs)
console.log(blogs.length)
if(blogs.length === 0) {
return <p>There are no results for your filter criteria.</p>
} else {
console.log("There are blog results")
console.log(blogs)
console.log(user)
return blogs.map((blog, index) => {
console.log("blog map")
console.log(blog)
return (
<div className="row">
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
})
}
}

How can I get a component to update on path change while using the same route and component?

When the user is viewing another users profile, and attempt to click their own profile link in the navbar, the component never updates. In both the Redux and React dev tools, it shows that the state has been updated correctly but the component doesnt seem to notice and update.
class App extends Component {
render() {
return (
<Router>
<div>
<Navbar />
<NavbarFix />
<Switch>
<Route exact path="/" component={Posts} />
<Route exact path="/submit" component={AuthRoute(Submit)} />
<Route exact path="/signup" component={AlreadyAuth(SignUp)} />
<Route exact path="/login" component={AlreadyAuth(LogIn)} />
<Route exact path="/user/:id" component={AuthRoute(User)} />
<Route exact path="/item/:id" component={AuthRoute(Item)} />
<Route exact path="/admin" component={AdminAuth(Admin)} />
<Route exact path="/banned" component={Banned} />
<Route component={NoMatch} />
</Switch>
</div>
</Router>
);
}
}
.
class User extends Component {
constructor(props) {
super(props);
this.state = {
posts: [],
user: [],
comments: []
};
}
componentDidMount() {
this.loadUser();
}
loadUser = () => {
API.findUserById(this.props.match.params.id)
.then(res => {
console.log(res.data);
this.setState({
user: res.data.user,
posts: res.data.user.Posts,
comments: res.data.comments
});
console.log(this.state)
})
.catch(err => console.log(err));
this.setState(this.state)
}
handleCommentDelete = id => {
API.deleteComment(id)
.then(res => this.loadUser())
.catch(err => console.log(err));
}
handlePostDelete = id => {
API.deletePost(id)
.then(res => this.loadUser())
.catch(err => console.log(err));
}
render() {
return (
<div>
<div className="container-fluid">
<div className="row">
<div className="col-4 user-data-container">
<div className="row">
<div className="col-12 text-center">
<h2>{this.state.user.username}'s Posts</h2>
</div>
</div>
<hr className="pb-4" />
<div className="row">
<div className="col-12">
{this.state.posts.length > 0 ?
this.state.posts.map(post => (
<PostContainer handledelete={this.handlePostDelete} post={{ ...post, User: this.state.user }} key={post.id} check={this.props.id} />
))
:
<h1>No Posts To Show!</h1>
}
</div>
</div>
</div>
<div className="col-4 user-data-container">
<div className="row">
<div className="col-12 text-center">
<h2>{this.state.user.username}'s Comments</h2>
</div>
</div>
<hr className="pb-4" />
<div className="row">
<div className="col-12">
{this.state.comments.length > 0 ?
this.state.comments.map(comments => (
<CommentContainer verified={this.state.user.verified} handledelete={this.handleCommentDelete} check={this.props.id} comment={comments} className="hover-effect single-comment" key={comments.id}/>
))
:
<h1>No Comments To Show!</h1>
}
</div>
</div>
</div>
<div className="col-4 user-data-container">
<div className="row">
<div className="col-12 text-center">
<h2>{this.state.user.username}'s Information</h2>
</div>
</div>
<hr className="pb-4" />
<div className="row">
<div className="col-12">
<UserContainer user={this.state.user}/>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
username: state.auth.username,
id: state.auth.id,
email: state.auth.email,
profileImg: state.auth.profileImg,
verified: state.auth.verified
};
};
export default withRouter(connect(mapStateToProps)(User));
I believe this has to do with the same route and component being used, so the change isn't actually noticed. Is there any way to fix this? To my understanding, the component should be updating on state change.
If the link is directing to the same route with just a different param, it will not remount/componentDidMount will not be called again.
So, you could use the componentWillReceiveProps(newProps) lifecycle function and look for newProps.match.params.id and compare it with this.props.match.id and if different call loadUser again.
**You would also need to change your loadUser function to accept an id param
(Param validation not included)
componentDidMount() {
this.loadUser(this.props.match.id);
}
loadUser = (id) => {
API.findUserById(id)..
…
}
componentWillReceiveProps(newProps) {
if(newProps.match.params.id !== this.props.match.id) {
this.loadUser(newProps.match.params.id)
}
}

Resources