I recently moved my static navigation bar from being a general html element to included in my React rendering for my page because I wanted to incorporate the ability to dynamically load notifications in a modal that can get triggered in the navigation. With this change, I have noticed that my navigation bar does not appear immediately when the page is loaded, but when componentDidMount() { this.fetchList(); } finishes loading.
I personally belief that this is because the navigation component is being set in the render() call involved with this API fetch and since this class is being set after the call is made, then the navigation will have to wait until the fetch comes back successfully or as a failure.
If this is true, does that mean that I need to set my navigation at a higher level to ensure it loads when the page loads with styling and non-react elements?
Here is my ReactDOM.render():
import React from 'react';
import ReactDOM from 'react-dom';
import AnnotationFeedContainer from './components/app/activity-feed/activity-feed.js';
ReactDOM.render(<AnnotationFeedContainer />, document.getElementById('annotation-card'));
Here is <AnnotationFeedContainer /> which is rendering my react elements (<Navigation /> is the component I am looking to load before and regardless of fetchList()):
import React from 'react';
import fetch from 'node-fetch';
import path from 'path';
import Navigation from '../navigation';
import AnnotationSearchForm from './annotation-search-form';
import OnboardingInformation from './onboarding/information';
import ActivityFeedNotifications from './notifications/notifications';
import AnnotationFeed from './annotation-card/annotation-card-feed';
import { API_ROOT } from '../config/api-config';
//GET /api/test and set to state
export default class AnnotationFeedContainer extends React.Component{
constructor(props, context) {
super(props, context);
this.state = this.context.data || window.__INITIAL_STATE__ || { annotations: [], isLoading: true, onboardingWelcome: false, notifications: [] };
}
fetchList() {
fetch(`${API_ROOT}` + '/api' + window.location.search, { compress: false })
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
this.setState({ annotations: data.annotation, user: data.user, csrf: data.csrfToken, isLoading: false, onboardingWelcome: data.onboardingWelcome, notifications: data.notifications, feedPreference: data.feedPreference });
})
.catch(err => {
console.log(err);
});
}
componentDidMount() {
this.fetchList();
}
render() {
if(this.state.feedPreference === 1){
return (
<div>
<Navigation notifications={this.state.notifications}/>
<AnnotationSearchForm />
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome}/>
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
<div className="col-md-12">
<AnnotationFeed {...this.state} />
</div>
</div>
</div>
</div>
</div>
)
} else {
return (
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome}/>
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</div>
</div>
</div>
)
}
}
};
//Loading Indicator
const LoadingIndicator = props => {
if(props.loading == true){
return (
<div className="spinner">
<div className="bounce1"></div>
<div className="bounce2"></div>
<div className="bounce3"></div>
<p>Loading...</p>
</div>
)
} else {
return null;
}
}
Navigation Component:
import React from 'react';
import NotificationPopover from './activity-feed/notifications/notifications-popover';
//Navigation
export default class Navigation extends React.Component {
render() {
return (
<nav className="navbar">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#navigationLinks">
<span className="icon-bar mobile-nav-toggle"></span>
<span className="icon-bar mobile-nav-toggle"></span>
<span className="icon-bar mobile-nav-toggle"></span>
</button>
<a className="navbar-brand" href="/app"><img src="/images/synotate_logo.svg" className="nav-logo-svg"></img></a>
</div>
<div className="collapse navbar-collapse" id="navigationLinks">
<ul className="nav navbar-nav">
<li className="nav-item">
<a className="nav-link" href="/app">Activity Feed</a>
</li>
<li className="nav-item">
<a className="nav-link" href="/app/settings">Settings</a>
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li className="nav-item">
<NotificationPopover notifications={this.props.notifications}/>
</li>
<li className="nav-item">
<a className="nav-link" href="/app/logout">Log Out</a>
</li>
</ul>
</div>
</div>
</nav>
)
}
}
in your <AnnotationFeedContainer> you have this line if(this.state.feedPreference === 1) in the render() method.
For that condition wont be true unless you have a successful fetch() event coming from fetchList() in componentDidMount(), and if this condition returns true, you will render what ever is inside the braces of it, which includes the <Navigation> component
Else-wise you will render another code, which is loading indicator I guess, and in here, you didnt include your Navigation component, thus it won't show.
That is the logic you are using, you are telling your app to not include the Navigation component unless it fetches ur data, which happens to be logically fine!
if you want to display it other wise, you may wanna be moving it out of the if statement you have
It seems like you fetch call is responsible for setting the feedPreference variable in your state.
Since this variable is used in your if condition, and the <Navigation/> component isnt rendered when the feedPreference isn't set.
A simple solution would be to add <Navigation/> into the else condition (in the <AnnotationFeedContainer/>'s render function) :
} else {
return (
<>
<Navigation notifications={this.state.notifications}/>
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome}/>
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</div>
</div>
</div>
</>
)
}
A more "React-like" way of doing it could be to replace your entire condition wiht the following :
return (
<>
<Navigation notifications={this.state.notifications} />
{this.state.feedPreference === 1 && <AnnotationSearchForm />}
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome} />
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
{this.state.feedPreference === 1 ?
<>
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</>
:
<div className="col-md-12">
<AnnotationFeed {...this.state} />
</div>
}
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</div>
</div>
</div>
</>
)
Using inline ifs (&&) allows you to avoid repetitions. If you cannot use fragments (<> they were added in the latest React version) you can replace them with <div> tags
Related
I'm new in next js and I'm working on project using NextJS. I have some lots in my items page that shows lots currently(pagination/items.tsx) and I also have lotDetails page that I want it to show details of each lot using dynamic route(lotDetails\id\index.tsx).
This is the folder structure:
Now when I click the Link in Items.tsx I expect it to go to lotDetails page and pass the props, but nothing happens! (It stays on Items page!). here is Items.tsx:
import React from 'react'
import Link from "next/link"
const Items = ({currentItems}:{currentItems:any}) => {
console.log(currentItems)
// const ids=currentItems.map((el:any)=>el.id)
// const paths=ids.map((el:any)=>{params:{id:el.toString()}})
// console.log(paths)
return (
<>
<div className="container">
<div className="row">
{currentItems.map((el:any)=><div className="col-md-3 ">
//this should be linked to lotDetails page!
<Link href={{pathname:"../lotDetails/[id]",query:{id:JSON.stringify(el.id),title:el.title,image:el.image,description:el.description.toString(),rate:el.rating.rate,count:el.rating.count,price:el.price},}} as={`/lotDetails/${el.id.toString()}`}>
<div className="lot">
<div className="img-container">
<img src={el.image}/>
</div>
<div className="title">
{el.title}
</div>
<div className="price">
<span className="price-title">Price:</span>
<span>{el.price}</span>
</div>
</div>
</Link>
</div>)}
</div>
</div>
</>
)
}
export default Items;
I'm using getStaticProps and GetStaticPaths in lotDetails:
const LotDetails = (props:any) => {
const dispatch=useDispatch();
console.log(props)
const lotCount=1;
const addLots=()=>{
dispatch(AddCustomerLot({...props,lotCount:lotCount}))
}
return (
<>
<div className='container lot-details'>
<div className="row" >
<div className="col-md-6">
<div className="detail-container">
<div className="title-details"><h3>{props.title}</h3></div>
<div className="badge"><FontAwesomeIcon icon={faStar}/><span>{props.rate}</span></div>
<div className="inventory">
Inventory: <span className="count">{props.count}</span>
</div>
<div className="description-details">{props.description}</div>
<div className="price">Price: <span className="price-number">{props.price}$</span> </div>
<button className="btn btn-regist" onClick={addLots}>Add to shopping basket</button>
</div>
</div>
<div className="col-md-6"><img src={props.image} alt="" /></div>
</div>
</div>
</>
)
}
export const getStaticPaths:GetStaticPaths=async(context:any)=>{
const response= await axios.get("https://fakestoreapi.com/products")
const paths=response.data.map((el:any)=>({params:{id:el.id.toString()}}))
console.log(paths)
return{
paths,
fallback:'blocking'
}
}
export const getStaticProps:GetStaticProps=async(context:any)=>{
return{
props:
{
//recieve props
id:context.query.id,
title:context.query.title,
image:context.query.image,
description:context.query.description,
rate:context.query.rate,
count:context.query.count,
price:context.query.price
}
}
}
export default LotDetails;
when I deleted getStaticProps and getStaticPaths, the link worked! So I receive that link works perfectly and the problem is in getStaticProps and getStaticPaths. Of course I don't want to use getServerSideProps.
Update
According to julio's suggestion I changed pathName, and I also changed context.query to context.params.id:
Items:
<Link href={{pathname:`/lotDetails/${el.id}`,query:{id:JSON.stringify(el.id),title:el.title,image:el.image,description:el.description.toString(),rate:el.rating.rate,count:el.rating.count,price:el.price},}} as={`/lotDetails/${el.id.toString()}`}>
<div className="lot">
<div className="img-container">
<img src={el.image}/>
</div>
<div className="title">
{el.title}
</div>
<div className="price">
<span className="price-title">Price:</span>
<span>{el.price}</span>
</div>
</div>
</Link>
lotDetails:
return (
<>
<div className='container lot-details'>
<div className="row" >
<div className="col-md-6">
//I removed all section which used props and querys using comments
{/* <div className="detail-container">
<div className="title-details"><h3>{props.title}</h3></div>
<div className="badge"><FontAwesomeIcon icon={faStar}/><span>{props.rate}</span></div>
<div className="inventory">
Inventory: <span className="count">{props.count}</span>
</div>
<div className="description-details">{props.description}</div>
<div className="price">Price: <span className="price-number">{props.price}$</span> </div>
<button className="btn btn-regist" onClick={addLots}>Add to shopping basket</button>
</div> */}
</div>
{/* <div className="col-md-6"><img src={props.image} alt="" /></div> */}
</div>
</div>
</>
)
}
export const getStaticPaths:GetStaticPaths=async(context:any)=>{
const response= await axios.get("https://fakestoreapi.com/products")
const paths=response.data.map((el:any)=>({params:{id:el.id.toString()}}))
console.log(paths)
return{
paths,
fallback:'blocking'
}
}
export const getStaticProps:GetStaticProps=async(context:any)=>{
return{
props:
{
id:context.params.id,
// title:context.query.title,
// image:context.query.image,
// description:context.query.description,
// rate:context.query.rate,
// count:context.query.count,
// price:context.query.price
}
}
}
Finally, I solved problem in two steps:
1-I'm using google chrome browser, so I configured chrome and added my localhost port:
I used this link: chrome://inspect/, clicked configure button and added my localhost port(in my case localhost:3000)
2-I added this code to lotDetails page(the page which I used axios)
axios.defaults.httpsAgent=new https.Agent({
rejectUnauthorized:false,
})
don't forget to import:
import https from "https"
Now It's working.
I have a website that I used fixed menu options to anchor to the place I wanted ,below is a simple structure to demonstrate
mypage.jsx
<div className="mywork">
<TopbarWork menuOpen={menuOpen} setMenuOpen={setMenuOpen}/>
<MenuWork menuOpen={menuOpen} setMenuOpen={setMenuOpen}/>
<div className="sections">
<Product /> //origin
<Story workData={workData}/> //what I want to achieve
<Userflow workData = {workData}/>
<System workData={workData}/>
<FinalDesign workData={workData}/>
</div>
</div>
menu.jsx(click to go to the different anchors)
import './menu.scss'
export default function Menu({menuOpen,setMenuOpen}) {
return (
<div className={"menu " + (menuOpen && "active")}>
<ul>
<li onClick={()=>setMenuOpen(false)}>
Back
</li>
<li onClick={()=>setMenuOpen(false)}>
product
</li>
</ul>
</div>
)
}
Product.jsx(one of the components)
import { CheckCircle } from '#material-ui/icons';
import './product.scss'
export default function Product({workData}) {
return (
<div className="product" id="product"> //important
<div className="productLeft">
<div className="productIntro">
<p className="title">
{workData.title}
</p>
<p className="desc">
{workData.desc}
</p>
{workData.featured.map((feature)=>{
return(
<div className="featured">
<CheckCircle className="featuredIcon"/>
{feature.title}
</div>
)
})}
</div>
</div>
<div className="productRight">
<div className="item">
<img src="assets/desktop.jpeg" alt="" className="desktopImg" />
<img src={workData.productImg} alt="" className="productImg" />
</div>
</div>
</div>
)
}
So right now the problem is I have to pass "workData" into components , but after that if I click on the anchor, it would show Cannot read properties of undefined (reading 'workData') , I assume that I have to pass the workData too when I click on the anchor ?
But what's the right way to do that ?
EDIT
I tried using Link and history (react-router-dom) but failed , here's my code
menu.jsx
export default function MenuWork({menuOpen,setMenuOpen,workData}) {
console.log(workData)
const history = useHistory();
const handleClick = ()=>{
setMenuOpen(false)
history.push({
pathname:'/mywork#userflow',
state: { workData:workData}
})
}
return (
<div className={"menu " + (menuOpen && "active")}>
<ul>
<li onClick={()=>setMenuOpen(false)}>
Back
</li>
<li onClick={()=>setMenuOpen(false)}>
<Link to="#product">Product</Link>
</li>
<li onClick={()=>setMenuOpen(false)}>
<Link to={{
pathname:"/mywork#story",
state:{ workData:workData}
}}>Story</Link>
</li>
<li onClick={()=>handleClick()}>
userflow
</li>
</ul>
</div>
)
}
If I use Link and pass the state, it will return blank page without loading anything, and If I don't pass any state with Link , it will shows the same error as above.
I am building a blog and after clicking on Link linking to an article I want the new page to be scrolled on the Title of the article.
I have tried to use <Link to={`/${node.slug}#blog-title`}>{node.title}</Link>
but it doesn't scroll to title, it scrolls back to top of the page.
I have tried to use window.scrollTo(document.getElementById('blog-title); in componentDidMount but it looks like it's not renered until then and it still goes to top. If I use setTimeout it only scrolls to top to straight away scroll to the correct id.
index.js
return (
<article className="blog-listing" key={node.slug}>
<div className="entry-meta-content">
<h2 className="entry-title">
<Link to={`/${node.slug}`}>{node.title}</Link>
</h2>
<span className="entry-meta">
Created on {node.publishDate} By {node.author.name}
</span>
</div>
<div className="entry-media">
<Img fluid={node.heroImage.fluid} backgroundColor="#f4f8fb" />
</div>
<div className="entry-content-bottom">
<p className="entry-content">{node.body.childMarkdownRemark.excerpt}</p>
<Link to={`/${node.slug}`} className="entry-read-more">
<span />
Read More
</Link>
</div>
</article>
);
blog-post.js
import React, { Component } from 'react';
import get from 'lodash/get';
import Helmet from 'react-helmet';
import { graphql } from 'gatsby';
import Img from 'gatsby-image';
import Template from '../components/layout';
class BlogPostTemplate extends Component {
render() {
const post = get(this, 'props.data.contentfulBlogPost');
return (
<Template>
<Helmet title={`${post.title}`} id="blog-title" />
<div className="inner-blog-post">
<div className="container">
<div className="row">
<div className="col-lg-12 col-md-12">
<div className="entry-media">
<Img fluid={post.heroImage.fluid} backgroundColor="#f4f8fb" />
</div>
<h1 className="section-headline"> {post.title} </h1>
<p> {post.publishDate} </p>
<div
dangerouslySetInnerHTML={{
__html: post.body.childMarkdownRemark.html
}}
/>
</div>
</div>
</div>
</div>
</Template>
);
}
}
export default BlogPostTemplate;
export const pageQuery = graphql`
query blogPostQuery($slug: String) {
contentfulBlogPost(slug: { eq: $slug }) {
title
body {
childMarkdownRemark {
html
}
}
heroImage {
file {
url
}
fluid(maxWidth: 1800) {
...GatsbyContentfulFluid_withWebp_noBase64
}
}
}
}
`;
Thanks!
In your blogpost.js, you're attaching an id to the <Helmet> tag, which doesn't generate any content in the body. Perhaps you should attach it to the title in the body instead?
...
<Template>
- <Helmet title={`${post.title}`} id="blog-title" />
+ <Helmet title={`${post.title}`} />
<div className="inner-blog-post">
<div className="container">
<div className="row">
<div className="col-lg-12 col-md-12">
<div className="entry-media">
<Img fluid={post.heroImage.fluid} backgroundColor="#f4f8fb" />
</div>
- <h1 className="section-headline"> {post.title} </h1>
+ <h1 id="blog-title" className="section-headline"> {post.title} </h1>
...
I am new at React. Will be glad if someone can help:
I have parent (Dashboard) which contains all data. This data is passed to the children component (OnBoardingCard).
How can I render n times the OnBoardingCard component based on the data in the object at Dashboard without using the [num](in this case 3 times - 3x OnBoarding Cards;)?
Thank you!!
Parent- Dashboard
const cardData = [
{
svg: icon1,
title: 'Add',
content: 'add more'},
{
svg: icon2,
title: 'remove',
content: 'remove'
},
{
svg: icon3,
title: 'move',
content: 'move down'
}];
class Dashboard extends Component {
render() {
return (
<Section>
<OnboardingCard listData={cardData}/>
</Section>
);
} }
Children- OnBoardingCard
import Dashboard from "../../../../screens/Dashboard/index.js";
class OnboardingCard extends Component {
render() {
return (
<div className={styles.cardHolder}>
<div className={styles.fullCard}>
<div className={styles.onboardingCard}>
<div className={styles.iconBackground}>
<img src={this.props.listData[0].svg} />
</div>
<div className={styles.title}>{this.props.listData[0].title}</div>
</div>
<p className={styles.cardDescription}>
{this.props.listData[0].content}
</p>
</div>
</div>
); }}
When you are using a map inside render assign a unique key to its child component.
render(){
return(
{this.props.listData.map((item, i) =>
<div className={styles.cardHolder} key={i}>
<div className={styles.fullCard}>
<div className={styles.onboardingCard}>
<div className={styles.iconBackground}>
<img src={this.props.listData[0].svg} />
</div>
<div className={styles.title}>{this.props.listData[0].title}</div>
</div>
<p className={styles.cardDescription}>
{this.props.listData[0].content}
</p>
</div>
</div>
)}
);
}
You can use map function,
like this,
{this.props.listData.map((item)=>
<div className={styles.cardHolder}>
<div className={styles.fullCard}>
<div className={styles.onboardingCard}>
<div className={styles.iconBackground}>
<img src={item.svg} />
</div>
<div className={styles.title}>{item.title}</div>
</div>
<p className={styles.cardDescription}>
{item.content}
</p>
</div>
</div>)}
<Section>
<div className={styles.cardRow}>
{cardData.map((card, i) => (
<OnboardingCard {...card} key={i} />
))}
</div>
</Section>
This is what I meant (and wanted to do). So this solves my question. Thanks everyone!!
I work actually in my first React Project for a little startup, an "search and add to your collection" app. For the search feature, i use React Instant Search by Algolia. Everything work find.
Now, for the "add to collection" feature, i know how to do that, but I can not recover the data in my function. I did a little test like this:
import React from 'react';
import withAuthorization from './withAuthorization';
import * as firebase from 'firebase';
import {database, } from '../firebase/firebase';
import Image from 'react-image-resizer';
import{InstantSearch, SearchBox, Hits, Highlight, RefinementList} from "react-instantsearch/dom";
import { orderBy } from "lodash";
function addToCollection({hit}) {
console.log('-----');
console.log('The selected item is:', hit);
console.log('------');
}
const Hit = ({hit}) =>
<div className="hit">
<div className="hit-image">
<img src={hit.avatarURL} height={150} width={150}/>
</div>
<div className="hit-content">
<div className="hit-marque">
{hit.marque}
</div>
<div className="hit-numero">
{hit.numero}
</div>
<div className="hit-marquesuite">
{hit.marquesuite}
</div>
<div className="hit-reference">
{hit.reference}
</div>
<div className="hit-cote">
{hit.cote}
</div>
<button className="btn btn-success" onClick={addToCollection}>Ajouter à ma collection</button>
</div>
</div>
const Content = () =>
<div className="content container-fluid text-center">
<div className="row">
<div className="col-lg">
<Hits hitComponent={Hit} key ={Hit}/>
</div>
</div>
</div>
class Catalogue extends React.Component {
constructor(){
super();
this.state = {
catalogue: {},
};
}
render(){
if(this.state.catalogue === null) {
return <p>Le catalogue est vide</p>
}
return (
<div class="container-fluid">
<h1 className="text-center">Catalogue de capsule</h1>
<h4 className="text-center">Rechercher une capsule</h4>
<InstantSearch
apiKey="a8de2c61b98e1ca62a5df03f1970f46a"
appId="7H3CTF406R"
indexName="catalogue">
<SearchBox translation={{placeholder:'Rechercher une capsule'}} width="500 px"/>
<Content />
</InstantSearch>
</div>
);
}
}
const authCondition = (authUser) => !!authUser;
export default withAuthorization(authCondition)(Catalogue);
So, how can I transmit the information of my const "Hit" to my function "addToCollection" when I click on button.
Thank in advance for your help
<div className="hit-content">
<div className="hit-marque"> {hit.marque} </div>
<div className="hit-numero"> {hit.numero} </div>
<div className="hit-marquesuite"> {hit.marquesuite} </div>
<div className="hit-reference"> {hit.reference} </div>
<div className="hit-cote"> {hit.cote}
</div>
How can you access hit.cote and hit.avatarurl ?