-1
I’m working on a project with many components, and most of them need to share the same state, do it through props
works, but is difficult to maintain, if a child component needs any more value, it is necessary to change all parent components up to the data source
Alternatively, there are Contexts, but they are bad, to use more than one Context in the same component, you must add a Comsumer in the method render
, but access to it will only be within this method, in my case I need to access multiple contexts throughout the component, plus there are places I need to access before the render
be called then it is bad to expect to pass from within the method to a component property
A third alternative is the use of Redux, its operation is exactly as accurate, a place that stores all the states and makes available to the components. However, it is a bit complicated, requires a certain mapping of the state to a property, in addition, one of the prerequisites of the project is to be as small as possible, so it is preferable to use as few libraries as possible
In the end, I created a small simple utility that kept a global state within the components always updated:
- A file contains the global state variable and provides three methods to create and change a "substate" (not to keep everything in the same state, so organize better) and a Systener to warn when there is a change of state, this file is used only by the utility, not by the whole project
const globalState = {};
const listeners = [];
function createState(name, initialValue = {}) {
globalState[name] = initialValue;
callListeners();
}
function setState(name, state) {
globalState[name] = { ...globalState[name], ...state };
callListeners();
}
function onChange(listener) {
listeners.push(listener);
}
function callListeners() {
for (const listener of listeners)
listener(globalState);
}
const store = { createState, setState, onChange };
export default store;
- A class that must be used to create the state, by extension, in it is defined a unique name for the substation and its value, which will be set in the global state (
global[name] = value
)
import store from './store';
export default class State {
name;
state = {};
constructor(name) {
this.name = name;
store.createState(name);
}
setState(state) {
this.state = { ...this.state, ...state };
store.setState(this.name, this.state);
}
}
- Finally, an abstract component to be extended in place of
React.Component
, it adds a Listener in the first file, when there is a change in the global state, updates the state of the component
import { Component as ReactComponent } from 'react';
import store from './store';
export default class Component extends ReactComponent {
mounted = false;
state = {
global: { }
};
constructor(props) {
super(props);
store.onChange(state => {
if (this.mounted)
this.setState({ global: state });
else
this.state = { ...this.state, global: state };
});
}
componentDidMount() {
this.mounted = true;
}
}
Its use is simple, to create a state, just create a class that extends State and instantiate. To use, just extend component of Component, the global state will always keep up to date on this.state.global
:
import React from 'react';
import states from './components/states';
// Defini o subestado
class MyState extends states.State {
state = { myValue: Math.random(), change: () => this.change() };
constructor() {
super('myState');
this.setState(this.state);
}
change() {
this.setState({
myValue: Math.random()
});
}
}
export default class App extends states.Component {
constructor(props) {
super(props);
// Adiciona ao estado global
new MyState();
}
render() {
// Utiliza através de this.state.global
return (<>
<pre>{ JSON.stringify(this.state.global) }</pre>
<button onClick={() => this.state.global.myState.change()}>change</button>
</>);
}
}
Until then everything worked, however, at a given moment, I had to observe the changes in the global state, and make some actions when there is a change in a certain property:
componentDidUpdate(prevProps, prevState) {
if (prevState.global.myState.myValue !== this.state.global.myState.myValue)
// ...
}
When there is a change in the overall state, the componentDidUpdate
is called and this.state.global.myState.myValue
is changed by the new value, but prevState.global.myState.myValue
is also changed by the new value instead of keeping the old one. No other changes are made so that the componentDidUpdate
is called for another reason than the change in myState.myValue
, the comparisons JSON.stringify(prevState) === JSON.stringify(this.state)
and JSON.stringify(prevProps) === JSON.stringify(this.props)
return true. So why the variable that should keep the previous state is changed to the new state?
const globalState = {};
const listeners = [];
function createState(name, initialValue = {}) {
globalState[name] = initialValue;
callListeners();
}
function setState(name, state) {
globalState[name] = { ...globalState[name], ...state };
callListeners();
}
function onChange(listener) {
listeners.push(listener);
}
function callListeners() {
for (const listener of listeners)
listener(globalState);
}
const store = { createState, setState, onChange };
class State {
name;
state = {};
constructor(name) {
this.name = name;
store.createState(name);
}
setState(state) {
this.state = { ...this.state, ...state };
store.setState(this.name, this.state);
}
}
class Component extends React.Component {
mounted = false;
state = {
global: store.globalState
};
constructor(props) {
super(props);
store.onChange(state => {
if (this.mounted)
this.setState({ global: state });
else
this.state = { ...this.state, global: state };
});
}
componentDidMount() {
this.mounted = true;
}
}
class MyState extends State {
state = { myValue: Math.random(), change: () => this.change() };
constructor() {
super('myState');
this.setState(this.state);
}
change() {
this.setState({
myValue: Math.random()
});
}
}
class App extends Component {
constructor(props) {
super(props);
new MyState();
}
componentDidUpdate(prevProps, prevState) {
console.log(JSON.stringify(prevProps) === JSON.stringify(this.props))
console.log(JSON.stringify(prevState) === JSON.stringify(this.state))
console.log(prevState.global.myState.myValue, this.state.global.myState.myValue)
}
render() {
return (<div>
<pre>{ JSON.stringify(this.state.global) }</pre>
<button onClick={() => this.state.global.myState.change()}>change</button>
</div>);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
perhaps because it has already updated.
– novic
you who created all the code type a context or Redux ???
– novic
@Virgilionovic also thought about it, but there is no other amendment that can call the
componentDidUpdate
, as it is called can only be because of the changing global state. Yes, that would be just a prototype, to see if it works, but for now it’s not working 100%– Costamilam
so that’s it, I study
react
you who made the code, maybe (I’m just thinking you) did not have the state next to thereact
, may be a change only in the value of the variable and not in the state. that the code is too large have not tested– novic
Mount with code in your question I find it better to do it in my https://answall.com/questions/463410/problema-na-l%C3%b3gica-React-n%C3%a3o-calcula/463417#463417 can be tested
– novic
@Virgilionovic to change the state of the component I use the
setState
React, so I should change it normally. I added an executable, thanks for the tip– Costamilam