State managed in Context is showing undefined - reactjs

I'm implementing a dark theme to understand React context. I successfully created a dark mode that saves mode to local storage and determined if the user prefers dark mode from local storage.
However now I want to refactor to keep the theme state in context.
I have moved the code in a theme context however I get the error stating
ERROR: TypeError: Cannot read property 'darkMode' of undefined
I can't seem to work it out. I was under the impression that I could pass the state in this case darkMode and setDarkMode into my App component using useContext?
import React, { useContext } from 'react';
import ThemeContextProvider, { ThemeContext } from './contexts/ThemeContext';
function App() {
const { darkMode, setDarkMode } = useContext(ThemeContext);
return (
<div className='App'>
<ThemeContextProvider>
<div className={darkMode ? 'dark-mode' : 'light-mode'}>
<nav>
<div className='toggle-container'>
<span style={{ color: darkMode ? 'grey' : 'yellow' }}>☀︎</span>
<span className='toggle'>
<input
checked={darkMode}
onChange={() => setDarkMode((prevMode) => !prevMode)}
type='checkbox'
className='checkbox'
id='checkbox'
/>
<label htmlFor='checkbox' />
</span>
<span style={{ color: darkMode ? '#9c27b0' : 'grey' }}>☽</span>
</div>
</nav>
<main>
<h1>{darkMode ? 'Dark Mode' : 'Light Mode'}</h1>
<h2>Toggle the switch to change theme</h2>
</main>
</div>
</ThemeContextProvider>
</div>
);
}
and the ThemeContext
import React, { createContext, useState, useEffect } from 'react';
export const ThemeContext = createContext();
const ThemeContextProvider = (props) => {
const [darkMode, setDarkMode] = useState(getInitialMode);
useEffect(() => {
localStorage.setItem('dark', JSON.stringify(darkMode));
getPrefColourScheme();
}, [darkMode]);
function getInitialMode() {
const isReturningUser = 'dark' in localStorage;
const savedMode = JSON.parse(localStorage.getItem('dark'));
const userPrefersDark = getPrefColourScheme();
// if mode was saved -> dark / light
if (isReturningUser) {
return savedMode;
// if preferred colour scheme is dark -> dark
} else if (userPrefersDark) {
return true;
// otherwise -> light
} else {
return false;
}
}
function getPrefColourScheme() {
if (!window.matchMedia) return;
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
return (
<ThemeContext.Provider value={{ darkMode, setDarkMode }}>
{props.children}
</ThemeContext.Provider>
);
};
export default ThemeContextProvider;
Forgive my ignorance i'm struggling to wrap my head around this problem.
Any help would be grateful thanks.

I think you can only use
const { darkMode, setDarkMode } = useContext(ThemeContext);
whenever some component above the one that uses this hook has a <Context.Provider>
However, you're using this hook inside your App component - it's not a child of your Provider.
What you can do is separate the children to a new component and use the hook there, or render your <App /> as a child of your <ThemeContextProvider> (which means moving your <ThemeContextProvider> to another place)
Option 1
const FooComp = () => {
const { darkMode, setDarkMode } = useContext(ThemeContext);
return (
<div className={darkMode ? 'dark-mode' : 'light-mode'}>
<nav>
<div className='toggle-container'>
<span style={{ color: darkMode ? 'grey' : 'yellow' }}>☀︎</span>
<span className='toggle'>
<input
checked={darkMode}
onChange={() => setDarkMode((prevMode) => !prevMode)}
type='checkbox'
className='checkbox'
id='checkbox'
/>
<label htmlFor='checkbox' />
</span>
<span style={{ color: darkMode ? '#9c27b0' : 'grey' }}>☽</span>
</div>
</nav>
<main>
<h1>{darkMode ? 'Dark Mode' : 'Light Mode'}</h1>
<h2>Toggle the switch to change theme</h2>
</main>
</div>
)
}
then in App
function App() {
return (
<div className='App'>
<ThemeContextProvider><FooComp /></ThemeContextProvider>
)
}
or Option 2
in the place you're rendering App you do
<ThemeContextProvider><App /></ThemeContextProvider>
and you remove ThemeContextProvider from App

If you are using a class component and getting this issue
context is stored in a variable called context in a class component
import React, {Component, useContext} from 'react';
import {ThemeContext} from '../contexts/ThemeContext';
class Navbar extends Component {
static contextType = ThemeContext
render() {
console.log(this.context);
return (
<nav>
<h1>Content Apps</h1>
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
);
}
}
export default Navbar;

Related

Can't make different pages into one depending user button selection ReactJS

I want to load a component depending in what page are the user.
Pages:
Executables
Shop
In the main screen I have a sidebar with 2 icons that i want the primary button sets the Executables Page and the second shop page.
Like having a web page with no routes and rendering components depending the user selection.
My code:
Components/Dashboard.tsx
import styled from "styled-components"
import Executable from "./Executable"
import Navbar from "./Navbar"
import { useEffect } from "react"
type EntryProps = {
section: string
}
const Dashboard = ({ section }: EntryProps) => {
var TypeElement
useEffect(() => {
if (section === "executables") {
TypeElement = (
<div className="grid">
<div className="row">
<Executable />
</div>
</div>
)
}
}, [section])
return (
<Section>
<Navbar />
{TypeElement}
</Section>
)
}
Components/Sidebar.tsx
import styled from "styled-components"
import { FaBars } from "react-icons/fa"
import { BsFileEarmarkBinary } from "react-icons/bs"
import { BiLogOut } from "react-icons/bi"
import { AiOutlineShoppingCart } from "react-icons/ai"
import { IoMdSettings } from "react-icons/io"
import { useState } from "react"
type SidebarProps = {
section: string
setSection: Function
}
const Sidebar = ({ section, setSection }: SidebarProps) => {
const [disabled, setDisabled] = useState(true)
const handleDisabled = () => setDisabled(!disabled)
return (
<Aside id="aside">
<div
className={disabled ? "brand center" : "brand"}
onClick={handleDisabled}
>
<FaBars />
</div>
<ul className="links">
<li>
<BsFileEarmarkBinary />
<span
className={disabled ? "disabled" : ""}
onClick={(e) => {
console.log("Executables")
setSection("executables")
}}
>
Executables
</span>
</li>
<li>
<AiOutlineShoppingCart />
<span
className={disabled ? "disabled" : ""}
onClick={(e) => {
e.preventDefault()
setSection("shop")
}}
>
Shop
</span>
</li>
<li>
<IoMdSettings />
<span
className={disabled ? "disabled" : ""}
onClick={setSection("settings")}
>
Settings
</span>
</li>
</ul>
<div className="logout">
<BiLogOut />
</div>
</Aside>
)
}
Pages/DashboardPage.tsx
import styled from "styled-components"
// Components
import Sidebar from "../components/Sidebar"
import Rightsidebar from "../components/Rightsidebar"
import Dashboard from "../components/Dashboard"
import { useState } from "react"
const DashboardPage = () => {
const [page, setPage] = useState("executables")
const setSection = (name: string) => {
setPage(name)
}
return (
<Div>
<Sidebar section={page} setSection={setSection} />
<Dashboard section={page} />
<Rightsidebar />
</Div>
)
}
You are changing the value of TypeElement conditionally according to the value of the section. TypeElement is not a state, so after changing the value of the TypeElement component is not rerendered, and the updated value is not showing on the UI. Here conditional rendering might be a good solution.
<Section>
<Navbar />
{section==='executables'? <Executable />: <Shop/>}
</Section>

Error : "createSliderWithTooltip is not a function"

Im trying to implement the rc-slider for the web app however the rc-slider tooltip isnt recognized and shows an error of "createSliderWithTooltip is not a function" , which im not sure why .
For implementation of rc-slider i followed the rc-slider documentation which is the same way i have implemeneted in the code of home.js somehow im getting an error in console and nothing shows at all.
Thanks in advance.
Home.js
import React, { Fragment, useEffect , useState } from 'react'
import MetaData from './layouts/MetaData'
import { useDispatch , useSelector } from 'react-redux'
import { getProducts } from '../actions/products'
import Product from './products/Products'
import Loader from './layouts/Loader'
import { useAlert } from 'react-alert'
import Pagination from "react-js-pagination";
import {useParams} from 'react-router-dom'
import Slider from 'rc-slider'
import 'rc-slider/assets/index.css';
const { createSliderWithTooltip } = Slider;**//Error occurs here**
const Range = createSliderWithTooltip(Slider.Range)
const Home = () => {
const [currentPage,setCurrentPage]=useState(1);
const [price,setPrice]=useState([1,1000]);
let params=useParams();
const dispatch= useDispatch();
const alert=useAlert();
const {loading,products,error,productsCount,resPerPage,filteredProductsCount }= useSelector(state=>state.products)
const keyword=params.keyword;
useEffect(() => {
if (error) {
return alert.error("error");
}
dispatch(getProducts(keyword, currentPage));
}, [dispatch, alert, error, currentPage, keyword]);
function setCurrentPageNo(pageNumber) {
setCurrentPage(pageNumber)
}
return (
<Fragment>
{loading ? <Loader>Loading ...</Loader>:(
<Fragment>
<MetaData title={'Buy Electronics , Household Items and Many Others Online'} />
<h1 id="products_heading">Latest Products</h1>
<section id="products" className="container mt-5">
<div className="row">
<Fragment>
<div className="col-6 col-md-3 mt-5 mb-5">
<div className="px-5">
<Range
marks={{
1: `$1`,
1000: `$1000`
}}
min={1}
max={1000}
defaultValue={[1, 1000]}
tipFormatter={value => `$${value}`}
tipProps={{
placement: "top",
visible: true
}}
value={price}
onChange={price => setPrice(price)}
/>
</div>
</div>
</Fragment>
{products.map(product => (
<Product key={product._id} product={product} col={4} />
))}
</div>
</section>
<div className="d-flex justify-content-center mt-5">
<Pagination
activePage={currentPage}
itemsCountPerPage={resPerPage}
totalItemsCount={productsCount}
onChange={setCurrentPageNo}//sets current page no as it changes for state management
nextPageText={'Next'}
prevPageText={'Prev'}
itemClass="page-item"
linkClass="page-link"
/>
</div>
</Fragment>
)
}
</Fragment>
)}
export default Home
Instead of const { createSliderWithTooltip } = Slider;, try this:
const createSliderWithTooltip = Slider.createSliderWithTooltip;
I tried several ways and the only thing that actually worked was downgrading to 9.6.5 rc-slider and now everything is working perfectly
The document hasn't been updated yet since new version. As it seems you want to use Range component, now here the way to do it (thanks to Ashvin-Pal): https://github.com/react-component/slider/issues/825#issuecomment-1084416952
The createSliderWithTooltip has been removed in the new version.
Instead, you can implement your custom handle or tooltip easily like this:
handleRender={renderProps => {
return (
<div {...renderProps.props}>
<SliderTooltip>{round}%</SliderTooltip>
</div>
);
}}
let me know if you have any questions.

Add an element with React, Typescript and useContext

I'm new to Typescript and not very familiar with the useContext hook. Basically, I have two simple components. I would like to add the items from my left component to the list on the right when I click on the button under them. My items have a name and description property. I just watch to display item.name on the side div on the right.
I would like to try and do it with useContext but I'm not sure where to start even after reading the documentation and a bunch of examples. They all seem too complicated for my tiny little example.
From what I understand, I need to:
Create something like AppContext.tsx
Create a context with createContext() // not sure about the arguments I have to put it in here with Typescript
Create a provider? // not sure about that either
Wrap my two components with the context provider
So any hint on the procedure would be appreciated. Thank you!
function App() {
return (
<div className="App">
<ItemList />
<ItemContainer />
</div>
);
}
My item list component:
function ItemList() {
return (
<div className="itemlist">
{items.map((item, index) => (
<div key={index}>
<div>{item.name}</div>
<div>{item.description}</div>
<button>Add to sidebar</button>
</div>
))}
</div>
);
}
And finally, my container on the right side:
function ItemContainer() {
return (
<div>
<h1>List of items</h1>
<p>Number of items: {}</p>
</div>
);
}
You can do something like this:
First create a context file named for example ItemList.context.tsx :
import React, {
createContext,
Dispatch,
FunctionComponent,
useState
} from "react";
import { Item } from "./data";
type ItemListContextType = {
itemList: Item[]; // type of your items that I declare in data.ts
setItemList: Dispatch<React.SetStateAction<Item[]>>; //React setState type
};
export const ItemListContext = createContext<ItemListContextType>(
{} as ItemListContextType
);
export const ItemListContextProvider: FunctionComponent = ({ children }) => {
const [itemList, setItemList] = useState<Item[]>([]);
return (
<ItemListContext.Provider
value={{ itemList: itemList, setItemList: setItemList }}
>
{children}
</ItemListContext.Provider>
);
};
You can then add the context provider in the parent component ( App.tsx in your example):
import "./styles.css";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { ItemListContextProvider } from "./ItemList.context";
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<ItemList />
<ItemContainer />
</ItemListContextProvider>
</div>
);
}
and you can finally access your Item List by using the hook useContext in your two components:
for ItemList.tsx where you need to set the list (and optionally get the list to avoid putting twice an item):
import { useContext } from "react";
import { data, Item } from "./data";
import { ItemListContext } from "./ItemList.context";
const items: Item[] = data;
export default function ItemList() {
const { itemList, setItemList } = useContext(ItemListContext); // here you get your list and the method to set the list
const addItemToItemList = (item: Item) => {
//you are using the itemList to see if item is already in the itemList
if (!itemList.includes(item)) setItemList((prev) => [...prev, item]);
};
return (
<div className="itemlist">
{items.map((item, index) => (
<div style={{ marginBottom: 15 }} key={index}>
<div style={{ fontWeight: 800 }}>{item.name}</div>
<div>{item.description}</div>
<button onClick={() => addItemToItemList(item)}>
Add to sidebar
</button>
</div>
))}
</div>
);
}
And in your ItemContainer.tsx you only need the list so you can import only the setItemList from the context with useContext:
import { useContext } from "react";
import { ItemListContext } from "./ItemList.context";
export default function ItemContainer() {
const { itemList } = useContext(ItemListContext);
return (
<div style={{ flexGrow: 4 }}>
<h1 style={{ textAlign: "center" }}>List of items</h1>
<p>Number of items: {itemList.length}</p>
{itemList.length > 0 && (
<ul>
{itemList.map((item, i) => (
<li key={i}>{item.name}</li>
))}
</ul>
)}
</div>
);
}
UPDATE with a Router
It's quite the same thing you only need to wrap your browser router in the context provider if you want it to be at the highest place in your app (for a them provider or a dark mode provider for example):
export default function App() {
return (
<div className="App">
<ItemListContextProvider>
<BrowserRouter>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/itemList" component={ItemListPage} />
</Switch>
</BrowserRouter>
</ItemListContextProvider>
</div>
);
}
but I suggest you to put your provider the nearest place where your subscribers components are.
You can for example create a page component that will be use in the browser router and put in it the provider, like this:
ItemListPage.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;
and of course you remove the context provider in your App.tsx and it should look like :
App.tsx
import React, { FunctionComponent } from "react";
import ItemList from "./ItemList";
import ItemContainer from "./ItemContainer";
import { Link } from "react-router-dom";
import { ItemListContextProvider } from "./ItemList.context";
const ItemListPage: FunctionComponent = () => {
return (
<>
<ItemListContextProvider>
<h1 style={{ alignSelf: "flex-start" }}>ITEM LIST</h1>
<Link to="/">homePage</Link>
<div className="itemListPage">
<ItemList />
<ItemContainer />
</div>
</ItemListContextProvider>
</>
);
};
export default ItemListPage;

nested problems about react usestate and setstate

Hi I mimic a project and there is no problem but can not display the ui,I am not sure what kind of problem it is.The object project link:https://codesandbox.io/s/learning-react-color-organizer-2-iytxb?file=/src/StarRating.js
My project link:https://codesandbox.io/s/nervous-flower-xv669?file=/src/App.js
My project has 2 warnings,this first warning is Each child in a list should have a unique "key" prop. which direct to the app.js:
import React, { useState } from "react";
import Data from "./color-data.json";
import RatingList from "./RatingList";
export default function App() {
const [colors,setColors]=useState(Data)
const removeId=id=>{
const newcolors=colors.filter(color=>color.id !== id);
setColors(newcolors);
}
return (
<div>
{
<RatingList
colors={colors}
onDelete={removeId}
/>
}
</div>
);
}
the second warning is Cannot update a component (App) while rendering a different component (ColorRating).which direct to the ColorRating.js:
import React from "react";
import Star from "./Star";
import { AiFillDelete } from "react-icons/ai";
export default function ColorRating({id,
title,
color,
rating,
onDelete = (f) => f,
}) {
return (
<div>
<h1>{title}</h1>
<h1>
<AiFillDelete onClick={onDelete(id)} />
</h1>
<h1 style={{ backgroundColor: color, padding: "30px" }}> </h1>
{[...Array(5)].map((n, i) => (
<Star key={i} selected={rating > i} />
))}
<p>{rating} of 5 stars</p>
</div>
);
}
No, I mean remove the { } curly brackets from the return block.
And also, change this line:
<AiFillDelete onClick={onDelete(id)} />
to this (right now you invoke it's on every rerender):
<AiFillDelete onClick={() => onDelete(id)} />

Icon className doesn't update on state change

I am trying simplest ever theme toggle in react with context and don't seem to be able to get the icon re-render when context changes. Everything else works just fine: colors, background image... It renders either of icons depending on initial state, but icon doesn't update when theme is toggled.
import React, { useContext } from "react"
import { ThemeContext } from "../../contexts/ThemeContext"
const ThemeToggle = () => {
const { isDarkMode, dark, light, toggleTheme } = useContext(ThemeContext)
const theme = isDarkMode ? dark : light
return (
<li
style={{ background: theme.bgPrimary, color: theme.text }}
onClick={toggleTheme}
>
<i className={theme.ico} />
</li>
)
}
export default ThemeToggle
Context:
import React, { Component, createContext } from "react"
export const ThemeContext = createContext()
class ThemeContexProvider extends Component {
state = {
isDarkMode: false,
light: {
text: "#333",
bgPrimary: "#eee",
bgSecondary: "#333",
ico: "fas fa-moon"
},
dark: {
text: "#ddd",
bgPrimary: "#000003",
bgSecondary: "#bbb",
ico: "fas fa-sun"
}
}
toggleTheme = () => {
this.setState({ isDarkMode: !this.state.isDarkMode })
}
render() {
return (
<ThemeContext.Provider
value={{ ...this.state, toggleTheme: this.toggleTheme }}
>
{this.props.children}
</ThemeContext.Provider>
)
}
}
export default ThemeContexProvider
I fixed this installing dedicated react fa package, still don't know why above doesn't work. This works though:
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome"
import { faMoon } from "#fortawesome/free-solid-svg-icons"
import { faSun } from "#fortawesome/free-solid-svg-icons"
//.....
return (
<li
style={{ background: theme.bgPrimary, color: theme.text }}
onClick={toggleTheme}
>
{isDarkMode ? (
<FontAwesomeIcon icon={faSun} />
) : (
<FontAwesomeIcon icon={faMoon} />
)}
</li>
I suspect the spaces in your ico property could be the issue. Normally this isn't an issue for state/props. Context is possibly to blame for this. This might fix it:
<i className={`fas ${theme.ico}`} />
Replace your context ico properties with just the class that changes fa-moon and fa-sun
You could have used key props in the icon tag. Keys help React identify which items have changed. So it can rerender your icon.
{isDarkMode ? (
<i key={1} icon={faSun} />
) : (
<i key={2} icon={faMoon} />
)}

Resources