I'm using React or Gatsby for a static website. A subpages need to send an prop or a variable(bool) to the main layout component, to determinme if we show a Hero image or not.
I got the following code (simplified) for the page:
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../components/layout'
import dividerIcon from '../images/hair-cut-tool.svg'
const IndexPage = ({ data }) => (
<Layout showHero={true}>
<div className="divider-wrapper">
<div className="divider">
<img alt="divider" src={dividerIcon} />
</div>
</div>
</Layout>
)
export default IndexPage
How can I "get" the prop in may Layout.js?
I'm sending it with "" but I have no idea, how to get this variable and use it.
As for right now the Layout.js looks like this:
const Layout = ({ children }) => (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
heroImgLogo: file(relativePath: { eq: "logo.png" }) {
childImageSharp {
fixed(width: 300) {
...GatsbyImageSharpFixed_withWebp_noBase64
}
}
}
}
`}
render={data => (
<>
<div className="site">
{(children.showHero) ?
<Hero logoImg={data.heroImgLogo.childImageSharp.fixed} />
:
null }
<div className="site-content container">{children}</div>
</div>
</>
)}
/>
);
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
yet again, simplified.
I tried to children.showHero but it wasn't the right approach, I guess.
Any hints?
You can destructure it alongside children:
const Layout = ({ children, showHero }) => (
Be sure to replace children.showHero with just showHero.
Related
I'm having some trouble with a dynamic route in Next JS, and navigating from one dynamic page to another.
I have a custom type called 'projects'. In that custom type I have set up a Content Relationship field from Prismic called 'relatedProject', so I can select another project in the CMS as a linked document at the end of each project page.
Displaying this related project works fine, and is displaying the correct information when I navigate to a project page, but when I click the link to take me to the related project document - which is the same dynamic route, just with different content, the content is not changing to the content that should be getting pulled in from the new project. A proper load doesn't occur as the page load animations are not playing when clicking the link. When I perform a hard refresh the page is rendering correctly with the content of the project page I clicked through to.
I'm unsure whether this is something wrong with Prismic or something wrong with Next JS? Apologies if I have not explained this brilliantly. I am sure getStaticPaths is setup correctly, as I can navigate to any other project page from the home or work pages, just not from one project page to another. Really has got me stumped!
This is my code for the dynamic page template [uid].js, any help is appreciated:
import { useEffect, useRef } from 'react';
import { createClient } from '../../prismic';
import resolver from '../../sm-resolver.js';
import * as prismicH from '#prismicio/helpers';
import { linkResolver } from '../../utils/linkResolver';
import { SliceZone } from '#prismicio/react';
import { gsap } from 'gsap';
import { Layout } from '../../components/Layout';
import ProjectHero from '../../components/Hero/Project';
import RelatedProjectCta from '../../components/RelatedProjectCta';
const ProjectDetail = ({ data, url, lang, layout }) => {
const seo = {
metaTitle: data.metaTitle || layout.metaTitle,
metaDescription: data.metaDescription || layout.metaDescription,
metaImage: data.metaImage?.url || layout.metaImage?.url,
url: url,
article: true,
lang: lang,
};
const pageData = { data };
const relatedProject = { data };
const revealOverlay = useRef();
// Hero reveal
useEffect(() => {
gsap.to(revealOverlay.current, {
opacity: 0,
duration: 2.3,
ease: "power2.out"
});
}, []);
return (
<Layout seo={seo} {...layout}>
<ProjectHero {...pageData} />
<SliceZone slices={data.slices} resolver={resolver} />
{
prismicH.isFilled.link(data.relatedProject) ? (
<RelatedProjectCta {...relatedProject}/>
)
: null
}
</Layout>
);
};
// Fetch content from prismic - previews but doesn't hot reload
export const getStaticProps = async ({ params, previewData }) => {
const client = createClient({ previewData });
// Default Layout components reused across the site
// If a singleton document is missing, `getStaticProps` will throw a PrismicError.
const seo = await client.getSingle("defaultSeo");
const header = await client.getSingle("header");
const footer = await client.getSingle("footer");
const socials = await client.getSingle("socials");
const projectDetail = await client.getByUID("project", params.uid, {'fetchLinks': 'project.theme, project.client, project.projectTitle, project.projectIntroduction, project.featuredImage'} );
return {
props: {
layout: {
seo,
header,
footer,
socials,
},
...projectDetail
}
};
};
export const getStaticPaths = async () => {
const client = createClient();
const projectDetail = await client.getAllByType("project");
return {
paths: projectDetail.map((page) => prismicH.asLink(page, linkResolver)),
fallback: false,
};
};
export default ProjectDetail;
This is the code of the component that is the link for the related project:
import React from 'react';
import { PrismicText } from '#prismicio/react';
import { PrismicNextImage } from '#prismicio/next';
import { Link } from "../Link";
const RelatedProjectCta = ({ data }) => {
const relatedProject = {
uid: data.relatedProject.uid,
url: data.relatedProject.url,
theme: data.relatedProject.theme,
client: data.relatedProject.data.client,
title: data.relatedProject.data.projectTitle,
introduction: data.relatedProject.data.projectIntroduction,
image: data.relatedProject.data.featuredImage,
}
return (
<section className={`component cta-slice ${relatedProject.theme}`} data-header={relatedProject.theme === "light" && (`is-dark`) || relatedProject.theme === "dark" && ('is-light')}>
<div className="container">
<div className="cta-slice_text-wrapper">
<div className="eyebrow-heading">
Related project
</div>
<h2 className="primary-heading">
<PrismicText field={relatedProject.client}/>
</h2>
<div className="description lead-body">
<PrismicText field={relatedProject.title}/>
</div>
<Link
href={`/work/${relatedProject.uid}`}
className="btn animated-button">
View project
</Link>
</div>
</div>
<div className="cta-slice_background">
<div className="cta-slice_background_image">
<PrismicNextImage
className="block width-100% object-cover"
field={relatedProject.image}
imgixParams={{ q: 80 }}
layout="fill"
/>
</div>
</div>
</section>
)
};
export default RelatedProjectCta
Link component:
import NextLink from "next/link";
import { asLink } from "#prismicio/helpers";
import { linkResolver } from "../utils/linkResolver";
export const Link = ({
href: link,
target,
disabled,
children,
className,
...rest
}) => {
if (disabled) {
return <span {...rest}>{children}</span>;
}
//Standard link
if (typeof link === "string") {
if (link[0] === "/") {
return (
<NextLink href={link}>
<a className={className} {...rest}>
{children}
</a>
</NextLink>
);
}
return (
<a
href={link}
target={target ?? "_blank"}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
//Unknown link
if (link.link_type === "Any") return null;
//Prismic Link
if (link.link_type === "Web") {
if (!link.url) return null;
//Same page anchor links
if (link.url.includes("://#")) {
const anchor = link.url.split("://")[1];
return (
<a href={anchor} className={className} {...rest}>
{children}
</a>
);
}
return (
<a
href={asLink(link, linkResolver)}
target={target ?? "_blank"}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
if (link.link_type === "Document") {
return (
<NextLink href={asLink(link, linkResolver)}>
<a className={className} {...rest}>
{children}
</a>
</NextLink>
);
}
if (link.link_type === "Image") {
return (
<a
href={asLink(link, linkResolver)}
className={className}
{...rest}
rel="noopener noreferrer"
>
{children}
</a>
);
}
return null;
};
I'm implementing a project where
I have a array of 44 object data
When I type a it returns 37 data immediately by onChange()
After type ad it return 20
The Problem is when I return back to a by backspace. It stay on 20.
How can I get back 37 data again.
Code of Root.jsx
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
}
getBadge = (e) => {
console.log(e)
const searched = this.state.data.filter(
item => {
if (e === '') {
return item
} else if (item.title.toLowerCase().includes(e.toLowerCase())) {
console.log(item)
return item
}
}
)
this.setState({ data:searched })
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search getBadge={this.getBadge} />
</>
<div className='container'>
<IconCard data={data} />
</div>
</>
)
}
}
export default Root
state data be like
state={
data:data
}
data
{
"title": "Academia",
"hex": "41454A"
},
{
"title": "Academia",
"hex": "41454A"
}
Code of Search.jsx
import React, { Component } from 'react';
class Search extends Component {
handleChange = (e) => {
this.props.getBadge(e.target.value)
}
render() {
// console.log(this.state.search)
return (
<div className='container pb-3'>
<div className="row">
<div className="col-md-3 align-self-center ">
<input type="text" className="form-control" placeholder="Search by brand..." onChange={this.handleChange} />
</div>
</div>
</div>
)
}
}
export default Search;
I understood your problem. You are mutating the original data whenever the search text is changing. Actually, you should not do that.
Instead,
import React, { Component } from 'react'
import icons from './services/icons'
import IconCard from './components/IconCard'
import Header from './components/Header'
import Search from './components/Search'
const icon = new icons()
class Root extends Component {
state = {
data: icon.getIcon(),
searchText: '',
}
getBadge = (search) => {
console.log(search)
return this.state.data.filter(
item => {
if (item.title.toLowerCase().includes(search.toLowerCase())) {
console.log(item)
return true;
}
return false;
}
)
}
render() {
const data = this.state.data
return (
<>
<>
<Header />
<Search
value={this.state.searchText}
onChange={(value) => this.setState({searchText: value})} />
</>
<div className='container'>
<IconCard data={this.getBatchData(this.state.searchText)} />
</div>
</>
)
}
}
export default Root
Set searchText state in the component
Change the props of the <Search /> component
Update the state when the search updates
Update the getBatchData() as per above code.
Everytime you update the search text, the data will remains same, but the filter will return the results according to search text
In your function getBadge :
const searched = this.state.data.filter(...)
this.setState({ data:searched })
You are replacing the state with the object you found. So if the data object had 44 elements, after a search it will only have the filtered elements. All the other elements are gone.
You should consider filtering from a constant object instead of state.data
I have parent component as below:
import React from "react"
import PropTypes from "prop-types"
import Header from "./header"
import "./layout.css"
import TopBar from "./topbar"
import Bottom from "./bottom"
const Layout = ({ children, isMobile = false }) => {
const mainStyle = !isMobile ? ".main-desktop" : ".main-mobile"
//children.isMobile = isMobile
return (
<>
<Header siteTitle={'MAIN TITLE'}
moto={'SLOGAN.'}
isMobile={isMobile} />
<TopBar isMobile={isMobile} />
<div
style={{
margin: `0 auto 0 auto`,
minHeight: `50%`,
padding: `0 1.0875rem 1.45rem`,
paddingLeft: "90px"
}}
>
<main className={mainStyle}>{children}</main>
<br />
<br />
</div>
<Bottom isMobile={isMobile} />
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
I have a child component as below:
import React from "react"
import SEO from "../components/seo"
const ContactUsPage = ({ isMobile = false }) => (
<>
<SEO title="Contact Us"
</>
)
export default ContactUsPage
ContactUsPage is being called inside Layout by the Gatsby framework into children variable. Now, I want to pass isMobile property from Layout. I tried setting it directly but is giving error that object is not extensible.
What is the way and if possible, what is the correct way to set the property for variable components?
I think you can do this in two way, the Gatsby way and the React Context way:
The Gatsby way:
Gatsby allows you to pass a state prop to its <Link/> component.
The value of the state can then be retrieved from the page component (that automatically receives a location prop. See the Gatsby documentation
The Context way:
Create a React context that holds the isMobile value and a Component Provider:
export const MobileContext = React.createContext({ isMobile: false });
export MobileProvider = ({ children }) => {
const [isMobile, setIsMobile] = useState(false);
// Define your logic to check if it is mobile or not
const value = useMemo(() => ({ isMobile }), [isMobile]);
return (
<MobileContext.Provider value={value}>
{children}
</MobileContext.Provider>
)
}
And wrap your layout with that.
Then you can access the context value on all Layout children (and their children too) with const { isMobile } = useContext(MobileContext);.
Check the React context documentation
In return in the parent component:
<ContactUsPage isMobile={isMobile} />
And inside ContactUsPage component:
const ContactUsPage = ({ isMobile }) => {
return (
<div>
</div>
);
};
so I've recently completed a project that I was working on that displayed a list of pokemon, and once clicked on, the user is directed to the pokemon information page.
It looks as follows:
So I have my main dashboard.js that contains my "PokemonList" as follows:
import React, { Component } from "react";
import styled from "styled-components";
import PokemonList from "../pokemon/PokemonList";
export default class Dashboard extends Component {
render() {
return (
<div>
<div className="row">
<div className="col">
<PokemonList />
</div>
</div>
</div>
);
}
}
my PokemonList.js is responsible for obtaining the Pokemon information from the PokeAPI and the code is as follows:
import React, { Component } from "react";
import PokemonCard from "./PokemonCard";
import axios from "axios";
export default class PokemonList extends Component {
state = {
url: "http://pokeapi.co/api/v2/pokemon/?limit=600",
pokemon: null
};
async componentDidMount() {
const res = await axios.get(this.state.url);
this.setState({ pokemon: res.data["results"] });
}
render() {
return (
<React.Fragment>
{this.state.pokemon ? (
<div className="row">
{this.state.pokemon.map(pokemon => (
<PokemonCard
key={pokemon.name}
name={pokemon.name}
url={pokemon.url}
/>
))}
</div>
) : (
<h1>Loading Pokemon</h1>
)}
</React.Fragment>
);
}
}
The pokemonList is built of several pokemonCards that's then displayed, but I don't think the coding for that is needed for what I'm looking for.
If I wanted to enable pagination, would I have to incorporate the code within my Dashboard.js or the pokemonList.js?
-----------------------EDIT--------------------------------------
What you could use is this library: https://www.npmjs.com/package/react-js-pagination
Then in your code the pagination would be smth like this:
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={this.state.itemsCountPerPage}
totalItemsCount={this.state.pokemon.length}
pageRangeDisplayed={5}
onChange={::this.handlePageChange.bind(this)}
/>
handlePageChange function:
handlePageChange(pageNumber) {
this.setState({activePage: pageNumber});
}
then in the render function of your pokemonList.js:
let indexOfLastTodo = this.state.activePage * this.state.itemsCountPerPage;
let indexOfFirstTodo = indexOfLastTodo - this.state.itemsCountPerPage;
let renderedPokemons = this.state.pokemon.slice(indexOfFirstTodo, indexOfLastTodo);
and finally
{renderedPokemons.map(pokemon => (
<PokemonCard
key={pokemon.name}
name={pokemon.name}
url={pokemon.url}
/>
))}
Of course don't forget to include activePage and itemsCountPerPage in your state. I think I have done something like this in one of my earlier projects. Enjoy!
So I'm trying to break the component on my App.js into a smaller component, that being my Sidebar.js. I took a small section of the code and put it in its own Sidebar.js file but no matter what I've tried, I cant call my function getNotesRows() from App.js without it being unable to find it or this.states.notes being undefined.
I just want it to send the code back and forth. This is a demo app, so I know it's not the most practical.
import React, { Component } from "react";
import classNames from "classnames";
import logo from "./logo.svg";
import checkMark from "./check-mark.svg";
import "./App.css";
import Sidebar from "./components/Sidebar.js";
class App extends Component {
constructor(props) {
super(props);
this.state = {
notes: [],
currentNoteIndex: 0
};
this.markAsRead = this.markAsRead.bind(this);
this.selectNote = this.selectNote.bind(this);
console.log("Test started 2.25.19 19:23");
}
componentWillMount() {
fetch('/notes')
.then(response => response.json())
.then(
notes => {
this.setState({
notes: notes,
currentNoteIndex: 0
})
}
)
.catch(
error => {
console.log('Ooops!');
console.log(error);
}
);
}
markAsRead() {
this.setState(currentState => {
let marked = {
...currentState.notes[currentState.currentNoteIndex],
read: true
};
let notes = [...currentState.notes];
notes[currentState.currentNoteIndex] = marked;
return { ...currentState, notes };
});
}
selectNote(e) {
this.setState({ currentNoteIndex: parseInt(e.currentTarget.id, 10) });
}
getTotalUnread() {
let unreadArray = this.state.notes.filter(note => {
return note.read === false;
})
return unreadArray.length;
}
getNotesRows() {
return this.props.notes.map(note => (
<div
key={note.subject}
className={classNames("NotesSidebarItem", {
selected:
this.props.notes.indexOf(note) === this.props.currentNoteIndex
})}
onClick={this.selectNote}
id={this.props.notes.indexOf(note)}
>
<h4 className="NotesSidebarItem-title">{note.subject}</h4>
{note.read && <img alt="Check Mark" src={checkMark} />}
</div>
));
}
// TODO this component should be broken into separate components.
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Notes Viewer Test App</h1>
<div>
Unread:
<span className="App-title-unread-count">
{this.getTotalUnread()}
</span>
</div>
</header>
<div className="Container">
<Sidebar />
<section className="NoteDetails">
{this.state.notes.length > 0 && (
<h3 className="NoteDetails-title">
{this.state.notes[this.state.currentNoteIndex].subject}
</h3>
)}
{this.state.notes.length > 0 && (
<p className="NoteDetails-subject">
{this.state.notes[this.state.currentNoteIndex].body}
</p>
)}
{this.state.notes.length > 0 && (
<button onClick={this.markAsRead}>Mark as read</button>
)}
{this.state.notes.length <= 0 && (
<p>
No Notes!
</p>
)}
</section>
</div>
</div>
);
}
}
export default App;
Above is my App.js
and below is the Sidebar.js that I'm trying to create
import React, { Component } from "react";
import "../App.css";
import App from "../App.js";
class Sidebar extends React.Component{
constructor(props) {
super(props);
}
render(){
return (
<section className="NotesSidebar">
<h2 className="NotesSidebar-title">Available Notes:</h2>
<div className="NotesSidebar-list">{App.getNotesRows()}</div>
</section>
)}}
export default Sidebar;
You cannot access a method like that. You need to pass the method as a prop and use it in the child.
<Sidebar getNotesRows={this.getNotesRows} />
and in Sidebar use
<div className="NotesSidebar-list">{this.props.getNotesRows()}</div>
In your sidebar, you're trying to call getNotesRows() from App, but Sidebar doesn't need access to app (you shouldn't have to import App in Sidebar.js). Instead, you should pass the function from App to your Sidebar component, and reference it from Sidebar's props.
In App.js, you'll need to bind getNotesRows and pass it to sidebar.:
<Sidebar getNotesRows={ this.getNotesRows } />
Then in Sidebar.js, you'll need to reference getNotesRows in your render method:
render() {
const notes = this.props.getNotesRows();
return (
<section className="NotesSidebar">
<h2 className="NotesSidebar-title">Available Notes:</h2>
<div className="NotesSidebar-list">{ notes }</div>
</section>
);
}
It seems like the problem here is that you are trying to use a class function as a static property, to put it simply, you have not initialized the App class when you import it into your sidebar(?), thus no static function was found on your App class so you can call App.getNotesRows() maybe you should re-think your components and separate them in container-components using a Composition Based Programming approach instead of OO approach.