How Exactly Use and Update the Global Variables in React-Native

Can't modify global variable in react native

This has less to do with React-Native and more to do with how javascript works as a language.

In short, you must export what you wish to use in other files.

Global.js

let counter = 0;

const increment = () => {
counter++;
console.log('counter_updated', counter);
};

const worker = {
increment,
counter
};

module.exports = worker;

We've told the JS engine exactly what we want to import in other files. Assigning it to and object and exporting the object is not necessary but allows for cleaner imports/exports IMO.

Component.js

import { counter, increment } from 'pathTo/global';

We deconstruct the object we exported and are now able to modify/use values in the global.js file.

Food for thought

This is not an ideal solution as it is not two ways. Incrementing the count in the global.js file will not update the value in the component.js.

This is because React needs to have it's state updated to initiate a re-render of a component.

Also, this will not persist through app reloads, which I believe you would want for something such as notifications.

Other solutions

  1. Storing the notification counts in a database.
  2. Using component state to manage the count.
  3. Using a state manager like Redux or MobX

how exactly use and update the global variables in react-native?

I'm trying to understand what do you need, so I have the following question, do you want the interval to continue running after you change the screen?, because the components do not work that way.
You must follow the life cycle of the components (https://reactjs.org/docs/react-component.html), therefore, after you change the screen of the "Record" component, it will be unmount and the interval will destroy.
You may describe a little more what do you want to do with the interval, in case you want to use it in more than one component.

How to update the value of global variable react native?

I try to understand your code. Is this what you meant?

class Dashboard extends React.Component {
state = {
name: '',
bjp: ''
}

componentDidMount() {
firebase.database().ref("candid/BJP").once('value')
.then(snapshot => {
let bjp = snapshot.val().votes;
this.setState({ bjp });
})
}
}

React Native : What is the best practice to use global variables throughout your whole application?

After the user logs in I'm saving the username in the AsyncStorage, so it will be remembered, the next time he/she uses the app.

This is out of the context of the question, but you need to save a token in async storage with which you can fetch the user object, and not store a username in async storage. If you don't have a token next time, then you cannot assume that the saved username is valid and the user is logged in. It's a massive security issue.

Since the username will remain unchanged throughout the whole application

This is not true since user can log out, making the username invalid, and login with a different username.

This works, but it feels like a bad practice to manually pass this variable to every page where I want to show the username. Since the username doesn't change

The username does change, which makes passing it in params worse since the passed username will become outdated if it did change in future. Regardless, data such as user data doesn't belong in params: https://reactnavigation.org/docs/params#what-should-be-in-params

Is there a way to retrieve all the values from AsyncStorage just once, and then store them in some kind of global variable, so that they can be used throughout the whole application?

You need to store them in state after retrieving from async storage. Since you want to use this state in whole application, it's a matter of lifting state up to the root component.

Assuming you have a function component, you can store the state using React's useState hook: https://reactjs.org/docs/hooks-state.html

To be able to use this state in all child components, you can expose the state using React context: https://reactjs.org/docs/context.html

function App() {
const [token, setToken] = React.useState({ checking: true });

React.useEffect(() => {
AsyncStorage.getItem('user_token').then(
(value) => setToken({ checking: false, value }),
(error) => setToken({ error })
);
}, []);

if (token.checking) {
return <LoadingScreen />;
}

return (
<UserTokenContext.Provider value={token.value}>
{/* rest of app code */}
</UserTokenContext.Provider>
);
}

You may also want use a global state management library such as Redux or Mobx State Tree if you need to store more than just a token.

I seem to understand that the variables only would be passed from parent to child

Yes, props are always passed from parent to child. If you need other components to be able to access the state, you lift it up to a parent component.

I made the Homescreen with a functional component which I understand is stateless

Function components are not stateless. They can hold state using the useState hook. Though your data needs to be in the root component and not home screen since you also need this data to be accessed by other screen.

Unable to change global variable in React Native, are they read-only?

This is a simple one, changing the value of any variable that is not part of some sort of state or props does not cause the React tree to re-render.

The variable is being changed but your HomeScreen component never gets notified of that change and this is by design. Can you imagine the performance issues that we would be having if every variable assignment triggered re-renders?

I suggest you look into the Context API, it allows you to expose values to all your components as well as notifying the necessary components when the values change.

React Native: where to place global state variables

You can achieve this using React-Context

You can simply have a state in the app.js and use context to access it anywhere you need in the app.

First you need to create the context. Its better if its a separate file

const AppContext = React.createContext({sport:'value',setSport=()=>{}});

Here the default values are options but preferred specially when you use typescript to avoid warnings.

Now you have to use this in your app.js like below

export default function App() {
const [sport,setSport] = React.useState('value');

const state={sport,setSport};
...

return (
<AppContext.Provider value={state}>
<OfflineNotice />
<Screen />
</AppContext.Provider>
);
}

You will have to import the context and use the provider as the wrapper setting the value from the local state that you have. Now you can access this anywhere in the tree and modify it if required.

// Accessing the context using the useContext hook, this component should be in the tree and you should import AppContext

const {sport,setSport} = useContext(AppContext);

You can show it like below

<Text>{sport}</Text>

Or set it like below

<Button title="Set Value" onPress={()=>{setSport('value')}}>

This example is just on a string but you can even have an object.

Dynamic change of global variables in react native

To continue my comment:
This is a full example of how i'm doing.

I'm using the mqttws31.js library to connect to mosquito. If you need it, just tell me.

I have the app.js that load the redux store and the app in the App.jsx.

App.jsx handle my wsClient functions but you can move them to an other file.

This is the Redux Connect function and mapstateToProps that re-render my component. If props change, the compoenent render again

store.js:

import { createStore } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension';
import { createReducer } from '@reduxjs/toolkit'; // I'm using the toolkit here

const reducer = createReducer(initialState, {
SET_ONE: (state, action) => {
state.items[action.key] = action.value
},
SET_ALL: (state, action) => {
state.items = action.data
}
})

const initialState = {
items: {}
}

const store = createStore(reducer,initialState);

export default store;

App.js:

import store from './store';
import { Provider } from 'react-redux';
import App from './app.jsx';
window.addEventListener('load', (event) => {
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.querySelector('#app-root')
);
});

App.jsx:

import React from 'react';
import store from './store';
import '../vendor/paho-mqtt/mqttws31.js'; // the mqtt lib
const MQTT = window.Paho.MQTT; // the MQTT object

class App extends React.Component {

state = {
activeTab: 0,
items: {}, //from redux
wsClientStatus: 'Not Connected'
}

shouldComponentUpdate = (nextProps, nextState) => {
const { wsClientStatus } = this.state
if (wsClientStatus !== nextState.wsClientStatus) {
return true
}
return false;
}


componentDidMount = () => {
this.wsClientInit();
}


render () {
//console.log('app render')
const { wsClientStatus } = this.state; // for a status bar
const state = this.state
return (
<DimmableLight topics={{DMX:{read:'DMX_0', write:'WEB/DMX_0'}}} bpTopic={"WEB/DI01"} location="WC" publish={this.wsClientPublish} />
)
}

wsClient = new MQTT.Client("YOUR HOST", 9001, "myclientid_" + parseInt(Math.random() * 100, 10));

wsClientSetCallBacks = () => {

const that = this;
this.wsClient.onConnectionLost = function (responseObject) {
console.log("Ws client:: connexion lost...");
that.setState({wsClientStatus: 'Not connected'});
//reconnect
that.wsClientConnect(that.wsClient);
};

this.wsClient.onMessageArrived = function (message) {
//Do something with the push message you received
var data = JSON.parse(message.payloadString);
console.log("Received <- " + message.destinationName + ":: ", data);

//update the store
//only one topic / all topics
if (Object.keys(data).length > 1)
store.dispatch({type:'SET_ALL', data:data})
else
store.dispatch({type:'SET_ONE', key:Object.keys(data)[0], value:data[Object.keys(data)[0]]})
};
}

wsClientInit = () => {
this.wsClientSetCallBacks();
this.wsClientConnect(this.wsClient);
window.wsClientPublish = this.wsClientPublish; // to publish manualy within chrome console
}

wsClientConnect = (wsClient) => {
const _this = this
console.log("Ws client:: tentative de connexion...");
_this.setState({wsClientStatus: 'Tentative de connexion'});
wsClient.connect({
timeout: 15,
useSSL: true,
userName: 'USER_NAME',
password: 'USER_PASSWORD',
//Gets Called if the connection has sucessfully been established
onSuccess: function () {
console.log("Ws client:: Connecté.");
_this.setState({wsClientStatus: 'Connecté'});
wsClient.subscribe('unipi_data/#', {qos: 0});
wsClient.subscribe('WEB/#', {qos: 0});
setTimeout(function() {this.wsClientPublish("getAllData", "unipi_system/data", 1);}, 1000);
},
//Gets Called if the connection could not be established
onFailure: function (message) {
console.log("Ws client:: La Connexion a échoué: " + message.errorMessage);
setTimeout(function() {
_this.wsClientConnect(wsClient);
}, 1000);
}
});
}

wsClientPublish = (payload, topic, qos = 1) => {
//Send your message (also possible to serialize it as JSON or protobuf or just use a string, no limitations)
var message = new MQTT.Message(JSON.stringify(payload));
message.destinationName = topic;
message.qos = qos;
this.wsClient.send(message);
console.log("publish -> ", topic, payload, qos);
}
}

export default App;

And in my case, DimmableLight.jsx:

import React from 'react';
import { connect } from 'react-redux'; //connect child to the redux store
import { withStyles } from '@material-ui/core/styles';

import Card from '@material-ui/core/Card';
import CardHeader from '@material-ui/core/CardHeader';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import Slider from '@material-ui/core/Slider';
import IconButton from '@material-ui/core/IconButton';
import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';

import Bulb from './Bulb' // an SVG


class DimmableLight extends React.Component {
static defaultProps = {
data: {},
topics: {DMX:{read:'DMX_0', write:'WEB/DMX_0'}}, // override from props in App.jsx
bpTopic: "WEB/DI01",
location: 'Chambre'
}

state = {

}


// IMPORTANT: update the component only when selected topics change
shouldComponentUpdate = (nextProps, nextState) => {
const { data } = this.props
let shouldUpdate = false;
data && Object.keys(data).map((key) => {
if (data[key] !== nextProps.data[key])
shouldUpdate = true
})
return shouldUpdate;
}

handleChange = (evt, value) => {
const { publish, topics } = this.props; // publish passed from props in App.jsx
publish(parseInt(value), topics.DMX.write);
}

onBpPressed = (evt) => {
const { publish, bpTopic } = this.props
publish(parseInt(1), bpTopic);
}

onBpReleased = (evt) => {
const { publish, bpTopic } = this.props
publish(parseInt(0), bpTopic);
}


render () {
const { data, topics, location, classes } = this.props
//console.log('render dimmable', location)
return (
<Card className={classes.root}>
<CardHeader title={location}>
</CardHeader>
<CardContent className={classes.cardContent}>
<Bulb luminosity={(data[topics.DMX.read] || 0)/254}/>
</CardContent>
<CardActions className={classes.cardActions}>
<Slider min={0} max={254} value={data[topics.DMX.read] || 0} onChange={this.handleChange} aria-labelledby="continuous-slider" />
<IconButton color="primary" variant="contained" onMouseDown={this.onBpPressed} onMouseUp={this.onBpReleased}>
<RadioButtonCheckedIcon/>
</IconButton>
</CardActions>
</Card>
)
}

}



const styles = theme => ({
root: {
[theme.breakpoints.down('sm')]: {
minWidth: '100%',
maxWidth: '100%',
},
[theme.breakpoints.up('sm')]: {
minWidth: 180,
maxWidth: 180,
},
},
cardContent: {
textAlign: 'center'
},
cardActions: {
margin: '0px 10px 0px 10px',
'& > :first-child': {
margin: '0 auto'
}
}

});


// Connect only wanted topics, set first in defaultProps to be sure to have them in ownProps
const mapStateToProps = (state, ownProps) => {
let data = []
Object.keys(ownProps.topics).map(topicKey => {
data[ownProps.topics[topicKey].read] = state.items[ownProps.topics[topicKey].read] || 0
});
return {
data: data
}
}

export default connect(mapStateToProps)(withStyles(styles, {withTheme: true})(DimmableLight))


Related Topics



Leave a reply



Submit