I have a functional component in which I am trying to use context, I get the value during import, but when I want to use it inside click handler, says its not defined
component.js
// I get these values over here
const [contextState, setContextState] = useContext(MyContext);
const clickHandler = (e) => {
debugger; // when I am trying to log any of these values I get error of not defined
}
return (<button onClick={clickHandler}>Click me!</button>);
Moreover I have wrapped my app.js components as below
import { MyContextProvider } from './MyContext';
return (
<Router>
<MyContextProvider>
<div className='App'>
<Component />
</div>
</AuthContextProvider>
</Router>
);
Now my context file, myContext.js
import React, { useState, createContext } from 'react';
const MyContext = createContext([{}, () => {}]);
const MyContextProvider = (props) => {
const [contextState, setContextState] = useState({
userIsLoggedin: false,
fName: '',
lName: '',
userName: ''
});
return (
<myContext.Provider value={[contextState, setContextState]}>
{props.children}
</myContext.Provider>
);
};
export { myContext, MyContextProvider };
It looks like you have some typos in your context file. First, use MyContext everywhere in that file (not myContext), and second, pay attention to the variable names you're specifying when you first call useState and then pass that same value to the context (authState vs contextState):
import React, { useState, createContext } from "react";
const MyContext = createContext([{}, () => {}]);
const MyContextProvider = props => {
const [authState, setAuthState] = useState({
userIsLoggedin: false,
fName: "",
lName: "",
userName: ""
});
return (
<MyContext.Provider value={[authState, setAuthState]}>
{props.children}
</MyContext.Provider>
);
};
export { MyContext as default, MyContextProvider };
Here's a working codesandbox with your setup
So, I have debugged over and over again only to find out that it is not reading in debugger but value is getting printed in console.log() and while using it in code directly.
It appears there is some issue with chrome debugger in the current version.
So, anyone facing this issue please try using the variable or log it if on chrome
update : it doesn't print in debugger, but it works with log. Probably it is expected, but it works
I think, You Need to Bind the event listener or use array func,
return (<button onClick={clickHandler.bind(this)}>Click me!</button>);
// Or,
return (<button onClick={ev => clickHandler(ev)}>Click me!</button>);
Related
I have the following object being exported into another file:
info.js
export const info = {
companyName: '',
title: 'Some title',
year: '',
};
I'm importing this object into my Context.js like so:
InfoContext.js
import React, { useState, createContext } from 'react';
import {info} from './info'
export const InfoContext = createContext();
export const InfoProvider = ({ children }) => {
const [state, setState] = useState({
...info,
});
return (
<InfoContext.Provider value={[state, setState]}>
{children}
</InfoContext.Provider>
);
};
What I want to do is access the object values from my state inside my App.js. - Here is what I have tried but I am not having any success:
App.js
import React from "react";
import { InfoProvider, InfoContext } from "./InfoContext";
export default function App() {
return (
<InfoProvider>
<InfoContext.Consumer>
{state => (
<>
<h1>{state.title}</h1>
</>
)}
</InfoContext.Consumer>
</InfoProvider>
);
}
I'm clearly missing something obvious here. I've tried a few things but I'm not sure what the issue is. I feel it has something to do with my object being accessed from a separate file.
Additionally, here is a sandbox link with the above code. Any help would be greatly appreciated!
Code Sandbox
You are passing your value to Provider as array, but on Consumer you expecting it to be an Object.
You need to pass an Object instead:
<InfoContext.Provider value={{state, setState}}>
Also you are using Consumer wrong. As a callback it takes whole value that you've passed in Provider, not state:
<InfoContext.Consumer>
{(value) => (
<>
<h1>{value.state.title}</h1>
</>
)}
</InfoContext.Consumer>
or using destructured assignment:
<InfoContext.Consumer>
{({state}) => (
<>
<h1>{state.title}</h1>
</>
)}
</InfoContext.Consumer>
then you can use value.setState({...}) for example. etc. But note that this is a bad practice updating state like that.
Code Sandbox
I have the next code, where I import NextButton and GroupButton from TitleHeader,
those components are simple buttons
After that, I declared a simple array ButtonsArray and filled it with those components in the useEffect segment, in adition, I 'bind' the Button function to the button component.
Example :
<NextButton function={ShowSearchBar}/>
Then, my other component TitleHeader receives the array and render the components inside it using a map function
My issue is, if I use the const array ButtonsArray with the components loaded as props in TitleHeader, when press the NextButton in the UI to confirm everything is working something weird happens
The only job of NextButton is execute ShowSearchBar function whose have to switch a const from true to false and vice versa but it doest not work,
If i debug the program, when I press the button, the program enters to the ShowSearchBar function but ALWAYS allowFind is false
Note: if I declare the array directly in the TitleHeader params everything works fine
import React, { useState, useEffect } from "react";
import { TitleHeader, NextButton, GroupButton } from "../Common/TitleHeader";
export const ACATG001 = () => {
const [allowFind, setAllowFind] = useState(false);
const [allowGroup, setAllowGroup] = useState(false);
const [ButtonsArray, setButtonsArray] = useState([]);
useEffect(() => {
setButtonsArray([
<NextButton function={ShowSearchBar} />,
<GroupButton function={ShowGroupBar} />,
]);
}, []);
function ShowSearchBar() {
setAllowFind(!allowFind);
}
return (
<GeneralContainer>
//doesnt work (using a const type array and filled in UseEffect)
<TitleHeader
Title={t("TTER001")}
BarSize="300px"
Embedded={false}
ButtonsArray={ButtonsArray}
/>
//Works declaring the array and the items inline
<TitleHeader
Title={t("TTER001")}
BarSize="300px"
Embedded={false}
ButtonsArray={[
<NextButton function={ShowSearchBar} />,
<GroupButton function={ShowGroupBar} />,
]}
/>
</GeneralContainer>
);
};
Second JS TitleHeader
import React, { Component } from "react";
import { Button } from "primereact/button";
export class TitleHeader extends Component {
constructor() {
super();
}
componentDidMount() {}
render() {
let TitleDesing;
TitleDesing = (
<div className="Buttons-Group">
{this.props.ButtonsArray.map((component, index) => (
<React.Fragment key={index}>{component}</React.Fragment>
))}
</div>
);
return TitleDesing;
}
}
export const NextButton = (props) => {
return (
<Button
id="nextButton"
label="test"
tooltip="Next"
className="p-button-rounded p-button-text"
onClick={props.function}
>
<CgChevronRight size="20PX" color=" #d6f1fa" />{" "}
</Button>
);
};
If the update you do to a state depends only on its current value, always use the function callback version of the dispatcher, this will guarantee you don't use a stale value
function ShowSearchBar() {
setAllowFind((previousAllowFind) => !previousAllowFind)
}
I've been studying react and developing an app, but i got a problem using context. In one component I create the context and provide its value, but when I try to use the current value of context in another component, I have the default value. Code:
Component One:
export const OwnerInformationContext = React.createContext({})
function NameChoose() {
...
const [ownerInformation,setOwnerInformation] = useState({})
function onpressSubmitButton(e : FormEvent) {
e.preventDefault();
...
setOwnerInformation({name:'name',roomId:'id',owner:'true'})
}
return(
<div className="page-container">
<OwnerInformationContext.Provider value={ownerInformation} />
...
<form onSubmit={onpressSubmitButton}>
...
</form>
...
);
}
export default NameChoose;
So when i try to use by:
import { OwnerInformationContext } from '../NameChoose/index'
function ComponentTwo(){
const consumeOwnerContext = useContext(OwnerInformationContext)
useEffect(() => {
console.log(consumeOwnerContext)
}, [])
return <h1>test</h1>
}
I got the default value provide in component one, that's {}.
It looks like your context provider is not actually wrapping any components, as it has a self-closing tag:
<OwnerInformationContext.Provider value={ownerInformation} />
It should be:
<OwnerInformationContext.Provider value={ownerInformation}>
{/* Your child components here will have access to the context */}
</OwnerInformationContext.Provider>
You are using useEffect as ComponentDidMount meaning only at start(mount) the value will be console log.
you should give consumeOwnerContext as a dependency to useEffect like this
useEffect(()=>{
console.log(consumeOwnerContext);
},[consumeOwnerContext]);
And rename consumeOwnerContext to consumeOwnerValue, because you are getting the value out of the context using useContext.
After that when you will click on submit button you should have ComponentTwo console log it.
import React, { useState, useEffect, useContext } from "react";
export const OwnerInformationContext = React.createContext({});
function ComponentTwo() {
const consumeOwnerContext = useContext(OwnerInformationContext);
useEffect(() => {
// You are using consumeOwnerContext inside useEffect, in that case add
// it as dependency if you want to see the updated consumeOwnerContext value
console.log(consumeOwnerContext);
}, [consumeOwnerContext]);
return <div>test</div>;
};
function NameChoose() {
const [ownerInformation, setOwnerInformation] = useState({});
function onpressSubmitButton(e) {
e.preventDefault();
setOwnerInformation({ name: "name",roomId: "id",owner: "true",});
}
return (
// The 'OwnerInformationContext.Provider' has to wrap the component
// that will use its context value. In your case, ComponentTwo
// has to be a child of NameChoose.
<OwnerInformationContext.Provider value={ownerInformation}>
<div className="page-container">
<form onSubmit={onpressSubmitButton}>
<button type="submit">Submit</button>
</form>
</div>
<ComponentTwo />
</OwnerInformationContext.Provider>
);
}
export default NameChoose;
I'm trying to port from class component to react hooks with Context API, and I can't figure out what is the specific reason of getting the error.
First, my Codes:
// contexts/sample.jsx
import React, { createContext, useState, useContext } from 'react'
const SampleCtx = createContext()
const SampleProvider = (props) => {
const [ value, setValue ] = useState('Default Value')
const sampleContext = { value, setValue }
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
)
}
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return (
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
export {
useSample
}
// Sends.jsx
import React, { Component, useState, useEffect } from 'react'
import { useSample } from '../contexts/sample.jsx'
const Sends = (props) => {
const [input, setInput ] = useState('')
const handleChange = (e) => {
setInput(e.target.value)
}
const handleSubmit = (e) => {
e.preventDefault()
props.setValue(input)
}
useEffect(() => {
setInput(props.value)
}, props.value)
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleChange} />
<button type="submit">Submit</button>
</form>
)
}
Error I got:
Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
Explanation for my code:
I used Context API to manage the states, and previously I used class components to make the views. I hope the structure is straightforward that it doesn't need any more details.
I thought it should work as well, the <Sends /> component gets passed into useSample HoC function, and it gets wrapped with <SampleProvider> component of sample.jsx, so that <Sends /> can use the props provided by the SampleCtx context. But the result is failure.
Is it not valid to use the HoC pattern with React hooks? Or is it invalid to hand the mutation function(i.e. setValue made by useState()) to other components through props? Or, is it not valid to put 2 or more function components using hooks in a single file? Please correct me what is the specific reason.
So HOCs and Context are different React concepts. Thus, let's break this into two.
Provider
Main responsibility of the provider is to provide the context values. The context values are consumed via useContext()
const SampleCtx = createContext({});
export const SampleProvider = props => {
const [value, setValue] = useState("Default Value");
const sampleContext = { value, setValue };
useEffect(() => console.log("Context Value: ", value)); // only log when value changes
return (
<SampleCtx.Provider value={sampleContext}>
{props.children}
</SampleCtx.Provider>
);
};
HOC
The consumer. Uses useContext() hook and adds additional props. Returns a new component.
const withSample = WrappedComponent => props => { // curry
const sampleCtx = useContext(SampleCtx);
return (
<WrappedComponent
{...props}
value={sampleCtx.value}
setValue={sampleCtx.setValue}
/>
);
};
Then using the HOC:
export default withSample(Send)
Composing the provider and the consumers (HOC), we have:
import { SampleProvider } from "./provider";
import SampleHOCWithHooks from "./send";
import "./styles.css";
function App() {
return (
<div className="App">
<SampleProvider>
<SampleHOCWithHooks />
</SampleProvider>
</div>
);
}
See Code Sandbox for full code.
Higher order Components are functions that takes a Component and returns another Component, and the returning Components can be class component, a Functional Component with hooks or it can have no statefull logic.
In your example you're returning jsx from useSample.
const useSample = (WrappedComponent) => {
const sampleCtx = useContext(SampleCtx)
return ( // <-- here
<SampleProvider>
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} />
</SampleProvider>
)
}
if you want to make a HOC what you can do is something like this
const withSample = (WrappedComponent) => {
return props => {
const sampleCtx = useContext(SampleCtx)
<WrappedComponent
value={sampleCtx.value}
setValue={sampleCtx.setValue} {...props} />
}
}
On the React 16 Context doc page, they have examples that look similar to this one:
const defaultValue = 'light'
const SomeContext = React.createContext(defaultValue)
const startingValue = 'light'
const App = () => (
<SomeContext.Provider theme={startingValue}>
Content
</SomeContext.Provider>
)
It seems that the defaultValue is useless because if you instead set the startingValue to anything else or don't set it (which is undefined), it overrides it. That's fine, it should do that.
But then what's the point of the defaultValue?
If I want to have a static context that doesn't change, it would be nice to be able to do something like below, and just have the Provider been passed through the defaultValue
const App = () => (
<SomeContext.Provider>
Content
</SomeContext.Provider>
)
When there's no Provider, the defaultValue argument is used for the function createContext. This is helpful for testing components in isolation without wrapping them, or testing it with different values from the Provider.
Code sample:
import { createContext, useContext } from "react";
const Context = createContext( "Default Value" );
function Child() {
const context = useContext(Context);
return <h2>Child1: {context}</h2>;
}
function Child2() {
const context = useContext(Context);
return <h2>Child2: {context}</h2>;
}
function App() {
return (
<>
<Context.Provider value={ "Initial Value" }>
<Child /> {/* Child inside Provider will get "Initial Value" */}
</Context.Provider>
<Child2 /> {/* Child outside Provider will get "Default Value" */}
</>
);
}
Codesandbox Demo
Just sharing my typical setup when using TypeScript, to complete answer from #tiomno above, because I think many googlers that ends up here are actually looking for this:
interface GridItemContextType {
/** Unique id of the item */
i: string;
}
const GridItemContext = React.createContext<GridItemContextType | undefined>(
undefined
);
export const useGridItemContext = () => {
const gridItemContext = useContext(GridItemContext);
if (!gridItemContext)
throw new Error(
'No GridItemContext.Provider found when calling useGridItemContext.'
);
return gridItemContext;
};
The hook provides a safer typing in this scenario. The undefined defaultValue protects you from forgetting to setup the provider.
My two cents:
After reading this instructive article by Kent C. Dodds as usual :), I learnt that the defaultValue is useful when you destructure the value returned by useContext:
Define the context in one corner of the codebase without defaultValue:
const CountStateContext = React.createContext() // <-- define the context in one corner of the codebase without defaultValue
and use it like so in a component:
const { count } = React.useContext(CountStateContext)
JS will obviously say TypeError: Cannot read property 'count' of undefined
But you can simply not do that and avoid the defaultValue altogether.
About tests, my teacher Kent has a good point when he says:
The React docs suggest that providing a default value "can be helpful
in testing components in isolation without wrapping them." While it's
true that it allows you to do this, I disagree that it's better than
wrapping your components with the necessary context. Remember that
every time you do something in your test that you don't do in your
application, you reduce the amount of confidence that test can give
you.
Extra for TypeScript; if you don't want to use a defaultValue, it's easy to please the lint by doing the following:
const MyFancyContext = React.createContext<MyFancyType | undefined>(undefined)
You only need to be sure to add the extra validations later on to be sure you have covered the cases when MyFancyContext === undefined
MyFancyContext ?? 'default'
MyFancyContext?.notThatFancyProperty
etc
You can set the default values using useReducer hook, then the 2nd argument will be the default value:
import React, { createContext, useReducer } from "react";
import { yourReducer } from "./yourReducer";
export const WidgetContext = createContext();
const ContextProvider = (props) => {
const { children , defaultValues } = props;
const [state, dispatch] = useReducer(yourReducer, defaultValues);
return (
<WidgetContext.Provider value={{ state, dispatch }}>
{children}
</WidgetContext.Provider>
);
};
export default ContextProvider;
// implementation
<ContextProvider
defaultValues={{
disabled: false,
icon: undefined,
text: "Hello",
badge: "100k",
styletype: "primary",
dir: "ltr",
}}
>
</ContextProvider>