React useContext() not reading context values in? - javascript

This is what my code looks like:
Context.js:
const Context = createContext();
export default Context;
ContextProvider.js:
import Context from './Context';
const ContextProvider = () =>
{
....
return(<Context.Provider value={data: 1}>{props.children}</Context.Provider>
}
ParentClass.js:
const ParentClass = () =>
{
...
return(
<div>
...
{boolValue ? (
<ContextProvider>
<ConsumerComponent/>
</ContextProvider>)
</div>)
}
ConsumerComponent.js:
import Context from './Context.js';
const ConsumerComponent = () => {
const contextData = useContext(Context);
...
}
My issue is that ConsumerComponent doesn't seem to be able to access context data; it doesn't render at all when I add the const contextData = useContext(Context); line, and nothing gets logged when I try to print contextData. Where exactly did I go wrong? From my understanding, I followed the necessary steps of creating context + a provider, making sure that the consuming component has a provider as one of its ancestor components, and then accessing the context with useContext().

Considering you want the value prop of the Context Provider to be an object like
{ data : 1 }
you probably forgot the extra curly braces, because the first pair is the JSX syntax to interpret the content as JavaScript instead of a string.
So your value prop on ContextProvider.js file probably should look like this:
<Context.Provider value={{data: 1}}>{props.children}</Context.Provider>

Related

Props are not passing from parent to child components in React Functional component

Hi developers I'm just a beginner in React.js. I tried to print props by passing from parent to child.
This is app.js file
import React from "react";
import Hooks from "./components/ReactHooks1";
import Hooks2 from "./components/ReactHooks2";
const App = () => {
return (
<div>
<h1>
Welcome to React App
</h1>
<Hooks2 title2={"Welcome"}/>
</div>
)
}
export default App
This is child component file
import React from 'react';
const Hooks2 = (props) => {
console.log(props);
}
export default Hooks2;
I just try to print props but it shows an empty object. what am I doing wrong please help me on this
You should return something or null to parent component from child, when you're using it in parent component. This will solve your problem
export const Hooks2 = (props) => {
console.log(props);
return <></>;
}
#Rasith
Not sure why would you want to do this, but if you're trying to pass a child component that would print something to the console. In this case you need to destructure the component's props. Here's an article about it from MDN.
This is how I would do it:
const CustomComponent = ({title}) => {
console.log(title)
}
const App = () => {
return (
<>
<h1>Hello World</h1>
<CustomComponent title={"Welcome"}/>
</>
);
};
For the title to be printed to the console, no need to add a return statement to the child component. Again, not sure why you would do this, but there you go.
Well trying to console.log title certainly would not work because what you are passing is called title2. Also your child component is not returning anything.
First, you have to return anything from your child component( even a fragment )
You can access title2 in the child component with any of these methods:
1- using props object itself
const Hooks2 = (props) => {
console.log(props.title2);
return;
}
2- you can also destructure props in place to access title2 directly
const Hooks2 = ({title2}) => {
console.log(title2);
return ;
}
You have to use destructuring in your ChildComponent, to grab your props directly by name:
const Hooks2 = ({title2}) => {
console.log(title2);
}
You can read a little bit more about it in here: https://www.amitmerchant.com/always-destructure-your-component-props-in-react/

How to get a new instance of an exported object on each import?

I have this file that I'm keeping a INITIAL_VALUE for a form field, that I'm building.
INITIAL_VALUE.js
const INITIAL_VALUE = [];
export default INITIAL_VALUE;
And the problem is that INITIAL_VALUE is an array. A non-primitive, that is handled by reference.
Component1.js
import INITIAL_VALUE from "./INITIAL_VALUE";
import React, { useState } from "react";
function Component1(props) {
const [myState, setMyState] = useState(INITIAL_VALUE);
const [boolean, setBoolean] = useState(false);
function handleClick() {
setMyState(prevState => {
prevState.push(1);
return prevState;
});
setBoolean(prevState => !prevState);
props.forceUpdateApp();
}
return (
<React.Fragment>
<div>This is my state: {JSON.stringify(myState)}</div>
<button onClick={handleClick}>Modify State Comp1</button>
</React.Fragment>
);
}
export default Component1;
Component2.js
The same as Component1, but it's named Component2 and it has its own file.
App.js
function App() {
const [boolean, setBoolean] = useState(false);
function forceUpdateApp() {
setBoolean(prevState => !prevState);
}
return (
<React.Fragment>
<Component1 forceUpdateApp={forceUpdateApp} />
<Component1 forceUpdateApp={forceUpdateApp} />
<Component2 forceUpdateApp={forceUpdateApp} />
</React.Fragment>
);
}
CodeSandbox
PROBLEM
Component1.js and Component2.js both import the INITIAL_VALUE file. And I was under the impression that, each one of these imports would get a brand new instance of the INITIAL_VALUE object. But that is not the case as we can see from the GIF below:
QUESTION
Is there a way to keep an array as a initial value living declared and imported from another file and always get a new reference to it on each import? Is there another pattern I can use to solve this? Or should I stick with only primitive values and make it null instead of [] and intialize it in the consumer file?
Is there a way to keep an array as a initial value living declared and imported from another file and always get a new reference to it on each import?
No, that's not possible. The top-most level code of a module will run once, at most. Here, the top level of INITIAL_VALUE.js defines one array and exports it, so everything that imports it will have a reference to that same array.
Easiest tweak would be to export a function which creates the array instead:
// makeInitialValue.js
export default () => {
const INITIAL_VALUE = [];
// the created array / object can be much more complicated, if you wish
return INITIAL_VALUE;
};
and then
import makeInitialValue from "./makeInitialValue";
function Component1(props) {
const INITIAL_VALUE = makeInitialValue();
const [myState, setMyState] = useState(INITIAL_VALUE);
In the simplified case that you just need an empty array, it would be easier just to define it when you pass it to useState.
All that said, it would be much better to fix your code so that it does not mutate the existing state. Change
setMyState(prevState => {
prevState.push(1);
return prevState;
});
to
setMyState(prevState => {
return [...prevState, 1];
});
That way, even if all components and component instances start out with the same array, it won't cause problems, because the array will never be mutated.

React memo keeps rendering when props have not changed

I have a stateless functional component which has no props and populates content from React context. For reference, my app uses NextJS and is an Isomorphic App. I'm trying to use React.memo() for the first time on this component but it keeps re-rendering on client side page change, despite the props and context not changing. I know this due to my placement of a console log.
A brief example of my component is:
const Footer = React.memo(() => {
const globalSettings = useContext(GlobalSettingsContext);
console.log('Should only see this once');
return (
<div>
{globalSettings.footerTitle}
</div>
);
});
I've even tried passing the second parameter with no luck:
const Footer = React.memo(() => {
...
}, () => true);
Any ideas what's going wrong here?
EDIT:
Usage of the context provider in _app.js looks like this:
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
...
return { globalSettings };
}
render() {
return (
<Container>
<GlobalSettingsProvider settings={this.props.globalSettings}>
...
</GlobalSettingsProvider>
</Container>
);
}
}
The actual GlobalSettingsContext file looks like this:
class GlobalSettingsProvider extends Component {
constructor(props) {
super(props);
const { settings } = this.props;
this.state = { value: settings };
}
render() {
return (
<Provider value={this.state.value}>
{this.props.children}
</Provider>
);
}
}
export default GlobalSettingsContext;
export { GlobalSettingsConsumer, GlobalSettingsProvider };
The problem is coming from useContext. Whenever any value changes in your context, the component will re-render regardless of whether the value you're using has changed.
The solution is to create a HOC (i.e. withMyContext()) like so;
// MyContext.jsx
// exported for when you really want to use useContext();
export const MyContext = React.createContext();
// Provides values to the consumer
export function MyContextProvider(props){
const [state, setState] = React.useState();
const [otherValue, setOtherValue] = React.useState();
return <MyContext.Provider value={{state, setState, otherValue, setOtherValue}} {...props} />
}
// HOC that provides the value to the component passed.
export function withMyContext(Component){
<MyContext.Consumer>{(value) => <Component {...value} />}</MyContext.Consumer>
}
// MyComponent.jsx
const MyComponent = ({state}) => {
// do something with state
}
// compares stringified state to determine whether to render or not. This is
// specific to this component because we only care about when state changes,
// not otherValue
const areEqual = ({state:prev}, {state:next}) =>
JSON.stringify(prev) !== JSON.stringify(next)
// wraps the context and memo and will prevent unnecessary
// re-renders when otherValue changes in MyContext.
export default React.memo(withMyContext(MyComponent), areEqual)
Passing context as props instead of using it within render allows us to isolate the changing values we actually care about using areEqual. There's no way to make this comparison during render within useContext.
I would be a huge advocate for having a selector as a second argument similar to react-redux's new hooks useSelector. This would allow us to do something like
const state = useContext(MyContext, ({state}) => state);
Who's return value would only change when state changes, not the entire context.
But I'm just a dreamer.
This is probably the biggest argument I have right now for using react-redux over hooks for simple apps.

React.createContext point of defaultValue?

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>

Why can't I curry a react component?

I've been getting started with react-redux and finding it a very interesting way to simplify the front end code for an application using many objects that it acquires from a back end service where the objects need to be updated on the front end in approximately real time.
Using a container class largely automates the watching (which updates the objects in the store when they change). Here's an example:
const MethodListContainer = React.createClass({
render(){
return <MethodList {...this.props} />},
componentDidMount(){
this.fetchAndWatch('/list/method')},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(oId){
this.props.fetchObject(oId).then((obj) => {
this._unwatch = this.props.watchObject(oId);
return obj})}});
In trying to supply the rest of the application with as simple and clear separation as possible, I tried to supply an alternative 'connect' which would automatically supply an appropriate container thus:
const connect = (mapStateToProps, watchObjectId) => (component) => {
const ContainerComponent = React.createClass({
render(){
return <component {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
this.props.fetchObject(watchObjectId).then((obj) => {
this._unwatch = this.props.watchObject(watchObjectId);
return obj})}
});
return reduxConnect(mapStateToProps, actions)(ContainerComponent)
};
This is then used thus:
module.exports = connect(mapStateToProps, '/list/method')(MethodList)
However, component does not get rendered. The container is rendered except that the component does not get instantiated or rendered. The component renders (and updates) as expected if I don't pass it as a parameter and reference it directly instead.
No errors or warnings are generated.
What am I doing wrong?
This is my workaround rather than an explanation for the error:
In connect_obj.js:
"use strict";
import React from 'react';
import {connect} from 'react-redux';
import {actions} from 'redux/main';
import {gets} from 'redux/main';
import {isFunction, omit} from 'lodash';
/*
A connected wrapper that expects an oId property for an object it can get in the store.
It fetches the object and places it on the 'obj' property for its children (this prop will start as null
because the fetch is async). It also ensures that the object is watched while the children are mounted.
*/
const mapStateToProps = (state, ownProps) => ({obj: gets.getObject(state, ownProps.oId)});
function connectObj(Wrapped){
const HOC = React.createClass({
render(){
return <Wrapped {...this.props} />
},
componentDidMount(){
this.fetchAndWatch()},
componentWillUnmount(){
if (isFunction(this._unwatch)) this._unwatch()},
fetchAndWatch(){
const {fetchObject, watchObject, oId} = this.props;
fetchObject(oId).then((obj) => {
this._unwatch = watchObject(oId);
return obj})}});
return connect(mapStateToProps, actions)(HOC)}
export default connectObj;
Then I can use it anywhere thus:
"use strict";
import React from 'react';
import connectObj from 'redux/connect_obj';
const Method = connectObj(React.createClass({
render(){
const {obj, oId} = this.props;
return (obj) ? <p>{obj.id}: {obj.name}/{obj.function}</p> : <p>Fetching {oId}</p>}}));
So connectObj achieves my goal of creating a project wide replacement for setting up the connect explicitly along with a container component to watch/unwatch the objects. This saves quite a lot of boiler plate and gives us a single place to maintain the setup and connection of the store to the components whose job is just to present the objects that may change over time (through updates from the service).
I still don't understand why my first attempt does not work and this workaround does not support injecting other state props (as all the actions are available there is no need to worry about the dispatches).
Try using a different variable name for the component parameter.
const connect = (mapStateToProps, watchObjectId) => (MyComponent) => {
const ContainerComponent = React.createClass({
render() {
return <MyComponent {...this.props} obj={this.state.obj} />
}
...
fetchAndWatch() {
fetchObject(watchObjectId).then(obj => {
this._unwatch = watchObject(watchObjectId);
this.setState({obj});
})
}
});
...
}
I think the problem might be because the component is in lower case (<component {...this.props} />). JSX treats lowercase elements as DOM element and capitalized as React element.
Edit:
If you need to access the obj data, you'll have to pass it as props to the component. Updated the code snippet

Categories