How to Listen for Events in children components in React Native? - reactjs

I want to implement an event listener from parents to children components in my React Native app, I'm using a StackNavigator as a router.
How can I listen for events occurred in the top/parent components?

Simply use React Native's DeviceEventEmitter.Emit your event from parent component like below:
DeviceEventEmitter.emit('eventKey', {name:'John', age:23});
and listen event in children component
componentDidMount(){
//add listener
this.eventListener = DeviceEventEmitter.addListener('eventKey',this.handleEvent);
}
handleEvent=(event)=>{
//Do something with event object
}
componentWillUnmount(){
//remove listener
this.eventListener.remove();
}

The best approach instead of using an EventEmitter with my current experience (7 months later this question) is using Redux as the state holder, you can create the variables that will pass the message through the components parent-children and then connect the component to get the variables (state) changes immediately it happens.
I decided to use Rematch that is a minified version of Redux and I have got very good results. It forced me to remove all my EventEmitters and use the dispatching methods to update variables (states) that will be updated in the connected components.

Simplest way:
1) Create listener at your app.js (Initial starting file):
Example:
(new NativeEventEmitter()).addListener('loggedIn',() => {
setAuth(true);
});
2) Just emit from any child: (new NativeEventEmitter()).emit('loggedIn');
Hope it will be useful to someone!)

I've been researching in how to emit and listen events from a Parent component to a Child component in React Native, after trying different ways I think that it could be good to share how I have got it working.
In your parent component (App.js in my case), import the EventEmitter lib:
import EventEmitter from 'EventEmitter';
Create a variable instance of the Event Emitter:
export default class App extends Component {
...
componentWillMount() {
this.eventEmitter = new EventEmitter();
}
...
}
Then, when something happens emit the event:
this.eventEmitter.emit('Event-Name', {args});
As I'm using a StackNavigator as a router I have to pass my eventEmitter variable to the children views:
const routes = {
ChildView1: {
screen: ChildComponent1
},
ChildView2: {
screen: ChildComponent2
},
...
};
const AppNavigator = StackNavigator(
{
...routes,
},
{
initialRouteName: 'ChildView1',
headerMode: 'none',
/*
* Use modal on iOS because the card mode comes from the right,
* which conflicts with the drawer example gesture
*/
mode: Platform.OS === 'ios' ? 'modal' : 'card',
}
);
/* Here is the magic for the children views */
render(){
return (
<AppNavigator screenProps={this.eventEmitter} />
);
}
Ok, now we have available our event listener for the children views, let's make a call in one of our children components:
export default class ChildComponent1 extends Component {
ComponentWillMount(){
this.eventEmitter = this.props.screenProps;
this.eventEmitter.addListener('Event-Name', ()=>{
// Do something
});
}
...
}
This is how I have done my event listener for child component, I hope it helps someone else. If you have a better implementation please share/comment it.

Having an Event Emitter can probably get a bit messy when tracking where the event came from or where it's being listened.
Just like you pass the event emitter down to the child via screenProps={this.eventEmitter} (which is what you are essentially doing), why not just pass an actual function your child can use to initiate actions on its parent. That'd probably be the most react way to do things.
It probably not is ideal to add an additional event listener as it will affect performance at some point.

I have followed OPs idea of using redux and it worked well for me. Here is the code...
actionTypes.js:
export const TRIGGER_REQUEST_SUBMIT = "TRIGGER_REQUEST_SUBMIT";
export const ACKNOWLEDGE_REQUEST_SUBMIT = "ACKNOWLEDGE_REQUEST_SUBMIT";
eventActions.js:
import * as types from "./actionTypes";
export function triggerRequestSubmit() {
return function(dispatch) {
dispatch({ type: types.TRIGGER_REQUEST_SUBMIT });
};
}
export function acknowledgeRequestSubmit() {
return function(dispatch) {
dispatch({ type: types.ACKNOWLEDGE_REQUEST_SUBMIT });
};
}
eventReducer.js:
import * as types from "../actions/actionTypes";
import initialState from "./initialState";
export default function eventReducer(state = initialState.event, action) {
switch (action.type) {
case types.TRIGGER_REQUEST_SUBMIT:
return { ...state, shouldSubmitRequest: true };
case types.ACKNOWLEDGE_REQUEST_SUBMIT:
return { ...state, shouldSubmitRequest: false };
default:
return state;
}
}
Don't forget to register reducer in your root-reducer/index.js in reducers
initialState.js:
export default {
event: {
shouldSubmitRequest: false
}
};
Usage in child(Use redux to connect & get shouldSubmitRequest via state, acknowledgeRequestSubmit via dispatch):
useEffect(() => {
if (shouldSubmitRequest) {
console.log("shouldSubmitRequest triggered in Request");
acknowledgeRequestSubmit();
}
}, [shouldSubmitRequest]);
In parent call triggerRequestSubmit(); (connect to redux to get that method)

Related

Common DRY principle violation React Native/Redux

I seem to be having a reoccurring issue that I'm hoping there is a design solution out there that I am not aware of.
I'm running into the situation where I need to dispatch the exact same things from two different components. Normally I would set this to a single function and call the function in both of the components. The problem being that if I put this function (that requires props.dispatch) in some other file, that file won't have access to props.dispatch.
ex.
class FeedScreen extends Component {
.
.
.
componentWillReceiveProps(nextProps) {
let {appbase, navigation, auth, dispatch} = this.props
//This is to refresh the app if it has been inactive for more
// than the predefined amount of time
if(nextProps.appbase.refreshState !== appbase.refreshState) {
const navigateAction = NavigationActions.navigate({
routeName: 'Loading',
});
navigation.dispatch(navigateAction);
}
.
.
.
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default connect(mapStateToProps)(FeedScreen)
class AboutScreen extends Component {
componentWillReceiveProps(nextProps) {
const {appbase, navigation} = this.props
//This is to refresh the app if it has been inactive for more
// than the predefined amount of time
if(nextProps.appbase.refreshState !== appbase.refreshState) {
const navigateAction = NavigationActions.navigate({
routeName: 'Loading',
});
navigation.dispatch(navigateAction);
}
}
}
const mapStateToProps = (state) => ({
info: state.info,
auth: state.auth,
appbase: state.appbase
})
export default connect(mapStateToProps)(AboutScreen)
See the similar "const navigateAction" blocks of code? what is the best way to pull that logic out of the component and put it in one centralized place.
p.s. this is just one example of this kind of duplication, there are other situations that similar to this.
I think the most natural way to remove duplication here (with a react pattern) is to use or Higher Order Component, or HOC. A HOC is a function which takes a react component as a parameter and returns a new react component, wrapping the original component with some additional logic.
For your case it would look something like:
const loadingAwareHOC = WrappedComponent => class extends Component {
componentWillReceiveProps() {
// your logic
}
render() {
return <WrappedComponent {...this.props} />;
}
}
const LoadingAwareAboutScreen = loadingAwareHOC(AboutScreen);
Full article explaining in much more detail:
https://medium.com/#bosung90/use-higher-order-component-in-react-native-df44e634e860
Your HOCs will become the connected components in this case, and pass down the props from the redux state into the wrapped component.
btw: componentWillReceiveProps is deprecated. The docs tell you how to remedy.

React, open all expanders when particular redux action is called

Basically what I'm trying to achieve is to open all expanders when someone starts submission. It sounds pretty easy but solution I came about is pretty ugly. When action is dispatched it changes the state in redux to signal component that it has to open expanders, then immediately after that calls another action to reset reducer state. I'm using redux observable so I can also call start and end in my epics just to signal component. However this approach seems quite strange, is there a better way to signal the component to do some action? This is my pseudo code below:
class ExpanderWrapper : React.Component<any,any>
{
state = {expanded:false}
componentWillReceiveProps(newProps)
{
if(newProps.forceExpand) {
this.setState({expanded:true});
this.props.forceExpandEnded();
}
}
render() {
return( <Expander expanded={this.state.expanded}
onExpandChange={()=>this.setState({expanded:false)} />
}
}
connect(ExpandWrapper,... map forceExpand and forceExpandEnded() here)
reducer(state,action)
{
if(action === forceExpandStarted)
return {...state,forceExpand:true}
if(action === forceExpandEnded)
return {...state,forceExpand:false}
}
I think the question here is how to have components observe application-specific events/signals when additional state doesn't seem necessary.
Your solution involves adding the forceExpand signal to the application state, which might be where the ugliness resides. You are forced to hook into the state tree to get that signal, and then reset it for each <Expander> that is currently rendered.
An alternative could be to utilize RxJS without redux-observable. With this solution, you create a Subject that emits whenever a relevant action is pushed to the redux store. Then, you subscribe to and unsubscribe from this Observable in your Component.
A quick and dirty example:
// in some module
export const ExpandSignal = new Rx.Subject();
export const ExpandSignalActionTypes = [ ... ];
// middleware that looks at the redux actions
const signalMiddleware = store => next => action => {
if (ExpandSignalActionTypes.includes(action.type))
ExpandSignal.next();
return next(action);
};
// implement with wrapper, or integrate into existing component
class ExpandWrapper extends React.Component {
state = { expanded: false };
componentDidMount() {
this.subscription = ExpandSignal.subscribe(() => this.setState({ expanded: true });
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
return (
<Expander expanded={this.state.expanded}
onExpandedChanged={() => this.setState({ expanded: false })} />
)
}
}

Rerendering React components after redux state's locale has updated

I've implemented Redux in my React application, and so far this is working great, but I have a little question.
I have an option in my navbar to change the locale, stored in redux's state. When I change it, I expect every component to rerender to change traductions. To do this, I have to specify
locale: state.locale
in the mapStateToProps function... Which leads to a lot of code duplication.
Is there a way to implicitly pass locale into the props of every component connected with react-redux ?
Thanks in advance!
Redux implements a shouldComponentUpdate that prevents a component from updating unless it's props are changed.
In your case you could ignore this check by passing pure=false to connect:
connect(select, undefined, undefined, { pure: false })(NavBar);
For performance reasons this is a good thing and probably isn't what you want.
Instead I would suggest writing a custom connect function that will ensure locale is always added to your component props:
const localeConnect = (select, ...connectArgs) => {
return connect((state, ownProps) => {
return {
...select(state, ownProps),
locale: state.locale
};
}, ...connectArgs);
};
// Simply use `localeConnect` where you would normally use `connect`
const select = (state) => ({ someState: state.someState });
localeConnect(select)(NavBar); // props = { someState, locale }
To cut down the duplication of code I usually just pass an arrow function to the connect method when mapping state to props, looks cleaner to me. Unfortunately though, I don't think there is another way to make it implicit as your component could subscribe to multiple store "objects".
export default connect((state) => ({
local: state.locale
}))(component);
To solve this problem, you can set the Context of your parent component, and use it in your child components. This is what Redux uses to supply the store's state and dispatch function to connected React components.
In your Parent component, implement getChildContext and specify each variable's PropType.
class Parent extends React.Component {
getChildContext() {
return {
test: 'foo'
};
}
render() {
return (
<div>
<Child />
<Child />
</div>
);
}
}
Parent.childContextTypes = {
test: React.PropTypes.string
};
In your Child component, use this.context.test and specify its PropType.
class Child extends React.Component {
render() {
return (
<div>
<span>Child - Context: {this.context.test}</span>
</div>
);
}
}
Child.contextTypes = {
test: React.PropTypes.string
};
Here's a demo of it working.
I might as well mention that, while libraries like Redux use this, React's documentation states that this is an advanced and experimental feature, and may be changed/removed in future releases. I personally would not recommend this approach instead of simply passing the information you need in mapStateToProps, like you originally mentioned.

Actions (playing sound) based on Redux store changes

I'm building an app that contains a store with an "offers" section of the state tree (ImmutableJS List Object). I need to take some action (play a browser sound) whenever an item is added to this list. Items can be added to this list via several different types of Redux actions.
I am trying to figure out the best way to react to the changes to a particular part of the store. I could do it in each action/reducer method, but then I would have it all over the place. I'd rather have one central place to handle the logic.
What's the best way to handle this? Should I create a generic store subscriber and has it's own logic for keeping track of the list values?
In this case your best bet is a store listener. Either a plain listener function or a redux connected React component.
Assuming a simple function to make noise:
function playSound () {
const audio = new Audio('audio_file.mp3')
audio.play()
}
You can create a store observer and listen for changes:
function createSoundObserver (store) {
let prevState = store.getState()
return store.subscribe(() => {
const nextState = store.getState()
if (prevState.messages.length < nextState.messages.length) {
playSound()
}
prevState = nextState
})
}
You can achieve the same with a React component:
import React, {Component, PropTypes} from 'react'
import {connect} from 'react-redux'
class Notifier extends Component {
static propTypes = {
messages: PropTypes.array.isRequired
}
componentDidUpdate (prevProps) {
if (this.props.messages.length > prevProps.messages.length) {
playSound()
}
}
render () { return null }
}
export default connect((state, props) => {
const {messages} = state
return {messages}
}, {})(Notifier)
As long as a Notifier is present amongst the rendered tree, it will check for changes and play the sound accordingly. The advantage of this approach is that you don't have to take extra care of unsubscribing the event if you want to stay quiet, and it seamlessly works server-side rendering.

ReactJS - What Alternatives are there to ContextType?

In keeping with the one purpose only principle of components, I have a deeply layered component, that many depths below has a open modal button.
When first completing the entire page, I foolishly realized that having the function to open the modal at a very deep level child, caused the modal window to open within that child's parameters and not the window as a whole.
When trying to overcome this, I found myself, too liberally passing down prop functions to sub components that had no need for that prop save to be passed down even further. I can see how this would be a nightmare down the road.
As a last resort I opted for context type:
Main Page Component:
export default class MainPage extends Component {
......
getChildContext() {
return {
openModal: (arg) => this.openModal(arg)
}
}
openModal(content) {
if (content !== undefined) {
const open = content.modalOpen
this.setState({modalOpen: open});
}
}
render() {
.....
return(
{ modalOpen ?
<Modal>
....
</Modal>
: '' }
)
}
}
Many generations down child component:
export default Child extends Component {
....
static contextTypes = {
openModal: PropTypes.func
};
render() {
.....
<img src="/images/shared/zoom_in.png"
onClick={ this.context.openModal.bind(this, { modalOpen: true }) }
....
/>
.....
}
...
}
One reads often where contextType is discouraged and may be done away with in the future, but in this scenario, I cannot see a cleaner, more efficient approach.
Under the scenario I discussed, am I following a prudent approach or might there be a better way?
Thanks!
I would have my child view dispatch an event that can be handled on in the root component.
Webpack and Browserify both have convenient ways to use a nodejs like event emitter in the browser. This is also what most flux implementations use for their dispatcher.
You could export a singleton instance of an event emitter that you require in both child and parent. ie..
import {EventEmitter} from 'events'
exports default new EventEmitter()
then from your child component you can
import emitter from './youemitter'
....
openModal() {
emitter.dispatchEvent(OPEN_MODAL, props)
}
and in your parent you can just
import emitter from './youremitter'
....
componentDidMount() {
emitter.addEventListener(OPEN_MODAL, this.openYourModal)
In the same way you can listen to events from your child views to update your data model or flux store or what you may have and rerender.

Resources