I'm trying to define a global state in a react project using React.useContext & React.useState.
I currently have something like this:
# GlobalState.js
class GlobalState extends AbstractState {
register(current, setCurrent) {
this.current = current
this.setCurrent = setCurrent
}
...
}
const globalState = new GlobalState()
export default globalState
And this is passed from App by something like:
const [current, setCurrent] = React.useState({})
globalState.register_state(current, setCurrent)
const state = {current: current, setCurrent: setCurrent}
return React.createElement(GlobalContext.Provider, {value: state},
...children
)
and it is used at some point as follows:
class WebRequestsTimeChart extends React.Component {
render(){
...
return React.createElement(GlobalContext.Consumer, null, (state) => {
return this.#getChart(state)
})
}
componentDidMount() {
console.log(GlobalState.setCurrent) // actual react dispatch content
}
componentWillUnmount() {
console.log(GlobalState.setCurrent) // actual react dispatch content
}
#getChart(state) {
console.log(GlobalState.setCurrent) // actual react dispatch content
return React.createElement(VictoryChart, {
...
containerComponent: React.createElement(VictoryZoomVoronoiContainer, {
...
events: {
onClick: (event) => {
console.log(state.setCurrent) // actual react dispatch content
console.log(GlobalState.setCurrent) // f(){} <--- ???
}
}
}
}
}
So somehow, lambda passed to onClick cannot access the GlobalState.setCurrent but it has no problem accessing state.setCurrent. If I define another method like GlobalState.someRandomMethod, this is still accessible by onClick. Somehow, setCurrent returned by React.useState seems to be behave differently. I also realized this method cannot be copied (eg. structuredClone doesn't work for that). Another thing I considered is Difference between class object Method declaration React?, but I'm not sure how this would cause GlobalState.setCurrent to behave differently.
adding below also doesn't change this behavior:
constructor(props) {
super(props);
this.getChart = this.getChart.bind(this).bind(GlobalState);
}
Please let me know if you have any idea on what is going on, and how I can use GlobalState.setCurrent inside onClick
GlobalState.setCurrent would be a static method on the class itself, which you haven't declared. It's not the same thing as a setCurrent instance method.
class Foo {
bar () {
return 'bar';
}
}
console.log(Foo.bar?.()); // undefined. "bar" doesn't exist on the class itself.
const foo = new Foo();
console.log(foo.bar()) // 'bar'; exists on instance of Foo.
class Baz {
static boom () {
return "static boom";
}
boom () {
return "instance boom";
}
}
console.log(Baz.boom()); // static boom
const baz = new Baz();
console.log(baz.boom()); // instance boom
Related
I have a custom React component, Foo, which contains another custom component, SubFoo, which in turn contains an inline library component, <Bar/>.From Foo I need to access a function, BarFunc, which is returned by <Bar/>. Unfortunately, <Bar/> is not set up to work with refs - when I try to pass in a ref to <Bar/> (forwarded from Foo to SubFoo using React.forwardRef()), I get undefined.
However, there is a div that is parent to <Bar/> in SubFoo, and if I pass my ref to this div I get back an HTMLDivElement.
Is there any way to execute BarFunc given my HTMLDivElement?
There are multiple ways to accomplish what you are looking to do:
Modify SubFoo to proxy the behavior that Bar provides via its render-prop. Then Foo can take a reference to SubFoo and invoke the behavior indirectly:
class SubFoo extends React.Component {
barBehavior = () => this.barBehaviorFunc?.()
renderProp = barApi => {
this.barBehaviorFunc = barApi.barBehavior;
return <div><other components={here} /></div>;
}
render() {
return <Bar>{this.renderProp}</Bar>;
}
}
class Foo extends React.Component {
onClick() { this.ref?.barBehavior();
render() {
return <SubFoo ref={r => this.ref = r} />
}
}
Use the React.Context API to expose this behavior upwards (if you have unmodifiable code in between Foo and SubFoo).
const BarBehaviorContext = React.createContext(() => {});
class Foo extends React.Component {
setBarBehavior = barBehavior => this.barBehavior = barBehavior;
render() { return <BarBehaviorContext.Provider value={this.setBarBehavior}><SubFoo /></BarBehaviorContext.Provider>;
}
class SubFoo extends React.Component {
render() {
return <BarBehaviorContext.Consumer>{setter => <Bar>{api => (setter(api.barBehavior), <div>...</div>)}</Bar>}</BarBehaviorContext.Consumer>
}
}
Could you please have a look on the following code. I need to get some value from another class. This works asynchronous, so I provided a function handleGameDeserialization.
The function gets the right value (as I tested with the alert), however the setState function has no impact. Could that be a "this-context" issue?
export default class GameplayScreen extends React.Component {
constructor(props) {
super(props);
this.fbGame = new FBGame();
global.currentScreenIndex = 'Gameplay';
this.state = {
currentGame: 'N/A'
}
// this.handleGameDeserialization = this.handleGameDeserialization.bind(this);
if (this.props.route.params != null) {
this.gameKey = this.props.route.params.gameKey;
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
}
handleGameDeserialization = (game) => {
// alert('yeah'+game); // here comes the expected output
this.setState({
currentGame: game
});
}
render() {
return (
<View>
<Text>{this.state.currentGame}</Text>
</View>
/*<Board game={this.state.game}/>*/
)
}
}
I call that function when the component GameplayScreen is navigated to. As you can see above, there is a class FBGame, which does the deserialization (read the game from firebase database)
export default class FBGame {
...
deserializeGame(key, handleGameDeserialization) {
var gameRef = firebase.database().ref("games/"+key).child("serialized");
gameRef.on("value", snapshot => {
//console.log('deserialized: ' + );
handleGameDeserialization(snapshot.val().serialized);
});
}
...
}
edit:
When I use componentDidMount like below, it works fine. But this seems to be an anti-pattern. I still don't understand, why it doesn't work, when callded in the constructor and how I am supposed to solve this.
componentDidMount() {
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
For things like subscriptions that will update the state and other side-effects, you should put the logic out in componentDidMount() which will fire immediately after the component is mounted and won’t give you any trouble if you update the state inside of it.
You can't but things that call this.setState in the constructor.
Whenever setState() is called, the component doesn't seem to rerender. As you can see by my comments, the state does in fact change and render seems to be called again, but if I don't add that if statement and simply add a paragraph tag that displays the data it will give me an error. I'm sure I'm missing something simple, but any help is appreciated.
import React from "react";
import axios from "axios";
import { constants } from "../constants/constants";
const { baseURL, apiKey, userName } = constants;
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: []
};
}
componentDidMount() {
let getUserInfo = axios.create({
baseURL,
url: `?
method=user.getinfo&user=${userName}&api_key=${apiKey}&format=json`
});
getUserInfo().then(response => {
let data = response.data;
console.log(data.user.playcount); //logs second, displays correct
this.setState(state => ({
user: data
}));
});
}
render() {
console.log(this.state); //logs first and third, doesn't work on first but does on third
let toReturn;
if (this.state.user.length > 0) {
toReturn = <p>{this.state.user.user.playcount}</p>;
} else {
toReturn = <p>didn't work</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
React LifeCycle function sequence is Constructor and then it calls render method.
In constructor method it initialises the state which is currently empty user array.
Now it calls render() method as this.state.user is an empty array, referencing something out of it gives an error
this.state.user.user.playcount
this will generate an error if you dont have if condition.
After the first render it will call componentDidMount, now you fetch something update state. As setState occurred, render will be called again Now you have something in this.state.user then displaying will happen.
this.state.user.length > 0 is true
Look at this: https://reactjs.org/docs/react-component.html and https://reactjs.org/docs/conditional-rendering.html
You can right in single tag using conditional render like this
<p>{this.state.user.length ? this.state.user.user.playcount : 'loading'}
Hope this helps.
I think your problem might have something to do with the changing shape of the user value. You initialise the value to an empty array, but then—after the fetch is done—you assume it's an object (by using user.user).
Maybe you could simplify the code a bit to look more like the one below?
/* imports */
class User extends React.Component {
constructor(props) {
super(props);
this.state = {
user: null // Make it explicit there's no value at the beginning.
};
}
componentDidMount() {
let getUserInfo = axios.create(/* ... */);
getUserInfo().then(response => {
let data = response.data;
this.setState({ // No need to for a setter function as you dno't rely on the previous state's value.
user: data.user // Assign the user object as the new value.
});
});
}
render() {
let toReturn;
// Since it's now a `null`, you can use a simple existence check.
if (this.state.user) {
// User is now an object, so you can safely refer to its properties.
toReturn = <p>{this.state.user.playcount}</p>;
} else {
toReturn = <p>No data yet.</p>;
}
return <div>{toReturn}</div>;
}
}
export default User;
I am having tough time understanding use of this keyword in Javascript.
The other questions on stackoverflow I stumbled upon have been more about calling a method or function using this keyword. Like using bind or ES6 arrow function and so on..
So I have this stateful component in React and we are using Axios to intercept request
import React, { Component } from 'react';
import Modal from '../../components/UI/Modal/Modal';
import Aux from '../Aux/Aux';
const withErrorHandler = ( WrappedComponent, axios ) => {
return class extends Component {
state = {
error: null
}
componentWillMount () {
this.reqInterceptor = axios.interceptors.request.use(req => {
this.setState({error: null});
return req;
});
this.resInterceptor = axios.interceptors.response.use(res => res, error => {
this.setState({error: error});
});
}
componentWillUnmount() {
axios.interceptors.request.eject(this.reqInterceptor);
axios.interceptors.response.eject(this.resInterceptor);
}
render () {
return (
<Aux>
<Modal
//Something
</Modal>
<WrappedComponent {...this.props} />
</Aux>
);
}
}
}
export default withErrorHandler;
Something like above code, Here in above code we call interceptors which we want to remove when we want componentWillMount (to avoid memory leaks)
For that the instructor did something like this in componentDidMount followed by
this.reqInterceptor = axios.interceptors.request.use(req => {
this.setState({error: null});
return req;
this in componentWillUnmount
axios.interceptors.request.eject(this.reqInterceptor);
[Question] Can some explain me this.reqInterceptor here? like shouldn't we create a constructor and declare it there and then use it (maybe I am thinking it wrong)?
To answer your question we need a good understanding of structure of React.Component first.
React stateful components are well design to leverage a bit of object-oriented programming (though you may achieve the same pattern in other paradigms.) You have this which refers to the whole component class at your disposal. You can retrieve or assign values to properties or call bounded methods to the component by referring to this within the scope.
In stateful components React executes componentDidMount() when the DOM is ready and mounted then according to your code you assign a value to reqInterceptor property of the component by this.reqInterceptor = value..., this is basically the component that we are returning from our function function withErrorHandler { return class extends Component {...} }.
This is a common pattern to dynamically create components on fly. We can apply same in the following example to demonstrate how this works in the scope of ES6 classes:
class Service {
constructor(x) {
this.x = x;
}
}
function getMyService(extra) {
return class extends Service {
getExtra() {
return extra; // extra value like WrappedComponent or axios
}
getX() {
return this.x;
}
};
}
// result
const MyService = getMyService('some extra value'); // Returns the class
const myServiceInstance = new MyService(1); // This is what React does to instantiate your component
console.log(myServiceInstance.getX()); // -> 1
console.log(myServiceInstance.getExtra()); // -> 'some extra value'
Update:
I updated the above example to be semantically close to React.Component
The constructor will be called with the new keyword, so since the method definition is not in the constructor, you could instantiate multiple objects and you won't register every time a listener.
In this case, he wants to tie the class method to the react lifecycle (componentWillMount and componentWillUnmount).
Let's say I have the following React Component class:
class SayHello extends React.Component {
constructor(props) {
super(props);
this.handleOnClick = this.handleOnClick.bind(this);
}
render() {
return <div onClick={this.handleOnClick}>Click Me</div>;
}
handleOnClick() {
console.log("clicked");
}
}
What I want to do is create a higher order component that knows about the handleOnClick in SayHello but before calling SayHello's handleOnClick, I want it to execute some code I pass in first (i.e. I want to run code that logs something in my server).
Is there a React pattern for doing something like this?
EDIT:
I want to provide some more context here. I want my higher order component to be dynamic in terms of which methods to call. For example, sometimes it might be handleOnClick but other times it might be handleOnSomethingElse.
A higher-order component is a function that takes a component argument and returns a new component.
This function returns a component with a decorated handleClick method:
// A higher-order component that runs some code before
// the given component's `handleClick` method
function wrapHello(componentClass) {
return class wrapped extends componentClass {
beforeHandleClick() {
console.log("I run first!")
}
handleClick(...args) {
this.beforeHandleClick()
super.handleClick(...args)
}
}
}
This pattern is neat because it isn't specific to React at all; it's just a pure function. That means it's easy to test and reason about.
Here's a test harness that doesn't use React:
function wrapHello(componentClass) {
return class wrapped extends componentClass {
beforeHandleClick() {
console.log("I run first!")
}
handleClick(...args) {
this.beforeHandleClick()
super.handleClick(...args)
}
}
}
class SayHello {
handleClick() {
console.log("handleClick")
}
}
const WrappedHello = wrapHello(SayHello)
new WrappedHello().handleClick()
You need something like a dynamic mixin.
This higher-order component takes a Component class and an Object of decorator methods.
The HOC wraps each method that has a matching decorator. These methods call the decorator then call through to the original component method. Non-decorated methods are unchanged.
// Higher-order component
function decorateMethods(componentClass, decorators) {
class decoratedClass extends componentClass { }
Object.keys(decorators).forEach(decoratorName => {
decoratedClass.prototype[decoratorName] = function(...args) {
decorators[decoratorName].call(this, ...args);
return componentClass.prototype[decoratorName].call(this, ...args)
}
})
return decoratedClass
}
//
// Test
//
class Component {
foo() {
console.log("foo")
}
bar() {
console.log("bar")
}
baz() {
console.log("baz")
}
}
const DecoratedComponent = decorateMethods(Component, {
foo() {
console.log("before foo")
},
bar() {
console.log("before bar")
}
})
const d = new DecoratedComponent()
d.foo()
d.bar()
d.baz()
In this case the decorator methods exactly match the base class method names. If you want the decorator to use, e.g. beforeFoo instead, you could map method names with:
const methodName = decoratorName replace(/before(\w)/, (_, a) => a.toLowerCase())