Unable to pass down state using react context - reactjs

I am trying to use React Context successfully but I a have had a lot of trouble with it. I can't even successfully pass anything one level to the provider's immediate children, as a result all I am getting at this stage is "x is undefined" errors in the console. I am using a separate class for the context an a custom hook to manage my state data.
App.js (where TodoProvider component warps around its children) -
import logo from './logo.svg';
import './App.css';
import React, {createContext, useContext} from "react"
import TodoItem from './Components/Other/TodoItem';
import TodoList from './Components/List/TodoList';
import TodoAdd from './Components/Forms/TodoAdd';
import CompletedTaskList from './Components/List/CompletedTaskList';
import useTodo from './libs/useTodo';
import {TodoContext, TodoProvider} from "./Contexts/TodoContext"
function App() {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useContext(TodoContext);
return (
<TodoProvider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
<div className="App">
<div className='card' id='mainCard'>
<div className='card-header' id='mainCardHeader'><h4>Todo List</h4></div>
<TodoList/>
<TodoAdd
/>
<CompletedTaskList
/>
</div>
</div>
</TodoProvider>
)
}
export default App;
TodoContext.js (My Context) -
import React, {createContext} from "react";
import useTodo from "../libs/useTodo";
const TodoContext = createContext();
const TodoProvider = ({children}) => {
const {
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
} = useTodo();
return (
<TodoContext.Provider
value={
todoArray, setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem
}
>
{children}
</TodoContext.Provider>
)
}
export {TodoContext, TodoProvider}
useTodo.js (My custom hook to manage state)
import React, {useState} from "react"
const useTodo = () => {
const [todoArray, setTodoArray] = useState([{id: 1,todoTitle: "Code", todoDescription: "Practice React"},{id: 2,todoTitle: "Clean", todoDescription: "Wash dishes, wipe surfaces"}]);
const [completedTaskArray, setCompletedTaskArray] = useState(["Wake up", "Make Bed"]);
const [currentId, setCurrentId] = useState(3);
const addTodoItem = (todoTitleInputItem, todoDescriptionInputItem) => {
let todoTitle = todoTitleInputItem;
let todoDescription = todoDescriptionInputItem;
let id = currentId;
setCurrentId(currentId+1)
setTodoArray(todoArray => [...todoArray, {id,todoTitle, todoDescription}]);
}
const addCompletedItem = ({todoTitle}) => {
setCompletedTaskArray(completedTaskArray => [...completedTaskArray, todoTitle]);
}
return {
todoArray, setTodoArray,
completedTaskArray, setCompletedTaskArray,
addTodoItem,
addCompletedItem
}
}
export default useTodo;
CompletedTasklist(An example of my implementation of using a the context in one of it's children) -
import { useContext } from "react";
import {TodoContext, TodoProvider} from "../../Contexts/TodoContext"
const CompletedTaskList = () => {
const {
completedTaskArray
} = useContext(TodoContext);
return (
<div className="card todo-item">
<div className="card-header">
<h3> Completed Task</h3>
</div>
<div className="card-body">
<ul className="list-group ">
{completedTaskArray.map((item,index) => {
return <li className="list-group-item list-group-item-success" key={index}>{item}</li>
})}
</ul>
</div>
</div>
)
}
export default CompletedTaskList;
I've been trying to resolve this for a while now and cannot wrap my mind around it.

App.js
import React, { createContext, useContext } from 'react';
import CompletedTaskList from './comp';
import { TodoProvider } from './context';
function App() {
// you dont need useTodo, or TodoContext here
return (
<TodoProvider>
{/** todo Provider is a wrapper, you dont need to pass value as prop again, you are already doing it */}
<div className="App">
<div className="card" id="mainCard">
<div className="card-header" id="mainCardHeader">
<h4>Todo List</h4>
</div>
<CompletedTaskList />
</div>
</div>
</TodoProvider>
);
}
export default App;
Context
import React, { createContext } from 'react';
import useTodo from './useTodo';
// Define default values of your context data.
// otherwise everything would be undefined and you need to handle it everywhere
// you are using context
const TodoContext = createContext({
todoArray: [],
setTodoArray: () => {},
completedTaskArray: [],
addCompletedItem: () => {},
addTodoItem: () => {},
});
const TodoProvider = ({ children }) => {
const {
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
} = useTodo();
return (
<TodoContext.Provider
value={{
// <--- you were passing value incorrectly here, it should be an object
// you passed it as (......) instead of {...}
// curly braces not paranthesis
todoArray,
setTodoArray,
completedTaskArray,
addCompletedItem,
addTodoItem,
}}
>
{children}
</TodoContext.Provider>
);
};
export { TodoContext, TodoProvider };
Repasting the answer from the link i shared above.

Related

React testing `useEffect` with `useState` update

On my component render, my useEffects hooks called, a function. the function updates the state status depending on the condition within the useEffects produce.
So in this case how to test the `mobileMenu` and how to set different condition in useEffect to test it?
I hope both my useEffects and useState need to mocked. I am in learning process with react. I could not get any correct answer upon searching, any one help me please?
here is my app.tsx
my ts file:
import { Footer, Header, ProductCart, ProductPhotoGallery, Tabs } from '#mcdayen/components';
import { Cart, Logo, MobileMenu, NaviLinks, QuickSearch, User } from '#mcdayen/micro-components';
import { initialNaviLinksProps, initialPhotoProps, initialTabsProps, NaviLinksProps, sizeProps } from '#mcdayen/prop-types';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCartDetails, sizeHandler } from './store/cart.slice';
import { AppDispatch, RootState } from './store/store.config';
export function App() {
const dispatch:AppDispatch = useDispatch();
dispatch(fetchCartDetails());
const {product} = useSelector((state:RootState) => state.cartStore)
const [mobileMenu, setMobileMenu] = useState<boolean>(false);
const [linkProps, setLinkProps] = useState<NaviLinksProps | null>(null);
function mobileMenuHandler() {
setMobileMenu((current: boolean) => !current);
}
useEffect(() => {
setLinkProps(initialNaviLinksProps);
const mobileId = document.getElementById('mobileMenu');
if (mobileId?.offsetParent) {
mobileMenuHandler();
}
}, []);
useEffect(() => {
setLinkProps((props) => {
return mobileMenu ? { ...initialNaviLinksProps } : { ...initialNaviLinksProps, classProps: props?.classProps + ' hidden' }
})
}, [mobileMenu]);
function onSizeSelect(selectedSize: sizeProps) {
dispatch(sizeHandler(selectedSize));
}
return (
<section className="box-border m-auto flex flex-col pl-[18px] py-6 min-h-screen flex-wrap px-5 md:container md:w-[1440px] md:pl-[70px] pr-5 ">
<Header>
<Logo />
{linkProps && <NaviLinks passNaviLinks={linkProps} />}
<div className="flex gap-3">
<QuickSearch />
<Cart />
<User />
<MobileMenu menuHandler={mobileMenuHandler} />
</div>
</Header>
<main className='flex flex-col justify-between lg:flex-row'>
<div className='hidden lg:block w-[325px]'>
<div>
<Tabs tabProps={initialTabsProps} />
</div>
</div>
<div className='grow-0 flex-auto' >
{initialPhotoProps.length && <ProductPhotoGallery gallery={initialPhotoProps} />}
</div>
<div className='flex bg-white'>
{product && <ProductCart sizeSelect={onSizeSelect} passCartProps={product} />}
</div>
</main>
<Footer />
</section>
);
}
export default App;
My spec:
import { configureStore } from '#reduxjs/toolkit';
import { render } from '#testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './app';
import cartReducer from './store/cart.slice';
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
export function createTestStore() {
const store = configureStore({
reducer: {
cartStore:cartReducer,
}
})
return store;
}
describe('App', () => {
const setMobileMenu = jest.fn();
const useStateMock = (initState: boolean) => [initState, setMobileMenu];
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
afterEach(() => {
jest.clearAllMocks();
});
const store = createTestStore();
it('should render successfully', () => {
const { baseElement } = render(
<BrowserRouter>
<Provider store={store}>{<App />}</Provider>
</BrowserRouter>
);
expect(baseElement).toBeTruthy();
useStateMock(true);
expect(setMobileMenu).toHaveBeenCalledWith(true);
});
});
I am getting an error at: `
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
`
as : Argument of type '(initState: boolean) => (boolean | jest.Mock<any, any>)[]' is not assignable to parameter of type '() => [unknown, Dispatch<unknown>]'.
and my test failing.
Need help for:
test the useEffect hook on anonymous function ( mocking )
fixing the error highlighted
testing the state on setMobileMenu
Any one please help me with the correct way?
Try to declare useStateMock as:
const useStateMock = (initState: any) => [initState, setMobileMenu];

How to store and access data from array [product] from context?

I have a context which contains a fetch() method used to retieve list of products from a server.I made this context so that I could reuse the fetched array values every webpage I might need.But I am unable to do so as it gives me an error in console.
this is the code for context
import React, { createContext, useState, useEffect } from 'react'
export const ProductContext = createContext()
const ProductContextProvider = (props) => {
const [product, setProduct] = useState([]);
const fetchData = () => {
fetch(`http://localhost:8080/product`)
.then((response) => response.json())
.then((actualData) => {
setProduct(actualData)
console.log(product);
})
};
useEffect(() => {
fetchData();
}, [])
return (
<ProductContext.Provider
value={{ product }}>
{props.children}
</ProductContext.Provider>
)
}
export default ProductContextProvider
and this is the error I am getting in console
enter image description here
I have done this too in index.js
enter image description here
and this is one page I want to call the product[]
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import { useContext } from 'react'
import ProductContext from '../context/ProductContext';
function Product() {
const { product } = useContext(ProductContext)
console.log(product);
return (
<div className="products-row ">
{
product.map((data, num) => {
return (
<div className="product" key={num}>
<div className="card">
<a target="_blank" href="/design" >
<img src={data.thumbnail} alt={data.name} style={{ width: "100%" }} ></img>
</a>
<h1>{data.name}</h1>
<p className="price">${data.price}</p>
</div>
</div>
)
})
}
</div>
);
}
export default Product;
I believe it's an import issue. You probably meant to use the following:
import { ProductContext } from '../context/ProductContext';
Right now, your ProductContext is actually ProuductContextProvider, which is the default export as per your code.

React Context API is not initialising more than one states

In my current project I'm using React Context to save component references so that the header component can access them to scroll to them. I managed to successfully make it work the first time with contactRef. But when I tried to add more states to the context, they just would not register.
Console logging the context in Header.js gives me;
contactRef: {current: div.contact}
dispatch: ƒ ()
findings: undefined
locationRef: undefined
[[Prototype]]: Object
I've attached the segments involved with this, but I've narrowed down the issue to be with the INITIAL_STATE in ComponentContext.js. Adding more states does not seem to work, every time only contactRef seems to be initialised.
ComponentContext.js
import { createContext, useReducer } from "react";
const INITIAL_STATE = {
contactRef: null,
locationRef: null,
findings: true,
};
export const ComponentContext = createContext(INITIAL_STATE);
const componentReducer = (state, action) => {
switch (action.type) {
case "contact":
return { contactRef: action.ref };
case "location":
return { locationRef: action.ref };
default:
return state;
}
};
export const ComponentProvider = (props) => {
const [state, dispatch] = useReducer(componentReducer, INITIAL_STATE);
return (
<ComponentContext.Provider
value={{
contactRef: state.contactRef,
locationRef: state.locationRef,
findings: state.findings,
dispatch,
}}
>
{props.children}
</ComponentContext.Provider>
);
};
Contact.js
import React, { useContext, useEffect, useRef } from "react";
import "./index.scss";
import { contactInfo } from "../../data/contactInfo";
import ContactPhoneIcon from "#mui/icons-material/ContactPhone";
import EmailIcon from "#mui/icons-material/Email";
import { ComponentContext } from "../../context/ComponentContext";
const Contact = () => {
const componentContext = useContext(ComponentContext);
const contactRef = useRef();
useEffect(() => {
componentContext.dispatch({ type: "contact", ref: contactRef });
}, []);
return (
<div className="contact" ref={contactRef}>
<div className="contact-accent"></div>
<div className="contact-body">
<div className="contact-left">
<h1 className="contact-title">Hit Me up!</h1>
<div className="contact-info">
<div className="contact-info-item">
<ContactPhoneIcon className="contact-info-icon" />
{contactInfo.phone}
</div>
<div className="contact-info-item">
<EmailIcon className="contact-info-icon" />
{contactInfo.email}
</div>
</div>
</div>
<div className="contact-right">
<p className="contact-description">
<b>I'm great with kids</b> <i>Sejarah</i> has been my passion since
high school and I'd love to show that to your kids; that history is
not just a boring compulsory subject for SPM.
</p>
</div>
</div>
</div>
);
};
export default Contact;
Header.js
import "./index.scss";
import React, { useContext } from "react";
import { ComponentContext } from "../../context/ComponentContext";
const Header = () => {
const componentContext = useContext(ComponentContext);
return (
<div className="header">
<div className="header-logo"></div>
<div className="header-sections">
<div className="header-section-item">Introduction</div>
<div className="header-section-item">About My Classes</div>
<div
className="header-section-item"
onClick={() => {
componentContext.contactRef.current.scrollIntoView({
behavior: "smooth",
});
}}
>
Contact Me
</div>
<div
className="header-section-item"
onClick={() => {
// componentContext.state.locationRef.current.scrollIntoView({
// behavior: "smooth",
// });
console.log(componentContext);
}}
>
Location
</div>
</div>
</div>
);
};
export default Header;

Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes' React -typescript Context

I'm currently coding a React -typescript App for practising FluentUI (a.k.a Fabric). Issue appears
with my App.tsx component.
import React, { useContext, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import Search from './components/Search';
//import CategoriasProvider from './components/Context/CategoriasContext';
import Title from './components/Title';
import { ListGhostingExample } from '../src/components/DrinkList';
import { PrimaryButton } from 'office-ui-fabric-react';
import { CategoriasContext, ICategoriasContextInterface } from './components/Context/CategoriasContext';
import CategoriasProvider from './components/Context/CategoriasContext';
import axios from 'axios';
import './components/DrinkList.css'
import './components/Search.css'
interface IApp{
items:ICategoriasContextInterface[],
renderList:boolean
}
const App =()=> {
const contextValues=useContext(CategoriasContext);
return(
<CategoriasProvider>
<div className="App">
<div className="search">
<Search name={contextValues?.name} image={contextValues?.image} thumbnail={contextValues?.thumbnail} />
</div>
</div>
</CategoriasProvider>
);
}
export default App;
CategoriasProvider comes from a Context (CategoriasContext.tsx ). CategoriasProvider has the mentioned error Inside of CategoriasProvider there's a Search.tsx Component.Search's works as a "wrapper". Code is:
import React, { useEffect, useState } from 'react';
import { SearchBox,ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
import { PrimaryButton, IContextualMenuProps, Stack, IStackTokens, StackItem, initializeIcons } from 'office-ui-fabric-react';
import { ComboBox, DefaultPalette, Dropdown, DropdownMenuItemType, IComboBoxOption, IDropdownOption, IDropdownStyles, IStackItemStyles, SelectableOptionMenuItemType, Toggle } from '#fluentui/react';
import { getGlassesOriginal } from './Utils/Utils';
import axios from 'axios';
import '../Search.css';
import { CategoriasContext, ICategoriasContextInterface } from './Context/CategoriasContext';
initializeIcons();
const Search = (props:ICategoriasContextInterface) => {
//State
const [textContent, setTextContent] = useState("");
const [textBoxDisabled,disableTextBox]=useState(false);
const [comboBoxDisabled,disableComboBox]=useState(true);
const CategoriasContextInSearch=React.useContext(CategoriasContext);
const setTextContentInstate = (e: any) =>{
console.log("Contenido de e" + e.target.value);
setTextContent(e.target.value);
}
const showMessageInConsole = ():void => {
console.log(textContent);
setTextContent("");
}
// Example formatting
const stackTokens: IStackTokens = { childrenGap: 20 };
const searchBoxStyles: Partial<ISearchBoxStyles> = { root: { width: 200 } };
const dropdownStyles: Partial<IDropdownStyles> = {
dropdown: { width: 200 },
};
const options: IDropdownOption[] = [
{ key: 'glasses', text: 'Glasses', itemType: DropdownMenuItemType.Header },
];
function getGlasses () {
let outputArray:string[] = [];
console.log("getGlasses");
axios
.get("https://www.thecocktaildb.com/api/json/v1/1/list.php?g=list")
.then((response)=>{
let responseDataJson=response.data.drinks;
for (let element in responseDataJson) {
options.push({key:responseDataJson[element].strGlass,text:responseDataJson[element].strGlass});
}
}
)
return outputArray;
}
function selectSearch(){
if(textBoxDisabled){
disableTextBox(false);
disableComboBox(true);
} else {
disableTextBox(true);
disableComboBox(false);
};
}
useEffect(() => {
//TODO: No se debería llamar siempre a esta función. Solamente cuando se activa el sistmea de búsqueda (y además, cachearlo)
getGlasses()
});
return(
<div className="wrapper">
<div className="one"> <Toggle onClick={selectSearch}/></div>
<div className="two">
{
<SearchBox
name="searchBox"
className="searchBox"
styles={searchBoxStyles}
placeholder="Cheers!"
onChange={setTextContentInstate}
value={textContent}
disabled={textBoxDisabled}
/>
}
</div>
<div className="three">
<Dropdown
placeholder="Select a glass"
options={options}
styles={dropdownStyles}
disabled={comboBoxDisabled}
/>
</div>
<div className="four">
<div className="primaryButton">
<PrimaryButton text="Search" onClick={showMessageInConsole}/>
</div>
</div>
</div>
);
}
export default Search;
Hope you can help me!!! Thanks in advance!
The code which is causing the error in your title is in your comment. It's this line:
export const CategoriasProvider = () => {
You are defining CategoriasProvider as a component which takes no props. It can only accept IntrinsicAttributes which is basically just the key property.
But when you use CategoriasProvider in App you are calling it with JSX element children. You get an error because you have not said that the CategoriasProvider component can accept a children prop.
Any of the following types will solve your problem:
export const CategoriasProvider: React.FC = ({children}) => {
export const CategoriasProvider = ({children}: {children: React.ReactNode}) => {
export const CategoriasProvider = ({children}: React.PropsWithChildren<{}>) => {
Regardless, you'll want to pass the children down as children of the inner Provider component.
return (
<CategoriasContext.Provider value={hola}>
{children}
</CategoriasContext.Provider>
);
Your App component is not going to work as expected because the useContext hook which accesses the CategoriasContext is located outside of the CategoriasProvider. It will just get the default value for the context -- not the value from the provider.
You need to rearrange your components such that the hook call occurs in a component that is rendered inside of the CategoriasProvider.
Try this:
const Search = () => {
const contextValues = useContext(CategoriasContext);
return (
<div className="search">
<Search
name={contextValues?.name}
image={contextValues?.image}
thumbnail={contextValues?.thumbnail}
/>
</div>
);
};
const App = () => {
return (
<CategoriasProvider>
<div className="App">
<Search />
</div>
</CategoriasProvider>
);
};
export default App;

How to get ref on children using react hook?

I'm trying to get hold of ref on children component but it doesn't seem to be working. The same approach works fine with the React class component but not with hooks.
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref) // this logs null always}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
export function Test(props) {
return <div className="test" {...props}>
{props.children}
</div>
}
Your component is okay. It is probably because the are no children rendered to that component. I reproduced it with using TestContainer in App and put <h2>Ref</h2> as a child of TestContainer:
(removed the comment of course, since it has been hiding the } )
App.js
import React from "react";
import "./styles.css";
import TestContainer from "./TestContainer";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<TestContainer>
<h2>Ref</h2>
</TestContainer>
</div>
);
}
TestContainer.js
import React, { useState, useRef } from "react";
export default function TestContainer(props) {
const ref = useRef(null);
return (
<div className="test-container" onClick={() => console.log(ref)}>
{React.Children.map(props.children, c =>
React.cloneElement(c, {
ref: n => {
console.log(n);
ref.current = n;
},
className: "test-container"
})
)}
</div>
);
}
CodeSndbox:
HERE

Resources