React styled components with themeContext - reactjs

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>
);

Related

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

Add animation to map components in React

So i take my data from sanity then map them and then render them as components
my code is
function Projects() {
return <ProjectList projects={projectData} />;
}
export default Projects;
import ProjectItem from "./ProjectItem";
function ProjectList(props) {
return (
<ul>
{props.projects.map((project) => (
<ProjectItem
key={project.id}
id={project.id}
title={project.title}
description={project.desc}
/>
))}
</ul>
);
}
export default ProjectList;
Card,
Project,
ProjectTitle,
ProjectDesc,
ProjectUrl,
BtnWrap,
} from "./ProjecItemElements";
function ProjectItem(props) {
return (
<Project>
<Card>
<ProjectTitle>{props.title}</ProjectTitle>
<ProjectDesc>{props.description}</ProjectDesc>
<BtnWrap>
<ProjectUrl>Test</ProjectUrl>
</BtnWrap>
</Card>
</Project>
);
}
export default ProjectItem;
Card, Project, ProjectTitle, ProjectDesc, ProjectUrl, BtnWrap, are custom components so i can customize them using that
import styled from "styled-components";
export const Card = styled.div`
background-color: white;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
`;
export const Project = styled.li`
margin: 1rem 0;
padding: 1rem;
`;
export const ProjectTitle = styled.h3`
text-align: center;
font-size: 1.25rem;
color: #2c292b;
`;
export const ProjectDesc = styled.p`
color: black;
`;
export const BtnWrap = styled.div`
text-align: right;
width: 100%;
padding: 0 1.5rem 1.5rem 0;
`;
export const ProjectUrl = styled.button`
text-decoration: none;
color: initial;
`;
what i want is to animate the components in the ul
{props.projects.map((project) => (
<ProjectItem
key={project.id}
id={project.id}
title={project.title}
description={project.desc}
/>
so they will come from the bottom to top with a delay when the page 'loads'
(its not really load because React)

Using styled-components

Can someone tell me why my styled components aren't working. I was following an example where the person put the css right in their react app using back ticks but my is not working like thiers.
import React from "react";
import styled from "styled-components";
const Wrapper = styled.div`
position: absolute;
left: ${(props) => (props.position === "left" ? "20px" : "380px")}
top: 20px
background: ${(props) => (props.lampOn ? "orange" : "lightgrey")};
width: 100px;
height: 100px;
border-radius: 50%;
`;
const Lamp = ({ lampOn, position }) => {
return (
<Wrapper lampOn={lampOn} position={position}>
<div>Test light oo </div>
</Wrapper>
);
};
export default Lamp;
Missing semi colons:
Refer to https://stackblitz.com/edit/react-thagj7
import React from 'react';
import styled from 'styled-components';
export default function App() {
return (
<div>
<h1>Hello StackBlitz!</h1>
<Lamp lampOn position="left" />
</div>
);
}
const Wrapper = styled.div`
position: absolute;
left: ${props => (props.position === 'left' ? '20px' : '380px')};
top: 200px;
background: ${props => (props.lampOn ? 'orange' : 'lightgrey')};
width: 100px;
height: 100px;
border-radius: 50%;
`;
const Lamp = ({ lampOn, position }) => {
return (
<Wrapper lampOn={lampOn} position={position}>
<div>Test light oo </div>
</Wrapper>
);
};

Styled-components: Styles are not applied when trying to style already styled component

I'm trying to style my component which was styled already. But styles in the new rules are not applied in output CSS.
Can I style component that I already styled?
Thanks you in advance for your help.
EDIT: Add rest of LanugageChooser definition
// COMPONENT THAT I'M TRYING TO STYLE
const LanguageChooser = () => {
const Container = styled.div`
display: flex;
align-items: center;
height: 36px;
& > div:not(:last-child) {
margin-right: 5px;
}
`;
return (
<Container>
<Flag { ...languages.pl }/>
<Flag { ...languages.en }/>
</Container>
)
}
const Flag = ({ flag, language }) => {
const { i18n } = useTranslation();
const Button = styled.div`
cursor: pointer;
font-size: 24px;
transition: .2s all;
&:hover {
font-size: 36px;
}
`;
return (
<Button onClick={ () => i18n.changeLanguage(language) }>{ flag }</Button>
)
}
// TRYING ADD MARGIN LEFT, BUT THERE IS NO RESULT.
// ANY OTHER CSS PROPERTY ARE NOT APPLYING
const Navbar = ({ color }) => {
...
const StyledLanguageChooser = styled(LanguageChooser)`
margin-left: auto;
`;
const Nav = styled.nav`
display: flex;
align-content:center;
background: ${ color };
padding: 2px 3px;
`;
return (
<Nav className="navbar">
<StyledNavLink to="/home">...</StyledNavLink>
<StyledNavLink to="/maps">...</StyledNavLink>
<StyledNavLink to="/charts">...</StyledNavLink>
<StyledLanguageChooser/>
</Nav>
)
}
First, move the styled component outside of function scope or your style will reset on every render and you will get heavy performance issues.
Secondly, in order to apply styles, you need to pass className property.
See Styling normal React components
If you use the styled(MyComponent) notation and MyComponent does not render the passed-in className prop, then no styles will be applied.
const Container = styled.div`
display: flex;
align-items: center;
height: 36px;
& > div:not(:last-child) {
margin-right: 5px;
}
`;
const LanguageChooser = ({ className }) => {
return (
<Container className={className}>
<Flag {...languages.pl} />
<Flag {...languages.en} />
</Container>
);
};
const Button = styled.div`
cursor: pointer;
font-size: 24px;
transition: 0.2s all;
&:hover {
font-size: 36px;
}
`;
const Flag = ({ flag, language }) => {
const { i18n } = useTranslation();
return <Button onClick={() => i18n.changeLanguage(language)}>{flag}</Button>;
};

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
}
])
);
}}

Resources