In React component lifecycle methods, `this.context` is an empty Object - reactjs

Why is this.context an empty object, in this React component lifecycle methods?
The context has the correct value in the Consumer for that context. Only the this.context API is failing.
const LoremContext = React.createContext({
lorem: "ipsum",
})
class MenuItem extends React.Component {
componentDidMount() {
console.log(
"In MenuItem.componentDidMount, this.context is:",
this.context)
}
render() {
console.log(
"In MenuItem.render, this.context is:",
this.context)
return ( <LoremContext.Consumer>{
(lorem) => {
console.log("In LoremContext.Consumer, lorem is:", lorem)
return (
<li>
{ `Eat ${this.props.dish} at ${lorem}` }
</li>
)
}
}</LoremContext.Consumer> )
}
}
MenuItem.contextType = LoremContext
class Menu extends React.Component {
render() {
…
}
}
class Application extends React.Component {
render() {
return (
<LoremContext.Provider value={ this.props.world.lorem }>
<section className="app">
<Menu menuItems={ [ … ] } />
<p>Fusce varius id arcu egestas sodales</p>
</section>
</LoremContext.Provider>
)
}
ReactDOM.render(
<Application world={ { lorem: "consecteur" } } />,
document.getElementById('app-container'),
)
This is using React 16.4, so it makes use of the documented context API (introduced in React 16.3).
According to that documented API, the above code should get access to the context (defined in the return value from React.createContext) in two ways:
The LoremContext.Consumer component receives the context value passed by the LoremContext.Provider.
The consumer then provides that context value as an argument to the function within that component. In this case, lorem is the argument that receives the context value.
The this.context property receives (because of the declared MenuItem.contextType class property) the context value, inside the “lifecycle methods”.
Only one of those is working for me.
The LoremContext.Consumer API is getting and passing the context value correctly. The console.log output is:
In LoremContext.Consumer, lorem is: consecteur
The this.context is not getting the correct value, instead it gets an empty Object. The console.log output is:
In MenuItem.render, context is: Object { }
In MenuItem.componentDidMount, context is: Object { }
So the consumer is receiving the correct value, but this.context is not. Why the difference? How can I get the correct value received at this.context?

this.context was introduced in React 16.6 that you see can see here
Before this version, on 16.4, that you are using, accessing context inside React Lifecycles can be achieved:
class Button extends React.Component {
componentDidMount() {
// ThemeContext value is this.props.theme
}
componentDidUpdate(prevProps, prevState) {
// Previous ThemeContext value is prevProps.theme
// New ThemeContext value is this.props.theme
}
render() {
const {theme, children} = this.props;
return (
<button className={theme || 'light'}>
{children}
</button>
);
}
}
export default props => (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
See docs for more information

Try creating the context in a separate file and then importing it. That worked for me. When createContext was called in the same file where the <MyContext.Provider> tag was used, the consumers only saw an empty object. When I moved createContext to a separate file, the consumers saw the expected values. This applies to both methods of consuming - <MyContext.Consumer> and MyClass.contextType/this.context.
I'm afraid I can't explain why this works, but I found the solution in this thread.

Unfortunatelly without this part in the target component it is empty.
static contextType = ThemeContext; // assign the correct context
before created like
const ThemeContext = React.createContext('light');
https://reactjs.org/docs/context.html

Related

Migration from componentWillReceiveProps to getDerivedStateFromProps

I am learning reactjs and I wrote component with the method componentWillReceiveProps (cWRP) but I read that it is deprecated and it must replace with getDerivedStateFromProps (gDSFP) - https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html.
Please note that the following code has the sole purpose of illustrating my problem and questions. It is not a full code.
App.js file :
import React from 'react';
import './App.css';
import Display from './component.js'
class App extends React.Component {
state={resetCounter:false}
resetCounter= () => this.setState( {resetCounter: true} );
render() {
return (
<div className="App">
<header className="App-header">
<Display resetCounter={this.state.resetCounter}></Display>
<div>
<p></p><p></p>
<button onClick={this.resetCounter}>Reset</button>
</div>
</header>
</div>
);
}
componentDidUpdate () {
if (this.state.resetCounter!==false)
this.setState( {resetCounter: false} );
}
}
export default App;
component.js file
import React from 'react'
class Display extends React.Component {
constructor() {
super();
this.state = this.resetState();
this.state.generalCounter=0;
}
/* method to avoid code duplication in constructor and cWRP
could not be used with getDerivedStateFromProps */
resetState = () => ({resettableCounter: 0,});
componentWillReceiveProps(nextProps) {
if (nextProps.resetCounter===true)
this.setState(this.resetState())
}
render() {
return (
<>
<div>
<div>general counter : {this.state.generalCounter}</div>
<div>resettable counter : {this.state.resettableCounter}</div>
</div>
<div>
<button onClick={this.incCounters}>+</button>
<button onClick={this.decCounters}>-</button>
</div>
</>
)
}
incCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter+1,
generalCounter: this.state.generalCounter+1
}
)
decCounters= () => this.setState(
{
resettableCounter: this.state.resettableCounter-1,
generalCounter: this.state.generalCounter-1
}
)
}
export default Display
In the state of the component, there is a resettable part and a non resettable one. A method resetState is used to avoid code duplication in the constructor and in cWRP.
To replace cWRP by gDSFP, I wrote a class method because instance method could NOT be called in gDSFP (this is not usable)
...
constructor() {
super();
this.state = Display.resetState();
this.state.generalCounter=0;
}
static resetState () {
return ({resettableCounter: 0,});
}
static getDerivedStateFromProps(nextProps) {
if (nextProps.resetCounter === true) {
return Display.resetState();
} else {
return null;
}
}
...
With this solution, it is very easy to modify all my components but I am not sure that it is a good mean.
I wonder if I have a misconception and if I should rewrite my components to separate them into Fully controlled components and Fully uncontrolled components with a key ( https://en.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#preferred-solutions).
For example, in this case, do I have to write :
One Fully uncontrolled components for the resettable counter
One Fully controlled one for the non resettable counter
A parent component with the +/- buttons to render them.
I ask this question because in some cases, it will be much work, so I want to be sure before continuing.
You would want to keep the gdsfp version in your post if your component depends on some outside props, which you don't have controll over (such as JSON returned or 3rd party render props component, etc).
It looks like you have a full control over what's passed down to the Display. You can pass down an initial resettableCounter value down to Display.
The advantage is two-folds.
Your Display props shows what the Display does - Making it more descriptivie/readable.
It's easier to maintain, as you don't have to massage the data.
For your particular case, Fully uncontrolled component with a key seems to make more sense, as Display should accept the initial value to show, but is responsible for managing the reseetableCounter.
Unless it's absolutely unavoidable, don't create components which control their siblings (or parents). Instead, lift state to a common ancestor:
const Display = ({
generalCounter,
resettableCounter,
incrementCounters,
decrementCounters,
}) => (
<div>
<div>General Counter: {generalCounter}</div>
<div>Resettable Counter: {resettableCounter}</div>
<button onClick={incrementCounters}>Increment</button>
<button onClick={decrementCounters}>Decrement</button>
</div>
);
class DisplayContainer extends React.Component {
state = {
generalCounter: 0,
resettableCounter: 0,
};
incrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter + 1,
resettableCounter: prevState.resettableCounter + 1,
}));
decrementCounters = () => this.setState(prevState => ({
generalCounter: prevState.generalCounter - 1,
resettableCounter: prevState.resettableCounter - 1,
}));
resetResettableCounter = () => this.setState({
resettableCounter: 0,
});
render() {
return (
<React.Fragment>
<Display
{...this.state}
incrementCounters={this.incrementCounters}
decrementCounters={this.decrementCounters}
/>
<button onClick={this.resetResettableCounter}>
Reset Resettable Counter
</button>
</React.Fragment>
);
}
}
const App = () => (
<div>
<DisplayContainer />
</div>
);
An alternative approach would be something like Redux (which effectively lifts state out of React).

ReactJS: Functional component with props defined as Array but seen as Object

When rendering <MyComponent {...docs} />, I kept the following error:
TypeError: docs.map is not a function
Here is how I am rendering <MyComponent /> from a parent class based component:
import * as React from 'react'
import IDoc from '../types/IDoc'
class Doc extends React.Component
{
public render()
{
const docs : IDoc[] = Defs.getDef();
// Example of map working (not doing anything just for an example)
docs.map(x => x);
return (
<div>
<MyComponent {...docs} />
</div>
)
}
}
For some reason when I pass the docs array to the functional <MyComponent/> component, its not seen as an array. I need to convert it to an Array before using .map() which I would prefer to avoid:
import * as React from 'react'
import IDoc from '../types/IDoc'
// docs is an array isn't?
function MyComponent(docs : IDoc[] )
{
if (Array.isArray(docs) === false)
{
//Its not seen as an array so falls into this
return (
<div>
{ Object.keys(docs).map((index) => {
const doc : IDoc = docs[index];
const name = doc.name;
return (
<div>{name}</div>
)
})
}
</div>
)
}else
{
// what I expected to work but it throws the error
return (
<div>
{ docs.map((doc) => {
return (
<div>{doc.name}</div>
)
})
}
</div>
)
}
}
I thought as I defined docs props as IDocs[] it would have been seen as an array due to the square brackets.
The workaround above works, but obviously I don't want to do this every time I use an array function like map(). I am new to React so would appreciate any pointers. I used create-react-app my-app --scripts-version=react-scripts-ts in case that is helpful.
Currently you're spreading the elements of the docs array into the props of MyComponent by doing this:
<MyComponent {...docs} />
Consider revising your MyComponent function so that docs is accessed via it's own prop. This would mean accessings docs via the props object passed to the MyComponent functional component like so:
/* Add props argument, where docs array is an entry of props */
function MyComponent(props : { docs : IDoc[] }) {
const { docs } = props;
/* You can now use docs as before
if (Array.isArray(docs) === false)
*/
}
This change would require that in your Doc component, the MyComponent component is rendered by passing docs as a prop (rather than spreading the elements of the docs array directly into the props of MyComponent) by doing the following:
return (
<div>
{/* Don't spread docs, but instead pass docs via docs prop */}
<MyComponent docs={docs} />
</div>
)
Hope this helps!

React props is passed, but only render() reads props?

I couldn't find a related situation to mines, however my problem I am having a common error of TypeError: Cannot read property 'props' of undefined.
Weird part is, this error is occurring only for the method I defined above render().
Inside of render() I am able to have access without errors though. React dev tools shows I even have access to props.
Code below:
import { Route } from 'react-router-dom'
import AuthService from '../../utils/authentication/AuthService'
import withAuth from '../../utils/authentication/withAuth'
const Auth = new AuthService()
class HomePage extends Component {
handleLogout() {
Auth.logout()
this.props.history.replace('/login')
}
render() {
console.log(this.props.history)
return (
<div>
<div className="App-header">
<h2>Welcome {this.props.user.userId}</h2>
</div>
<p className="App-intro">
<button type="button" className="form-submit" onClick={this.handleLogout}>Logout</button>
</p>
</div>
)
}
}
export default withAuth(HomePage)
Edit: Apologies. I don't want to cause a confusion either, so I will add that I am also using #babel/plugin-proposal-class-propertiesto avoid this binding.
It's because your method handleLogout has it's own context. In order to pass the this value of the class to your method have to do one of two things:
1) Bind it inside the constructor of the class:
constructor(props) {
super(props)
this.handleLogout = this.handleLogout.bind(this)
}
2) You declare your handleLogout method as an arrow function
handleLogout = () => {
console.log(this.props)
}
this isn't bound in non es6 I believe. So you could either bind it with a constructor, or you may be able to get away with an es6 type function
handleLogout = () => {
Auth.logout()
this.props.history.replace('/login')
}
I can't try this, but you could also do a
constructor(props) {
super(props);
// Don't call this.setState() here!
this.handleLogOut= this.handleLogOut.bind(this);
}
You need to use .bind on your click handler.
<button type="button" className="form-submit" onClick={this.handleLogout.bind(this)}>Logout</button>

How to create a global variable that can be accessed in different component in React?

I am getting username from server and i want to use the same user name from some other component. I know session Storage is one of the way to deal with it but i dont want to use for security reason. How can we create a global object in react?
// most simplistic
window.myAppData = {
userName: 'chad123',
language: 'EN',
// other stuff
};
window.myAppData.userName // 'chad123'
But most apps require something a bit more complex. You could use React context.
https://reactjs.org/docs/context.html
Context provider
// create context provider and consumer
const UserContext = React.createContext();
export default UserContext;
// wrap part of your app (or whole app)
// with Provider that needs access to user
class App extends React.Component {
constructor() {
super();
this.state = {
user: null
};
}
componentDidMount() {
yourUserAPI().then(user => this.setState({ user }));
}
render() {
return (
<UserContext.Provider value={this.state.user}>
<MyComponent />
</UserContext.Provider>
);
}
}
Context consumer
A) Current standard
// use anywhere in your app like this
// PS! must be descendant of Provider
class MyComponent extends React.Component {
render() {
return (
<UserContext.Consumer>
{user => {
// do stuff with 'user'
}}
</UserContext.Consumer>
);
}
}
B) React Hooks (IN ALPHA)
// only works with functional
// components (no classes)
function MyComponent() {
const user = React.useContext(UserContext.Consumer);
// do stuff with 'user'
return 'something';
}
I think to achieve that you need to use "React's context API"
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
For further info do visit the link React context api
You need a global state management like Redux.
Once you have this setup you can map your global state to your local component props and access it like you do any other prop: this.props.globalUsername.
I recommend you learn Redux by following their example program on the official website https://redux.js.org/basics/exampletodolist
Well you can create a global variable in ReactJS but it doesn't make it more "secure" over Session/Local storage.
I think creating a global variable in React project is not the best practice at all because of this simply reason: Should components track down this variable for any change ? If the answer is yes, what you are looking at should be "How to manage global state in React" not "How to create a Global Variable in React".
You can achieve it with Redux. As official documentation says "Redux is a predictable state container" but you can think it as Global State Container for your app.
You can check redux out from that url: https://redux.js.org/
USE CUSTOM HOOKS
It is very simple
globals.js
let _obj = {}
export const setGlobal = (obj) => {
Object.assign(_obj, obj)
}
export const getGlobal = varName => {
if(_obj[varName] !== undefined){
return _obj[varName]
}
else {
return null
}
}
component1.jsx
import React.....
import { setGlobal } from "./globals";
import.....
setGlobal({ title : "welcome" })
class comp.... {
render{
return(){
<i onClick={()=>setGlobal({location: "House"})}>Cmponent1</i>
}
}
}
module exp...
Component2.jsx
import React.....
import { setGlobal, getGlobal } from "./globals";
import.....
setGlobal({ greet : "Hi"})
class comp.... {
render{
return(){
<i>{getGlobal("greet")}, {getGlobal("title")} to our {getGlobal("location")}</i>
}
}
}
module exp...

Is it ok to use a wrapper component to pass props in React?

export function injectProps() {
const injects = {store: new Store()}; // some store
return function (Component) {
return class Proxy extends React.Component {
render() {
return React.createElement(Component, {
...injects,
...this.props,
});
}
};
}
}
Is it ok to use this instead of Redux or Context API with React?
Update: I think I missed to point out my expectation. I'm actually passing some service(http, localStorage) to childrens only when they asks for it. It's not only about the store as services don't have any state. But I also need to pass store through it.
https://pastebin.com/G3PgVxLn
Maybe this tweet by the Dan Abramov (React maintainer) might help.
I understand it was probably not the point of the article. But I see
people reaching for Context or Redux because they don’t realize
components can take any children — and that often removes the need for
deep prop passing. Would be great to highlight!
And Dave Ceddia posted a relavant React documentation link.
Composition vs Inheritance
You can read upon those two.
And here is a demo Nicolas Marcora created to show me how to pass properties to child/children.
You can pass props to children using React.cloneElement(child,...
Working demo on StackBlitz.
export default class WithMouse extends React.Component {
state = { x: 0, y: 0 }
handleMouseMove = event => { ... }
render() {
const { children } = this.props
const childElements = React.Children.map(children, child =>
React.cloneElement(child, {
mouse: this.state,
onMouseMove: this.handleMouseMove
})
)
return <div>
{ childElements }
</div>
}
}
You can use WithMouse class to pass props downward to all children and use it like following.
class App extends Component {
...
render() {
return (
<WithMouse>
<MouseTracker />
</WithMouse>
);
}
}
MouseTracker has access to props passed from WithMouse so you can just use it without directly passing it manually.
You can probably go further and pass all props instead of a few (mouse, onMouseMove)

Resources