Why function is empty that's passed form ThemeContext - reactjs

I am trying to call toggleTheme function from themeContext. But it is empty function.
themeContext.js
import React, { useState } from 'react';
export const ThemeContext = React.createContext({
theme: 'light',
toggleTheme: () => {}
});
export function ThemeContextProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
console.log("ToggleTheme")
setTheme(theme === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
//App.js
import React, { useContext } from "react";
import { ThemeContext, ThemeContextProvider } from "./theme/themeContext";
import styled from "styled-components";
const AppContainer = styled.div`
color: ${({ theme }) => theme.textPrimary};
background-color: ${({ theme }) => theme.background};
font-size: large;
font-weight: bold;
padding: 20px;
height: calc(100vh - 40px);
transition: all 0.5s;
`;
const Button = styled.button`
color: ${({ theme }) => theme.textPrimary};
background-color: ${({ theme }) => theme.background};
border: 2px ${({ theme }) => theme.textPrimary} solid;
float: right;
transition: all 0.5s;
cursor: pointer;
`;
function App() {
const { theme, toggleTheme } = useContext(ThemeContext);
const handleClick = () => {
console.log(theme);
console.log(toggleTheme);
toggleTheme();
};
return (
<ThemeContextProvider>
<AppContainer theme={theme}>
<Button onClick={() => handleClick()}>
Switch to {theme === "light" ? "Dark" : "Light"}
</Button>
{/* rest of your component code */}
</AppContainer>
</ThemeContextProvider>
);
}
export default App;

Related

React - Typescript - How to function as prop to another component

There are three components Toggle, ToggleMenu, Wrapper
The toggle should be universal, and used for different functions
The Wrapper should only change background color when toggle is on
The question is how to pass the function that responcible for changing color to togglemenu, that it executs when switch toggle
App
import React from 'react';
import { Wrapper } from './containers/wrapper';
import { Button } from './components/buttons/button';
import { ToggleMenu } from './components/settings/settings';
const App = () => {
return (
<>
<Wrapper>
<Button />
<ToggleMenu />
</Wrapper>
</>
)
}
export default App;
ToggleMenu
import styled from "styled-components";
import { Toggle } from "../buttons/toggle";
import { WrapperProps } from "../../containers/wrapper";
import { test } from "./test"
const SettingsContainer = styled.div`
margin: auto;
width: 50%;
height: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: white;
`;
const Container = styled.div`
height: 50%;
width: 50%;
display: flex;
justify-content: center;
flex-direction: column;
background-color: lime;
`;
const TogCon = styled.div`
margin: 0.5em;
`;
export const ToggleMenu = (props: WrapperProps) => {
return (
<SettingsContainer>
<Container>
<TogCon>
<Toggle toggleText={"Dark mode"} onChange={props.handleTheme}/>
</TogCon>
<TogCon>
<Toggle toggleText={"Sth"} onChange={() => {console.log("Sth")}}/>
</TogCon>
</Container>
</SettingsContainer>
);
};
Wrapper
import React, { useState } from "react";
import styled from "styled-components";
export type WrapperProps = {
children?: React.ReactNode;
color?: string;
handleTheme?: () => void;
};
const Container = styled.div<WrapperProps>`
width: 100%;
height: 100%;
top: 0;
left: 0;
position: fixed;
background-color: ${props => props.color }
`
export const Wrapper = ({ children }: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
return(
<Container
color={theme}
handleTheme={handleTheme}
> { children }
</Container>
);
}
Was solved with React.cloneElement
export const Wrapper = (props: WrapperProps) => {
const [theme, setTheme] = useState("black")
const handleTheme = () => {
theme === "black" ? setTheme("white"): setTheme("black")
}
const childrenWithProps = React.Children.map(props.children, child => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { handleTheme });
}
return child;
});
return(
<Container color={theme} >
{ childrenWithProps }
</Container>
);
}

Values getting undefined when consuming data using useContext in the child component

It is my first time working with useContext hook and I was trying to consume values with useContext in Navbar.js which I've provided in the parent component, but i'm receiving undefined only. In the Navbar.js when I console log user it showsundefined.
I tried to pass the data as props and it works fine. It only happens when I try with Context API. I think I messed up my context api implementation.
Created a context at AuthContext.js
import {createContext} from 'react'
export const AuthContext = createContext({})
Set values in App.js and wrapped using Provider.
import Login from "./pages/Login";
import Register from "./pages/Register";
import Cart from "./components/Cart/Cart";
import Slider from "./components/Slider";
import Categories from "./components/Categories";
import PopularProducts from "./components/PopularProducts";
import Navbar from "./components/Navbar";
import Checkout from "./components/Cart/Checkout";
import Products from "./components/Products";
import Footer from "./components/Footer";
import {
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
createUserWithEmailAndPassword,
} from "firebase/auth";
import { auth } from "./lib/firebase-config";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { useState, useEffect } from "react";
import { commerce } from "./lib/commerce";
import { AuthContext } from "./context/AuthContext";
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const [order, setOrder] = useState({});
const [errorMessage, setErrorMessage] = useState("");
const [loginEmail, setLoginEmail] = useState("");
const [loginPassword, setLoginPassword] = useState("");
const [user, setUser] = useState({});
const [registerEmail, setRegisterEmail] = useState("");
const [registerPassword, setRegisterPassword] = useState("");
const [isLoggedIn, setIsLoggedIn] = useState(
localStorage.getItem("isLoggedIn")
);
onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
const loginHandler = async (event) => {
event.preventDefault();
try {
const user = await signInWithEmailAndPassword(
auth,
loginEmail,
loginPassword
);
setIsLoggedIn(true);
localStorage.setItem("isLoggedIn", true);
console.log(auth.currentUser.email);
} catch (error) {
console.log(error.message);
}
};
const registerHandler = async () => {
try {
const user = await createUserWithEmailAndPassword(
auth,
registerEmail,
registerPassword
);
setIsLoggedIn(true);
localStorage.setItem("isLoggedIn", true);
console.log({ registerEmail });
console.log(user);
} catch (error) {
console.log(error.message);
}
};
const logoutHandler = async () => {
await signOut(auth);
setIsLoggedIn(false);
localStorage.clear();
};
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
};
const fetchCart = async () => {
setCart(await commerce.cart.retrieve());
};
const handleAddToCart = async (productId, quantity) => {
const item = await commerce.cart.add(productId, quantity);
setCart(item.cart);
};
const handleUpdateCartQty = async (lineItemId, quantity) => {
const response = await commerce.cart.update(lineItemId, { quantity });
setCart(response.cart);
};
const handleRemoveFromCart = async (lineItemId) => {
const response = await commerce.cart.remove(lineItemId);
setCart(response.cart);
};
const handleEmptyCart = async () => {
const response = await commerce.cart.empty();
setCart(response.cart);
};
const refreshCart = async () => {
const newCart = await commerce.cart.refresh();
setCart(newCart);
};
const handleCaptureCheckout = async (checkoutTokenId, newOrder) => {
try {
const incomingOrder = await commerce.checkout.capture(
checkoutTokenId,
newOrder
);
setOrder(incomingOrder);
refreshCart();
} catch (error) {
setErrorMessage(error.data.error.message);
console.log(error.data);
}
};
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
return (
<>
<AuthContext.Provider
value={
(user,
logoutHandler,
isLoggedIn) }>
<Navbar/>
</AuthContext.Provider>
</>
);
};
export default App;
Trying to consume data using useContext in Navbar.js
import { Badge } from "#material-ui/core";
import {
Search,
ShoppingCartOutlined,
TranslateOutlined,
} from "#material-ui/icons";
import { useContext } from "react";
import styled from "styled-components";
import { mobile } from "../responsive";
import { Link } from "react-router-dom";
import { AuthContext } from "../context/AuthContext";
const Parent = styled.div``;
const Container = styled.div`
height: 70px;
background-color: #b6e7f0;
${mobile({ height: "70px" })}
width: 98.75vw;
position: fixed;
top: 0;
z-index: 4;
`;
const Logo = styled.h4`
font-size: 30px;
color: black;
margin-left: 20px;
${mobile({ fontSize: "30px" })}
`;
const Wrapper = styled.div`
padding: 10px 0px;
display: flex;
justify-content: space-between;
max-width: 98.75vw;
${mobile({ padding: "10px 0px" })}
`;
const Left = styled.div`
display: flex;
align-items: center;
text-decoration: none;
width: 20%;
`;
const Center = styled.div``;
const Right = styled.div`
display: flex;
align-items: center;
margin-right: 20px;
justify-content: flex-end;
text-decoration: none;
${mobile({ flex: 2, justifyContent: "flexEnd" })}
`;
const Language = styled.span`
font-size: 14px;
cursor: pointer;
${mobile({ display: "none" })}
`;
const SearchContainer = styled.div`
flex: 1;
display: flex;
align-items: center;
margin-left: 0px;
margin-top: 10px;
padding: 5 px;
border-radius: 5px;
cursor: pointer;
${mobile({ display: "none" })}
&:focus {
}
`;
const Input = styled.input`
border: none;
&:focus {
outline: none;
}
width: 90%;
height: 100%;
border-radius: 5px;
font-size: 15px;
padding: 5px;
margin-right: 20px;
${mobile({ height: "50%" })}
`;
const MenuItem = styled.div`
margin-left: 25px;
margin-top: 3px;
font-size: 18px;
font-weight: 600;
color: #0a6596e6;
cursor: pointer;
justify-content: space-between;
${mobile({ fontSize: "13px", marginLeft: "10px" })}
`;
const Navbar = () => {
const { user, logoutHandler, isLoggedIn } = useContext(AuthContext);
console.log(user)
return (
<Parent>
<Container>
<Wrapper>
<Left>
<Link to={"/"}>
<Logo>audiobae</Logo>
</Link>
</Left>
<SearchContainer style={{ color: "gray", fontSize: 14 }}>
<Input placeholder="Search for products, brands and more" />
<Search style={{ fontSize: "30px" }} />
</SearchContainer>
<Right>
<Language style={{ marginLeft: "5px", marginTop: "3px" }}>
<TranslateOutlined />
</Language>
{console.log(isLoggedIn)}
{isLoggedIn ? (
<>
<MenuItem>{user?.email}</MenuItem>
<MenuItem onClick={logoutHandler}>Logout</MenuItem>
</>
) : (
<>
<Link to={"/register"} style={{ textDecoration: "none" }}>
<MenuItem>Register </MenuItem>
</Link>
<Link to={"/login"} style={{ textDecoration: "none" }}>
<MenuItem>Login</MenuItem>
</Link>
</>
)}
<MenuItem>
<Link to={"/cart"}>
<Badge badgeContent={cart?.total_items} color="primary">
{console.log(cart)}
<MenuItem>
<ShoppingCartOutlined />
</MenuItem>
</Badge>
</Link>
</MenuItem>
</Right>
</Wrapper>
</Container>
</Parent>
);
};
export default Navbar;
try it
<AuthContext.Provider value={{user,logoutHandler,isLoggedIn}}>
<Navbar/>
</AuthContext.Provider>

Styling Components (styled-components) | Superimposing one on top of the other

The problem is this:
There is a created UiInput React component using styled-components, above it there is also a UiInputExt React component, which should override some of the styles defined in UiInput, but for some reason this does not happen, apparently it does not even add the corresponding class...
I attach the code below:
const StyledInput = styled.input`
color: ${(props) => props.styles.color};
::placeholder,
::-webkit-input-placeholder {
color: ${(props) => props.styles.color};
}
:-moz-placeholder {
color: ${(props) => props.styles.color};
opacity: 1;
}
::-moz-placeholder {
color: ${(props) => props.styles.color};
opacity: 1;
}
:-ms-input-placeholder {
color: ${(props) => props.styles.color};
}
`;
<StyledInput
id={id}
className={cn(styles.input, classes)}
type={type}
placeholder={placeholder}
styles={isTheme.styles}
>
</StyledInput>
And the corresponding override (which doesn't work!)
import UiInput from '../';
const StyledUiInputExt = styled(UiInput)`
color: ${(props) => props.styles.color_uiinputext};
::placeholder,
::-webkit-input-placeholder {
color: ${(props) => props.styles.color_uiinputext};
}
:-moz-placeholder {
color: ${(props) => props.styles.color_uiinputext};
opacity: 1;
}
::-moz-placeholder {
color: ${(props) => props.styles.color_uiinputext};
opacity: 1;
}
:-ms-input-placeholder {
color: ${(props) => props.styles.color_uiinputext};
}
`;
<StyledUiInputExt classes={cn(styles.input, classes)} {...props} styles={isTheme.styles}></StyledUiInputExt>
You are not exporting the styled component StyledInput, rather you are exporting a plain React component that is rendering StyledInput.
const StyledInput = styled.input`
color: blue;
`;
const UiInput = () => {
return <StyledInput />;
};
export default UiInput;
UiInputExt
import UiInput from "./UiInput";
const StyledInputExt = styled(UiInput)`
color: green;
`;
const UiInputExt = () => {
return <StyledInputExt />;
};
The style you are applying to UiInput isn't applied to the JSX it renders, i.e. it isn't passed through to StyledInput.
You can only override other styled-components. For this you should export/import StyledInput (renamed to UiInput) directly.
const UiInput = styled.input`
color: blue;
`;
export default UiInput;
An alternative would be to export UiInput as a styled component and ensure that the className prop is passed through to the StyledInput component.
const StyledInput = styled.input`
color: blue;
`;
const UiInput = ({ className }) => {
return <StyledInput className={className} />;
};
export default styled(UiInput)``;

Property 'isOpen' does not exist on type 'IntrinsicAttributes. ts(2322)

I'm creating a navbar, but I'm not able to show the Wrapper menu when clicked.
The error occurs when inserting isOpen = {isOpen} in <Sidebar />
ERROR
Type '{ isOpen: boolean; toggle: () => void; }' is not assignable to type 'IntrinsicAttribute. Property 'isOpen' does not exist on type 'IntrinsicAttributes. ts(2322)
pages/Home.tsx
import React, { useState } from 'react';
...
import Navbar from '../../components/Navbar';
import Sidebar from '../../components/Sidebar';
interface SidebarProps {
isOpen?: boolean;
}
const Home: React.FC<SidebarProps> = () => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => {
setIsOpen(!isOpen);
};
return (
<>
<Sidebar isOpen={isOpen} toggle={toggle} /> <----------- ERRO
<Navbar />
<HomePage>
...
</HomePage>
components/Sidebar/styles.tsx
import { FaTimes } from 'react-icons/fa';
import { Link as LinkScroll } from 'react-scroll';
import styled from 'styled-components';
interface SidebarProps {
isOpen?: boolean;
}
export const SidebarContainer = styled.aside<SidebarProps>`
position: fixed;
z-index: 999;
width: 100%;
height: 100%;
background: #010311;
display: grid;
align-items: center;
top: 0;
left: 0;
transition: 0.4s ease-in-out;
opacity: ${({ isOpen }) => (isOpen ? '100%' : '0')};
top: ${({ isOpen }) => (isOpen ? '0' : '-100%')};
`;
I think in the styled component you should access the boolean isOpen in the following way:
opacity: ${props => props.isOpen ? '100%' : '0'}

React Storybook Checkbox change handler not working

I am creating a checkbox component using React, Typescript, Storybook and Styled-Components, after following this tutorial: building-a-checkbox-component-with-react-and-styled-components. I have had to adjust the code as I am using a FunctionComponent, but I am facing an issue with the change handler. I cannot check or uncheck the Checkbox which seems to be readonly, and I'm not sure where I am going wrong. Here is my code:
Checkbox.tsx
import React from 'react';
import styled from 'styled-components';
import { FunctionComponent } from 'react';
type CheckboxProps = {
checked?: boolean;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
const CheckboxContainer = styled.div`
display: inline-block;
vertical-align: middle;
`;
const Icon = styled.svg`
fill: none;
stroke: white;
stroke-width: 2px;
`;
const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
border: 0;
clip: rect(0 0 0 0);
clippath: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
`;
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: ${(props: CheckboxProps) => (props.checked ? 'green' : 'white')};
border-color: 'dark gray';
border-width: 1px;
border-style: solid;
border-radius: 2px;
${HiddenCheckbox}:focus + & {
box-shadow: 0 0 0 3px grey;
}
${Icon} {
visibility: ${(props: CheckboxProps) => (props.checked ? 'visible' : 'hidden')};
}
`;
const Checkbox: FunctionComponent<CheckboxProps> = ({ onChange, checked, ...props }) => {
return (
<CheckboxContainer>
<HiddenCheckbox checked={checked} {...props} onChange={onChange} />
<StyledCheckbox data-testid="styledcheckbox" checked={checked} {...props} onChange={onChange}>
<Icon viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</Icon>
</StyledCheckbox>
</CheckboxContainer>
);
};
export default Checkbox;
Checkbox.stories.js
// Checkbox.stories.js
import React, { useState, useRef } from 'react';
import Checkbox from '#components/Checkbox/Checkbox';
import { action } from '#storybook/addon-actions';
import { storiesOf } from '#storybook/react';
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const CheckboxStateful = props => {
const [value, setValue] = useState(props);
const valRef = useRef(value);
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const onChange = event => {
setValue(event.target.value);
valRef.current = event.target.value;
};
return (
<Checkbox
value={value}
onChange={event => {
onChange(event);
}}
></Checkbox>
);
};
storiesOf('Checkbox', module)
.add('with checked', () => {
const value = true;
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const onChange = event => setValue(event.target.value);
return <CheckboxStateful value={value} onChange={onChange}></CheckboxStateful>;
})
.add('with unchecked', () => {
const value = false;
// eslint-disable-next-line #typescript-eslint/explicit-function-return-type
const onChange = event => setValue(event.target.value);
return <CheckboxStateful value={value} onChange={onChange}></CheckboxStateful>;
});
As I am a novice at both React and Storybook I may require some expert input into whether my change handler code is correct or not. I have looked at other examples, but none of them use Typescript. Any help would be appreciated.
Solution is to change onChange to onClick in CheckboxProps in Checkbox.tsx, as follows:
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
and to simplify CheckboxStateful in Checkbox.stories.js as follows:
const CheckboxStateful = () => {
const [value, setValue] = useState(false);
return <Checkbox checked={value} onClick={() => setValue(!value)}></Checkbox>;
};
The complete updated code is now as follows:
Checkbox.tsx
import * as React from 'react';
import styled from 'styled-components';
import { FunctionComponent } from 'react';
type CheckboxProps = {
checked?: boolean;
onClick: (event: React.MouseEvent<HTMLElement, MouseEvent>) => void;
};
const CheckboxContainer = styled.div`
display: inline-block;
vertical-align: middle;
`;
const Icon = styled.svg`
fill: none;
stroke: white;
stroke-width: 2px;
`;
const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
border: 0;
clip: rect(0 0 0 0);
clippath: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
`;
const StyledCheckbox = styled.div`
display: inline-block;
width: 16px;
height: 16px;
background: ${(props: CheckboxProps) => (props.checked ? 'green' : 'white')};
border-color: 'dark gray';
border-width: 1px;
border-style: solid;
border-radius: 2px;
${HiddenCheckbox}:focus + & {
box-shadow: 0 0 0 3px grey;
}
${Icon} {
visibility: ${(props: CheckboxProps) => (props.checked ? 'visible' : 'hidden')};
}
`;
const Checkbox: FunctionComponent<CheckboxProps> = ({ onClick, checked, ...props }) => {
return (
<CheckboxContainer>
<HiddenCheckbox checked={checked} {...props} />
<StyledCheckbox checked={checked} {...props} onClick={onClick} data-testid="styledcheckbox">
<Icon viewBox="0 0 24 24">
<polyline points="20 6 9 17 4 12" />
</Icon>
</StyledCheckbox>
</CheckboxContainer>
);
};
export default Checkbox;
Checkbox.stories.js
/* eslint-disable #typescript-eslint/explicit-function-return-type */
// Checkbox.stories.js
import * as React from 'react';
import { useState } from 'react';
import Checkbox from '#components/Checkbox/Checkbox';
import { action } from '#storybook/addon-actions';
import { storiesOf } from '#storybook/react';
export default {
title: 'Checkbox',
component: Checkbox,
};
const CheckboxStateful = () => {
const [value, setValue] = useState(false);
return <Checkbox checked={value} onClick={() => setValue(!value)}></Checkbox>;
};
storiesOf('Checkbox', module)
.add('with checked', () => {
const value = true;
return <Checkbox checked={value} onClick={action('checked')}></Checkbox>;
})
.add('with unchecked', () => {
const value = false;
return <Checkbox checked={value} onClick={action('checked')}></Checkbox>;
})
.add('stateful', () => {
return <CheckboxStateful />;
});
Once you create the React project and add Storybook, just run yarn storybook and the checkboxes will show up correctly.
Instead of creating a new Stateful component a much better approach is to use state in the components template.
An example:
const Template: ComponentStory<typeof MyComponent> = (args) => {
const [state, setState] = useState('');
return (
<MyComponent
prop1={args.prop1}
prop2={args.prop2}
onChange={() => setState('Change')}
/>
);
};
Here's a link to the original answer I got this approach from:
https://stackoverflow.com/a/64722559/10641401

Resources