White screen when createPage with variable in Gatsby - reactjs

I'm trying to create dynamic product page in Gatsby.
Here is what I've edited in file gatsby-node.js
exports.createPages = async ({ graphql, actions }) => {
const { createRedirect } = actions
const { createPage } = actions
const {
data: { products },
} = await graphql(`
{
products: allTestJson {
edges {
node {
brand
category
}
}
}
}
`)
const nodeArray = products.edges.map(edge => edge.node.brand)
const unique = [...new Set(nodeArray)]
console.log("UNIQUE")
console.log(unique)
unique.forEach((brand) => {
console.log("BRAND")
console.log(brand)
createPage({
path: `/products/${brand}`,
component: require.resolve(`./src/pages/contact-us.js`),
})
})
}
Here I do the console.log and I'm sure the ${node.brand} returns a value. (One of it is "faro"). However, when I visit /products/faro on the browser, I only see a big white blank page:
When I change the path to path: "path: /products/faro" (just hard code path as products/faro), then the page component renders correctly.
Is there something wrong with the loop or variable? I'm following this tutorial https://dev.to/notrab/build-static-product-pages-with-gatsby-and-commerce-js-3952
import React, { Component } from "react"
import styled from "styled-components"
import { injectIntl, Link, FormattedMessage } from "gatsby-plugin-intl"
import Layout from "../components/layout"
import SEO from "../components/seo"
import { Background } from "../components/layout/Background"
import { Space } from "../components/layout/Space"
import ShowroomGrid from "../components/organisms/ShowroomGrid"
import ContactForm from "../components/organisms/ContactForm"
import Map from "../components/molecules/Map"
class ContactUs extends Component {
state = {
responsiveSize: null,
}
componentDidMount = () => {
this.setState({
responsiveSize: window.innerWidth,
})
}
render() {
const { responsiveSize } = this.state
return (
<Layout>
<SEO
description="Visit our showrooms in Ho Chi Minh City & Hanoi to admire the timeless masterpieces from Fritz Hansen, Flos, Louis Poulsen, Fontana Arte & Faro."
title="Contact Us - nanoHome showrooms in HCM & Hanoi"
keywords={[`luxury`, `lighting`, `furniture`, `showroom`]}
/>
<Background>
<MainContainer className="main-container">
<Space height="64" />
<H1Tag>nanoHome | Contact Us</H1Tag>
<SectionMapAndContact responsiveSize={responsiveSize}>
<GoogleMap>
<Map />
</GoogleMap>
<Contacts>
<Title>
<FormattedMessage id="page-contact-us.section-map-and-contact.title" />
</Title>
<Space height="48" />
<Address>
<b>
<FormattedMessage id="page-contact-us.section-map-and-contact.office" /> </b> <Space height="16" />
<p>
<FormattedMessage id="page-contact-us.section-map-and-contact.address" />
</p>
<Space height="16" />
<p>‭+84 90 984 0028</p>
<Space height="8" />
<p>info#nanohome.vn</p>
<Space height="8" />
<p>www.nanohome.vn</p>
</Address>
</Contacts>
</SectionMapAndContact>
<Space height="64" />
<SectionShowrooms>
<Title>Our Showrooms</Title>
<Space height="48" />
<ShowroomGrid responsiveSize={responsiveSize} />
</SectionShowrooms>
<Space height="64" />
<SectionContactForm responsiveSize={responsiveSize}>
<Title>Get in Touch</Title>
<Space height="48" />
<ContactForm />
</SectionContactForm>
</MainContainer>
</Background>
</Layout>
)
}
}
const MainContainer = styled.div`
margin: 0 auto;
`
const SectionMapAndContact = styled.div`
max-width: 960px;
margin: auto;
padding: 0 2rem;
display: grid;
grid-template-columns: ${props =>
props.responsiveSize > 1024 ? "1fr 1fr" : "1fr"};
grid-template-rows: ${props =>
props.responsiveSize < 1024 ? "1fr auto" : "1fr"};
align-items: center;
justify-content: center;
grid-gap: ${props => (props.responsiveSize > 1024 ? "4rem" : "2rem")};
`
const GoogleMap = styled.div`
width: 100%;
height: 500px;
`
const Contacts = styled.div`
width: 100%;
max-width: 440px;
`
const Title = styled.h2`
font-size: var(--font-size-larger);
color: var(--color-primary-blue);
font-family: "Miller";
`
const Address = styled.div`
color: var(--color-primary-blue);
font-size: var(--font-size-large);
border-left: 2px solid var(--color-primary-blue);
padding: 0 2rem;
p {
font-size: var(--font-size-medium);
line-height: 1.5;
margin: 0;
}
`
const SectionShowrooms = styled.div`
padding: 0 2rem;
max-width: 960px;
margin: auto;
`
const SectionContactForm = styled.div`
width: ${props =>
(props.responsiveSize > 1024 && "24%") ||
(props.responsiveSize > 768 && "50%") ||
(props.responsiveSize > 480 && "80%") ||
"100%"};
margin: auto;
max-width: 960px;
padding: 0 2rem;
min-width: 320px;
display: flex;
flex-direction: column;
align-items: center;
`
const H1Tag = styled.h1`
visibility: hidden;
font-size: var(--font-size-smaller);
`
export default ContactUs
I also add the debug of unique Array and brand here (sorry it's quite messy as I'm outside)

The fact that you're not getting a 404 page indicates that your page is being created correctly.
Is it possible the page is expecting some sort of props? In the tutorial you linked, it looks like they are passing an id prop. To pass such props you need to pass a context arg to createPage (eg. createPage({ component, path, context: { id })) ... but the code you provided doesn't have any.
Whatever the problem is, it seems likely that it's in contact-us.js, so you'll need to debug it.

The problem is the "brand data" that I loaded from graphql has some capital letters, and the browser always lowercase text, so it does not match the path in gatsby-node.js. Thank you #harley for spotting this.
Thank you for all your help.

Related

Uncaught Error: Cannot find module './undefined'

I watched the code multiple times to figure out the problem but couldn't find the solution to this exception thrown at the console. The Product page blanks out.
Product.jsx
import React, { useEffect, useState } from 'react'
import Navbar from '../../Components/Navbar/Navbar'
import Announcment from '../../Components/Announcment/Announcment'
import Newsletter from '../../Components/Newsletter/Newsletter'
import Footer from '../../Components/Footer/Footer'
import styled from 'styled-components'
import './Product.css'
import { Add, Remove } from '#mui/icons-material'
import { useLocation } from 'react-router-dom'
import {publicRequest} from '../../requestMethod';
const Container = styled.div`
`
const Wrapper = styled.div`
padding: 50px;
display: flex;
`
const ImageContainer = styled.div`
flex: 1;
`
const InfoContainer = styled.div`
flex: 1;
padding: 0 50px;
`
const Title = styled.h1`
font-weight: 200;
`
const Desc = styled.p`
margin: 20px 0;
`
const Price = styled.span`
font-weight: 100;
font-size: 40px;
`
const FilterContainer = styled.div`
width: 50%;
display: flex;
justify-content: space-between;
margin: 30px 0;
`
const Filter = styled.div`
display: flex;
align-items: center;
`
const FilterTitle = styled.span`
font-size: 20px;
font-weight: 200;
`
const FilterColor = styled.div`
width: 20px;
height: 20px;
border-radius: 50%;
background-color: ${props => props.color};
margin: 0 5px;
cursor: pointer;
`
const FilterSize = styled.select`
margin-left: 10px;
padding: 5px;
`
const FilterSizeOption = styled.option`
`
const AddContainer = styled.div`
width: 50%;
display: flex;
align-items: center;
justify-content: space-between;
`
const AmountContainer = styled.div`
display: flex;
align-items: center;
font-weight: 700;
`
const Amount = styled.span`
width: 30px;
height: 30px;
border-radius: 10px;
border: 1px solid teal;
display: flex;
justify-content: center;
align-items: center;
margin: 0 5px;
`
const Button = styled.button`
padding: 15px;
border: 3px solid teal;
background-color: white;
cursor: pointer;
font-weight: 500;
&:hover{
background-color: #f8f4f4;
}
`;
const Product = () => {
const location = useLocation();
const Id = location.pathname.split('/')[2];
const [product, setProduct] = useState({});
useEffect(() => {
const getSingleProduct = async () => {
try {
const res = await publicRequest.get(`/products/find/${Id}`);
setProduct(res.data);
} catch (error) {
console.log(error);
}
}
getSingleProduct();
}, [Id]);
return (
<Container>
<Navbar />
<Announcment />
<Wrapper>
<ImageContainer>
<img src={require(`../../Assets/${product.img}`)} alt='' className='productImg2' />
</ImageContainer>
<InfoContainer>
<Title>{product.title}</Title>
<Desc>{product.desc}</Desc>
<Price>$ {product.price}</Price>
<FilterContainer>
<Filter>
<FilterTitle>Color</FilterTitle>
{product.color.map((c, i) => (
<React.Fragment key={i}>
<FilterColor color={c} />
</React.Fragment>
))
}
</Filter>
<Filter>
<FilterTitle>Size</FilterTitle>
<FilterSize>
{product.size.map((s, i) => (
<React.Fragment key={i}>
<FilterSizeOption>{s}</FilterSizeOption>
</React.Fragment>
))
}
</FilterSize>
</Filter>
</FilterContainer>
<AddContainer>
<AmountContainer>
<Remove style={{ cursor: "pointer" }} />
<Amount>1</Amount>
<Add style={{ cursor: "pointer" }} />
</AmountContainer>
<Button>ADD TO CART</Button>
</AddContainer>
</InfoContainer>
</Wrapper>
<Newsletter />
<Footer />
</Container>
);
}
export default Product
requestMethod.js
import axios from 'axios';
const BASE_URL = "http://localhost:5000/api";
const Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyZjI5NmRjY2JmYzNjZTAyNGRhODNlNSIsImlzQWRtaW4iOnRydWUsImlhdCI6MTY2MDEzMzExNiwiZXhwIjoxNjYwMzkyMzE2fQ.TMBcTYhOKhsGTqHgVY6fAGoX9UCc2rC100JwvzoSqhE";
export const publicRequest = axios.create({
baseURL:BASE_URL,
});
export const userRequest = axios.create({
baseURL:BASE_URL,
headers:{token:`Bearer ${Token}`},
});
error
.$:106 Uncaught Error: Cannot find module './undefined'
at webpackContextResolve (.$:106:1)
at webpackContext (.*$:101:1)
at Product (Product.jsx:120:1)
at renderWithHooks (react-dom.development.js:16305:1)
at mountIndeterminateComponent (react-dom.development.js:20074:1)
at beginWork (react-dom.development.js:21587:1)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1)
at invokeGuardedCallback (react-dom.development.js:4277:1)
at beginWork$1 (react-dom.development.js:27451:1)
The above error occurred in the component:
at Product (http://localhost:3000/static/js/bundle.js:3288:81)
at Routes (http://localhost:3000/static/js/bundle.js:56686:5)
at Router (http://localhost:3000/static/js/bundle.js:56619:15)
at BrowserRouter (http://localhost:3000/static/js/bundle.js:55428:5)
at App
First time try to check if the product object it's not empty.
Or try every property with product?.title, product?.desc and so on
The issue has been solved by doing these changes.
const Product = () => {
const location = useLocation();
const Id = location.pathname.split('/')[2];
const [product, setProduct] = useState({});
useEffect(() => {
const getSingleProduct = async () => {
try {
const res = await publicRequest.get(`/products/find/${Id}`);
setProduct(res.data);
} catch (error) {
console.log(error);
}
}
getSingleProduct();
}, [Id]);
return (
<Container>
<Navbar />
<Announcment />
<Wrapper>
<ImageContainer>
<img src={product?.img ? require(`../../Assets/${product.img}`) : require('../../Assets/gray-men-shoes-1-free-img.jpg')} alt='' className='productImg2' />
</ImageContainer>
<InfoContainer>
<Title>{product?.title ? product.title : "Gray shoes"}</Title>
<Desc>{product?.desc ? product.desc : "abc"}</Desc>
<Price>$ {product?.price ? product.price : 20}</Price>
<FilterContainer>
<Filter>
<FilterTitle>Color</FilterTitle>
{product.color ? product.color.map((c, i) => (
<FilterColor color={c} key={i} />
))
: <><FilterColor color="black" /></>
}
</Filter>
<Filter>
<FilterTitle>Size</FilterTitle>
<FilterSize>
{product.size ? product.size.map((s, i) => (
<FilterSizeOption key={i}>{s}</FilterSizeOption>
))
: <> <FilterSizeOption >small</FilterSizeOption></>
}
</FilterSize>
</Filter>
</FilterContainer>
<AddContainer>
<AmountContainer>
<Remove style={{ cursor: "pointer" }} />
<Amount>1</Amount>
<Add style={{ cursor: "pointer" }} />
</AmountContainer>
<Button>ADD TO CART</Button>
</AddContainer>
</InfoContainer>
</Wrapper>
<Newsletter />
<Footer />
</Container>
);
}
export default Product

Unable to view custom sidebar component created with react router in storybook

I am quite new to React and have been trying to create a custom sidebar component. Based on my understanding by going through different tutorials, this is what I have so far:
react-router-dom version: ^6.3.0
storybook: ^6.1.17
These are my components
sidebar.jsx
const SidebarNav = styled.nav`
background: #15171c;
width: 250px;
height: 100vh;
display: flex;
justify-content: center;
position: fixed;
top: 0;
right: ${({ sidebar }) => (sidebar ? "0" : "-100%")};
transition: 350ms;
z-index: 10;
`;
const SidebarWrap = styled.div`
width: 100%;
`;
const Sidebar = () => {
const [sidebar, setSidebar] = useState(true);
return (
<>
<IconContext.Provider value={{ color: "#fff" }}>
<SidebarNav sidebar={sidebar}>
<SidebarWrap>
{sideBarData.map((item, index) => {
return <SubMenu item={item} key={index} />;
})}
</SidebarWrap>
</SidebarNav>
</IconContext.Provider>
</>
);
};
export default Sidebar;
sidebardata.jsx
export const sideBarData = [ {
title: "Question 4",
path: "/test"
},
// {
// title: "Question 5",
// path: "/testing"
// }
];
sidebarsubmenu.jsx
import React, { useState } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
const SidebarLink = styled(Link)`
display: flex;
color: #e1e9fc;
justify-content: space-between;
align-items: center;
padding: 20px;
list-style: none;
height: 60px;
text-decoration: none;
font-size: 18px;
&:hover {
background: #252831;
border-left: 4px solid green;
cursor: pointer;
}
`;
const SidebarLabel = styled.span`
margin-left: 16px;
`;
const DropdownLink = styled(Link)`
background: #252831;
height: 60px;
padding-left: 3rem;
display: flex;
align-items: center;
text-decoration: none;
color: #f5f5f5;
font-size: 18px;
&:hover {
background: green;
cursor: pointer;
}
`;
const SubMenu = ({ item }) => {
const [subnav, setSubnav] = useState(false);
const showSubnav = () => setSubnav(!subnav);
return (
<>
<SidebarLink to={item.path}
onClick={item.subNav && showSubnav}>
<div>
{item.icon}
<SidebarLabel>{item.title}</SidebarLabel>
</div>
<div>
{item.subNav && subnav
? item.iconOpened
: item.subNav
? item.iconClosed
: null}
</div>
</SidebarLink>
{subnav &&
item.subNav.map((item, index) => {
return (
<DropdownLink to={item.path} key={index}>
{item.icon}
<SidebarLabel>{item.title}</SidebarLabel>
</DropdownLink>
);
})}
</>
);
};
export default SubMenu;
BookSideBarLayout.jsx
import React from 'react';
import Sidebar from './BookSideBar';
function Layout(props) {
return (
<div>
<div style={{display: "flex"}}>
<Sidebar/>
</div>
</div>
);
}
export default Layout;
BookSidebarRoutes.jsx
import React from "react";
import {BrowserRouter, Route,Routes} from "react-router-dom";
import Support from "./Support";
import Layout from "./BookSideBarLayout";
function MyRoutes() {
return (
<BrowserRouter>
<Layout/>
<Routes>
{/* <Route path="/" element={<Sidebar/>}/> */}
<Route path="/test" element={<Support/>}/>
</Routes>
</BrowserRouter>
);
}
export default MyRoutes;
The story that I created was as below
import { storiesOf } from '#storybook/react';
import StoryRouter from 'storybook-react-router';
import MyRoutes from '../../src/components/BookSidebarRoutes';
// export default {
// title: 'Side Bar',
// //decorators : [(Story) => (<MemoryRouter><Story/></MemoryRouter>)]
// };
storiesOf('Params', module)
.addDecorator(StoryRouter())
.add('params', () => (
<MyRoutes/>
));
export const Default = () => <MyRoutes />;
After I run my story above, I keep getting the error as below:
A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.**
I also tried Switch in the MyRoutes component but that too didn't work. Any guidance in this will be really helpful. Thank you in advance

Passing a direction prop into styled-components keyframe component?

I'm trying to implement a reusable text animation component where a direction prop can be passed in. I probably close but doesn't seem to be working. Also open to better ways of implementing it a better way.
import React from "react"
import styled from "styled-components"
import { GlobalStyles } from "./global"
import TextAnimations from "./Components/TextAnimations"
const Container = styled.div`
display: flex;
justify-content: space-between;
flex-wrap: wrap;
`
const NavBar = styled.nav`
background: #3a3a55;
padding: 0.25rem;
width: 100%;
height: 10vh;
`
const Main = styled.main`
background: #3a3a55;
color: white;
padding: 0.25rem;
flex: 10 1 auto;
height: 100vh;
`
const Break = styled.div`
flex-basis: 100%;
width: 0;
`
function App() {
return (
<>
<GlobalStyles />
<Container>
<NavBar>NavBar</NavBar>
<Break />
<Main>
<TextAnimations text='Sample text from the left' direction='left' />
<TextAnimations text='Sample text from the right' direction='right' />
</Main>
</Container>
</>
)
}
export default App
and then the animation component:
import { motion } from "framer-motion"
import styled, { keyframes } from "styled-components"
type TextAnimationProps = {
text: string
direction: string
}
const Left = keyframes`
0% { left: -3.125em; }
100% { left: 3em;}
`
const Right = keyframes`
0% { Right: -3.125em; }
100% { Right: 3em;}
`
const HomeHeader = styled.div`
h1 {
font-weight: lighter;
}
position: relative;
top: 0;
animation: ${(props) => (props.defaultValue === "left" ? Left : Right)} // is this right?
animation-duration: 3s;
animation-fill-mode: forwards;
`
const TextAnimations = ({ text, direction }: TextAnimationProps) => {
return (
<HomeHeader defaultValue={direction}>
<h1>{text}</h1>
</HomeHeader>
)
}
export default TextAnimations
I'd also like to make it more flexible so that I can add 'top', 'bottom', etc.
Is this the best way to handle text animations?
You can create a separate function to set the animation. The function will receive the props of the styled component from which the function will access the direction prop.
const setHomeHeaderAnimation = ({ direction = "left" }) => {
const animation = keyframes`
0% {
${direction}: -3.125em;
}
100% {
${direction}: 3em;
}
`;
return css`
position: relative;
animation: ${animation};
animation-duration: 3s;
animation-fill-mode: forwards;
`;
};
const HomeHeader = styled.div`
${setHomeHeaderAnimation}
h1 {
font-weight: lighter;
}
`;
const App = () => (
<div>
<HomeHeader>
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="right">
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="top">
<div>Some text</div>
</HomeHeader>
<HomeHeader direction="bottom">
<div>Some text</div>
</HomeHeader>
</div>
);

React Context API returns undefined

I'm quite new to React, and i'm trying to make a ToDoList. I have a Modal with a submit button that when pressed should add a ToDoItem. But since i didn't want to prop drill my way through this i wanted to use the Context API. The Context API confuses me quite a bit, maybe i'm just a moron, but i have a hard time understanding why i have to make a hook and pass that as the value in the provider. I thought that in the ToDoContext that i already defined the default value as a empty array, so i just did it again.
In the console at line 62, which is my initial render it says that it's undefined, after the pressing the Add ToDo I get the same message.
App.jsx
import React, { useState } from "react";
import { render } from "react-dom";
import { ThemeProvider } from "emotion-theming";
import { defaultTheme } from "./theme";
import { Global, css } from "#emotion/core";
import Header from "./components/Header";
import ToDoList from "./components/ToDoList";
import AddBtn from "./components/AddBtn";
import ToDoContext from "./ToDoContext";
const App = () => {
const [toDoItems] = useState([]);
return (
<>
{/*Global styling*/}
<Global
styles={css`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
list-style: none;
text-decoration: none;
}
`}
/>
{/*App render start from here*/}
<ThemeProvider theme={defaultTheme}>
<ToDoContext.Provider value={toDoItems}>
<Header />
<main>
<ToDoList />
<AddBtn />
</main>
</ToDoContext.Provider>
</ThemeProvider>
</>
);
};
render(<App />, document.getElementById("root"));
ToDoContext.jsx
import { createContext } from "react";
const ToDoContext = createContext([[], () => {}]);
export default ToDoContext;
AddBtn.jsx
import React, { useState, useContext } from "react";
import { css } from "emotion";
import Modal from "../Modal";
import ToDoContext from "../ToDoContext";
const BtnStyle = css`
position: fixed;
bottom: 0;
right: 0;
cursor: pointer;
display: block;
font-size: 7rem;
`;
const ModalDiv = css`
position: fixed;
left: 50%;
background-color: #e6e6e6;
width: 60%;
padding: 20px 20px 100px 20px;
display: flex;
flex-direction: column;
align-items: center;
max-width: 400px;
height: 50%;
transform: translate(-50%, -50%);
border-radius: 20px;
top: 50%;
`;
const textareaStyle = css`
resize: none;
width: 100%;
height: 200px;
font-size: 1.25rem;
padding: 5px 10px;
`;
const timeStyle = css`
font-size: 3rem;
display: block;
`;
const modalSubmit = css`
width: 100%;
font-size: 3rem;
cursor: pointer;
margin-top: auto;
`;
const Label = css`
font-size: 2rem;
text-align: center;
display: inline-block;
margin-bottom: 50px;
`;
const AddBtn = () => {
const [showModal, setShowModal] = useState(true);
const [time, setTime] = useState("01:00");
const [toDoItems, setToDoItems] = useContext(ToDoContext);
console.log(toDoItems);
return (
<>
<div className={BtnStyle} onClick={() => setShowModal(!showModal)}>
<ion-icon name="add-circle-outline"></ion-icon>
</div>
{showModal ? (
<Modal>
<div className={ModalDiv}>
<div>
<label className={Label} htmlFor="time">
Time
<input
className={timeStyle}
type="time"
name="time"
value={time}
onChange={(e) => setTime(e.target.value)}
/>
</label>
</div>
<label className={Label} htmlFor="desc">
Description
<textarea
className={textareaStyle}
name="desc"
placeholder={`Notify yourself this message in ${time}`}
></textarea>
</label>
<button
className={modalSubmit}
onClick={() => {
setToDoItems(
toDoItems.push({
time,
})
);
}}
>
Add ToDo
</button>
</div>
</Modal>
) : null}
</>
);
};
export default AddBtn;
There are few issues in your code to fix:
useState returns a value and a setter. With this line of code, const [toDoItems] = useState([]);, you are just passing an empty array to your context.
So do this:
const toDoItems = useState([]);
In your ToDoContext.js, just pass an empty array as argument (initial value)
const ToDoContext = createContext([]);
Working copy of your code is here. (see console logs)
Also, I noticed that you are pushing the todo in setTodoItems in AddBtn.js.
Don't do this:
onClick={() => {
setToDoItems(
toDoItems.push({
time
})
);
}}
Do this:
onClick={() => {
setToDoItems(
toDoItems.concat([
{
time
}
])
);
}}

React styled components with themeContext

I have the following React Header component in which I'm trying to get a theme value from the ThemeContext.Consumer and pass it into my NavListItem. It's currently working (sort of) but I'd like to only pass it once and have it applied globally. Instead of having to write theme={theme.typography.navigation} each time I use the styled component?
import React from "react"
import { Link } from "gatsby"
import PropTypes from "prop-types"
import styled from "styled-components"
import ThemeContext from "../context/ThemeContext"
const Main = styled.header`
height: 70px;
background-color: white;
position: fixed;
width: 100%;
left: 0;
`
const NavWrapper = styled.div`
position: relative;
`
const NavListLeft = styled.ul`
color: black;
position: absolute;
top: 25px;
left: 40px;
list-style: none;
margin: 0;
padding: 0;
`
const NavListItem = styled.li`
display: inline-block;
margin: 0 10px;
font-family: ${props => props.theme};
font-size: 16px;
`
const Header = ({ siteTitle }) => (
<ThemeContext.Consumer>
{theme => (
<Main>
<NavWrapper>
<NavListLeft>
<NavListItem theme={theme.typography.navigation}>
<Link style={{color: 'black'}}>Women</Link>
</NavListItem>
<NavListItem theme={theme.typography.navigation}>Men</NavListItem>
<NavListItem theme={theme.typography.navigation}>Designers</NavListItem>
<NavListItem theme={theme.typography.navigation}>Collection</NavListItem>
<NavListItem theme={theme.typography.navigation}>Sale</NavListItem>
</NavListLeft>
</NavWrapper>
</Main>
)}
</ThemeContext.Consumer>
)
Header.propTypes = {
siteTitle: PropTypes.string,
}
Header.defaultProps = {
siteTitle: ``,
}
export default Header
You could create a NavListItem that consumes your theme.
const ThemedNavListItem = styled.li`
display: inline-block;
margin: 0 10px;
font-family: ${props => props.theme.typography.navigation};
font-size: 16px;
`
const NavListItem = props => (
<ThemeContext.Consumer>
{theme => <ThemedNavListItem {...props} theme={theme} />}
</ThemeContext.Consumer>
);
const Header = ({ siteTitle }) => (
<Main>
<NavWrapper>
<NavListLeft>
<NavListItem>
<Link style={{color: 'black'}}>Women</Link>
</NavListItem>
<NavListItem>Men</NavListItem>
<NavListItem>Designers</NavListItem>
<NavListItem>Collection</NavListItem>
<NavListItem>Sale</NavListItem>
</NavListLeft>
</NavWrapper>
</Main>
);
A simpler solution using styled-components ThemeProvider.
A helper component for theming. Injects the theme into all styled
components anywhere beneath it in the component tree, via the context
API.
const NavListItem = styled.li`
display: inline-block;
margin: 0 10px;
font-family: ${props => props.theme.typography.navigation};
font-size: 16px;
`
const Header = ({ siteTitle }) => (
<ThemeProvider theme={theme}>
<Main>
<NavWrapper>
<NavListLeft>
<NavListItem>
<Link style={{color: 'black'}}>Women</Link>
</NavListItem>
<NavListItem>Men</NavListItem>
<NavListItem>Designers</NavListItem>
<NavListItem>Collection</NavListItem>
<NavListItem>Sale</NavListItem>
</NavListLeft>
</NavWrapper>
</Main>
</ThemeProvider>
);

Resources