I have an mqtt client inside my react native app and it constently receives data from a broker. This data is being used in many different screens/components differently (I have 1 class which extends React.Component per screen). For example I show the data I receive from topic x on screen 1 while I show the data I receive from topic y and also topic x on screen 2.
The way I am storing this data is with redux. So I can reach out the latest data with using e.g. this.props.dataX
. I am not sure if this is the best way to store it. What I want to achieve is to show dynamic lists for this data, e.g. one flatlist for the data received on one topic on first screen and another flatlist for topic2+topic1-screen2.
If I simply send the data to a flatlist, it doesn’t become dynamic. I had to refresh the page to render the new data:
<FlatList style={styles...} data={this.props.dataX} <renderItem={({item}) => <Item item={item}/>} keyExtractor={item => item.name} />
function Item({item}) { return ( <View style={styles...}> <Text>{item.name}</Text> <Image style={styles.image} source={...getDeviceIcon(item.name)}/> </View> ); }
I know that if this data is in the state of the component then it would be dynamic. Therefore eventhough it doesn’t really make sense, I tried to duplicate it in the state with the hope that it would be dynamic. But it didn’t:
constructor(props) { super(props); this.state = { data: this.props.dataX }; }
So how can I achieve this?
Advertisement
Answer
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))