Reputation: 1404
I was trying to use a global variable in a React app for debugging purposes, and ran into some issues that made me realize that I don't understand how global variables in javascript works.
global.js:
export let myGlobal = 0
IncButton.js:
import { Component } from 'react'
import { myGlobal } from './global'
class IncButton extends Component {
render() {
return (
<button onClick={() => { myGlobal = myGlobal + 1 }}>increase</button>
)
}
}
export default IncButton
window.inc = () => {
myGlobal = myGlobal + 1
}
App.js:
import { myGlobal } from './global'
import IncButton from './IncButton'
function App() {
return (
<>
{myGlobal}
<IncButton />
</>
);
}
export default App;
Pressing IncButton
throws ReferenceError: assignment to undeclared variable myGlobal
. Calling inc() directly from console throws the same error.
Using let myGlobal = 0
at top of IncButton.js, and removing import removes errors, but of course this makes App.js not see the same myGlobal. My thinking is that imported variables and variables outside any function or {}
should be in the same scope.
So I would like to know: Why is the above not working, what is the right way to share a global variable across files, and do React add any further complications to this?
Note 1: The rest of the code for the example above is just the default from create-react-app.
Note 2: I do know that using global variables is often not a good idea.
Upvotes: 3
Views: 4520
Reputation: 8459
In raw javascript project, you can export a global variable in a file, and import anywhere in other files. That's what you did.
But in React, if you want to use a global state, you should use context API. Your code does not run because myGlobal is not declared as a state. Thus, React cannot track its state.
The correct method is like this:
const {useState, useContext} = React;
// you can create context outside of React component
const CounterContext = React.createContext(0);
// export default CounterContext;
// import CounterContext from 'global/counter-context';
const ChildComponent = () => {
const counter = useContext(CounterContext);
return (
<div className="other-component">
<p>This is Child Component that consumes CounterContext value </p>
<p>Current counter is: {counter}</p>
</div>
);
};
// import CounterContext from 'global/counter-context';
const App = () => {
const [counter, setCounter] = useState(0);
return (
<div className="app">
<CounterContext.Provider value={counter}>
<p>This is Context Provider component. Global state is maintained here and you can consume the state value in any other child components</p>
<label>Click this button to increment global counter state: </label>
<button onClick={() => setCounter((oldCounter) => (oldCounter + 1))}>
Increment
</button>
<ChildComponent />
</CounterContext.Provider>
</div>
);
};
class Root extends React.Component {
render() {
return (<App />);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
.app {
border: 1px solid red;
padding: 5px;
margin: 5px;
}
button {
margin-bottom: 5px;
margin-left: 5px;
}
.other-component {
border: 1px solid blue;
padding: 5px;
margin: 5px;
}
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<div id="root">
</div>
Upvotes: 1
Reputation: 6481
Quite a lot of clarifications are required to explain what happens in your code but i'll try to be succinct.
global.js
Is not really global but a module, and exported members are read-only.
You could make it work by exporting an object:
export let myGlobal = { count: 0 };
And changing your onClick handler to
() => { myGlobal.count++ }
But your components won't re-render, since it's not part of the state, and therefore the change won't be noticed by React.
If you want to share state between components - you should either lift the state up or use the context API as described in this answer
Upvotes: 2