2

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.

snowape
  • 1,274
  • 10
  • 23
  • *what is the right way to share a global variable across files* <-- There is no way to do that. For that you need to persist the value on the server, usually in a database. While you can share a JavaScript library across files, all the values reset upon each page load that uses it. – Scott Marcus Dec 09 '20 at 18:37
  • But since es modules uses live bindings, as explained in this [answer](https://stackoverflow.com/a/57552682/1026836), and since several files can import the same module, shouldn't variables shared across files be possible as a consequence? – snowape Dec 09 '20 at 18:40

2 Answers2

2

Quite a lot of clarifications are required to explain what happens in your code but i'll try to be succinct.

Having it your way

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.

The React way

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

Daniel
  • 6,194
  • 7
  • 33
  • 59
  • Changing to `myGlobal = { count: 0 }` stops errors from being thrown. But accessing `myGlobal.count` in App.js always shows zero even when pressing button to increase value. It looks like there are now 2 versions of `myGlobal`. – snowape Dec 09 '20 at 18:54
  • As stated in the answer - `App.js` won't re-render because `myGlobal` is not part of the state. You can see that the value is shared if you force a re-render of `App.js`. – Daniel Dec 09 '20 at 19:02
1

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>

Hooks API Reference

glinda93
  • 7,659
  • 5
  • 40
  • 78