Based on the documentation in here to subscribe and unsubscribe Netinfo listener - https://github.com/react-native-netinfo/react-native-netinfo
I define the listener method in a common file and export it
/* util.js */
function listener(){
Netinfo.addEventListener(state => /* something in here */);
}
export { listener };
Then in a React component
import { useEffect } from 'react';
import { listener } from 'util';
const exampleComponent = props => {
// then on useeffect
useEffect(() => {
listener() // to subscribe
return () => listener(); // to unsubscribe
}, []);
return <ExampleComponent />
}
Is this the correct way to setup Netinfo listener if i want to make listener as an exportable method that can be setup anywhere (altho by logic it should only be set up on the main file in the app, but just for the sake of question)?
Im not sure how calling listener the first time will subscribe Netinfo listener and calling it again will unsubscribe it.
You can try this,
import NetInfo from "#react-native-community/netinfo";
const [isConnected, setIsConnected] = useState(true);
NetInfo.fetch().then(state => {
if (state.isConnected && state.isInternetReachable) {
setIsConnected(true);
} else {
setIsConnected(false);
}
});
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
if (state.isConnected && state.isInternetReachable) {
setIsConnected(true);
} else {
setIsConnected(false);
}
});
if (isConnected) {
} else {
unsubscribe();
}
}, []);
Is this the correct way to setup Netinfo listener if i want to make listener as an exportable method that can be setup anywhere (altho by logic it should only be set up on the main file in the app, but just for the sake of question)?
if you want to setup once and use it many times, no need to do that by yourself, you can use useNetInfo()
import {useNetInfo} from "#react-native-community/netinfo";
const YourComponent = () => {
const netInfo = useNetInfo();
return (
<View>
<Text>Type: {netInfo.type}</Text>
<Text>Is Connected? {netInfo.isConnected.toString()}</Text>
</View>
);
};
Related
I have the following useEffect hook that adds an event listener if isAuthenticated is true:
useEffect(() => {
if (isAuthenticated) {
Doorman.guard(onSessionExpiration);
}
}, [isAuthenticated, onSessionExpiration]);
The problem is that when the user logs out, the Doorman.guard(onSessionExpiration) is not unregistered which causes some problems, which is why I'm trying to figure out how to unsubscribe.
The guard function looks like this:
import { DeviceEventEmitter } from 'react-native';
export const Doorman = {
guard(fn: () => void) {
return DeviceEventEmitter.addListener('UNAUTHORIZED', fn);
}
};
My folder structure:
|--App
|--Components
|--PageA.js
|--PageB.js
|--PageC.js
|--common-effects
|--useFetching.js
I am refactoring my code to fetch data from API, using react hooks.
I want to dispatch an action from useEffect in useFetching.js that is intercepted by saga middleware. The action should be dispatched only when the components(PageA, PageB, PageC) mount.
I am using redux, react-redux and redux-saga.
PageA.js:
function(props) {
useFetching(actionParams)
//....//
}
Similar code for PageB and PageC components.
I have abstracted the reusable code to fetch data in useFetching Custom hook.
useFetching.js
const useFetching = actionArgs => {
useEffect( () => {
store.dispatch(action(actionArgs)); // does not work
})
}
I don't know how to access redux dispatch in useFetching. I tried it with useReducer effect, but the sagas missed the action.
Version using react-redux hooks:
You can even cut out the connect function completely by using useDispatch from react-redux:
export default function MyComponent() {
useFetching(fetchSomething);
return <div>Doing some fetching!</div>
}
with your custom hook
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
Edit: removed dispatch from custom hook as suggested by #yonga-springfield
Note: React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.
You would need to pass either bound action creators or a reference to dispatch to your hook. These would come from a connected component, same as you would normally use React-Redux:
function MyComponent(props) {
useFetching(props.fetchSomething);
return <div>Doing some fetching!</div>
}
const mapDispatch = {
fetchSomething
};
export default connect(null, mapDispatch)(MyComponent);
The hook should then call the bound action creator in the effect, which will dispatch the action accordingly.
Also, note that your current hook will re-run the effect every time the component is re-rendered, rather than just the first time. You'd need to modify the hook like this:
const useFetching = someFetchActionCreator => {
useEffect( () => {
someFetchActionCreator();
}, [])
}
This is just to bring some optimization to #Alex Hans' answer.
As per the documentation here. A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
With this in mind, we need not send a reference to the dispatch function to the useFetching hook as a parameter but rather, simply not send it and rather simply use it from within the useFetching hook with the appropriate imports.
Here's an excerpt of what I mean.
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
I can't ascertain this example will fit without errors in your codebase in your case but just trying to explain the idea/concept behind this post.
Hope this helps any future comer.
Alex Hans right decision with dispatch, but to eliminate request loops to api you can specify the dependence on dispatch ( I used Redux Toolkit )
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import axios from 'axios'
import { getItemsStart, getItemsSuccess, getItemsFailure } from '../features/itemsSlice'
const fetchItems = () => async dispatch => {
try {
dispatch(getItemsStart());
const { data } = await axios.get('url/api')
dispatch(getItemsSuccess(data))
} catch (error) {
dispatch(getItemsFailure(error))
}
}
const PageA = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.dataSlice)
useEffect(() => {
dispatch(fetchItems())
}, [dispatch])
return (
<ul>
{items.map(item => <li>{item.name}</li>}
</ul>
)
}
export default PageA
it is important to passed dependency parameter of dispatch in the useEffect(() => {...}, [dispatch])
useEffect(() => {
fetchData();
}, []);
async function fetchData() {
try {
await Auth.currentSession();
userHasAuthenticated(true);
} catch (e) {
if (e !== "No current user") {
alert(e);
}
}
dispatch(authentication({ type: "SET_AUTHING", payload: false }));
}
My folder structure:
|--App
|--Components
|--PageA.js
|--PageB.js
|--PageC.js
|--common-effects
|--useFetching.js
I am refactoring my code to fetch data from API, using react hooks.
I want to dispatch an action from useEffect in useFetching.js that is intercepted by saga middleware. The action should be dispatched only when the components(PageA, PageB, PageC) mount.
I am using redux, react-redux and redux-saga.
PageA.js:
function(props) {
useFetching(actionParams)
//....//
}
Similar code for PageB and PageC components.
I have abstracted the reusable code to fetch data in useFetching Custom hook.
useFetching.js
const useFetching = actionArgs => {
useEffect( () => {
store.dispatch(action(actionArgs)); // does not work
})
}
I don't know how to access redux dispatch in useFetching. I tried it with useReducer effect, but the sagas missed the action.
Version using react-redux hooks:
You can even cut out the connect function completely by using useDispatch from react-redux:
export default function MyComponent() {
useFetching(fetchSomething);
return <div>Doing some fetching!</div>
}
with your custom hook
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
Edit: removed dispatch from custom hook as suggested by #yonga-springfield
Note: React guarantees that dispatch function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.
You would need to pass either bound action creators or a reference to dispatch to your hook. These would come from a connected component, same as you would normally use React-Redux:
function MyComponent(props) {
useFetching(props.fetchSomething);
return <div>Doing some fetching!</div>
}
const mapDispatch = {
fetchSomething
};
export default connect(null, mapDispatch)(MyComponent);
The hook should then call the bound action creator in the effect, which will dispatch the action accordingly.
Also, note that your current hook will re-run the effect every time the component is re-rendered, rather than just the first time. You'd need to modify the hook like this:
const useFetching = someFetchActionCreator => {
useEffect( () => {
someFetchActionCreator();
}, [])
}
This is just to bring some optimization to #Alex Hans' answer.
As per the documentation here. A custom Hook is a JavaScript function whose name starts with ”use” and that may call other Hooks.
With this in mind, we need not send a reference to the dispatch function to the useFetching hook as a parameter but rather, simply not send it and rather simply use it from within the useFetching hook with the appropriate imports.
Here's an excerpt of what I mean.
import { useDispatch } from 'react-redux';
const useFetching = (someFetchActionCreator) => {
const dispatch = useDispatch()
useEffect(() => {
dispatch(someFetchActionCreator());
}, [])
}
I can't ascertain this example will fit without errors in your codebase in your case but just trying to explain the idea/concept behind this post.
Hope this helps any future comer.
Alex Hans right decision with dispatch, but to eliminate request loops to api you can specify the dependence on dispatch ( I used Redux Toolkit )
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import axios from 'axios'
import { getItemsStart, getItemsSuccess, getItemsFailure } from '../features/itemsSlice'
const fetchItems = () => async dispatch => {
try {
dispatch(getItemsStart());
const { data } = await axios.get('url/api')
dispatch(getItemsSuccess(data))
} catch (error) {
dispatch(getItemsFailure(error))
}
}
const PageA = () => {
const dispatch = useDispatch()
const { items } = useSelector(state => state.dataSlice)
useEffect(() => {
dispatch(fetchItems())
}, [dispatch])
return (
<ul>
{items.map(item => <li>{item.name}</li>}
</ul>
)
}
export default PageA
it is important to passed dependency parameter of dispatch in the useEffect(() => {...}, [dispatch])
useEffect(() => {
fetchData();
}, []);
async function fetchData() {
try {
await Auth.currentSession();
userHasAuthenticated(true);
} catch (e) {
if (e !== "No current user") {
alert(e);
}
}
dispatch(authentication({ type: "SET_AUTHING", payload: false }));
}
Instead of writing my components inside a class, I'd like to use the function syntax.
How do I override componentDidMount, componentWillMount inside function components?
Is it even possible?
const grid = (props) => {
console.log(props);
let {skuRules} = props;
const componentDidMount = () => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
};
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
Edit: With the introduction of Hooks it is possible to implement a lifecycle kind of behavior as well as the state in the functional Components. Currently
Hooks are a new feature proposal that lets you use state and other
React features without writing a class. They are released in React as a part of v16.8.0
useEffect hook can be used to replicate lifecycle behavior, and useState can be used to store state in a function component.
Basic syntax:
useEffect(callbackFunction, [dependentProps]) => cleanupFunction
You can implement your use case in hooks like
const grid = (props) => {
console.log(props);
let {skuRules} = props;
useEffect(() => {
if(!props.fetched) {
props.fetchRules();
}
console.log('mount it!');
}, []); // passing an empty array as second argument triggers the callback in useEffect only after the initial render thus replicating `componentDidMount` lifecycle behaviour
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
useEffect can also return a function that will be run when the component is unmounted. This can be used to unsubscribe to listeners, replicating the behavior of componentWillUnmount:
Eg: componentWillUnmount
useEffect(() => {
window.addEventListener('unhandledRejection', handler);
return () => {
window.removeEventListener('unhandledRejection', handler);
}
}, [])
To make useEffect conditional on specific events, you may provide it with an array of values to check for changes:
Eg: componentDidUpdate
componentDidUpdate(prevProps, prevState) {
const { counter } = this.props;
if (this.props.counter !== prevState.counter) {
// some action here
}
}
Hooks Equivalent
useEffect(() => {
// action here
}, [props.counter]); // checks for changes in the values in this array
If you include this array, make sure to include all values from the component scope that change over time (props, state), or you may end up referencing values from previous renders.
There are some subtleties to using useEffect; check out the API Here.
Before v16.7.0
The property of function components is that they don't have access to Reacts lifecycle functions or the this keyword. You need to extend the React.Component class if you want to use the lifecycle function.
class Grid extends React.Component {
constructor(props) {
super(props)
}
componentDidMount () {
if(!this.props.fetched) {
this.props.fetchRules();
}
console.log('mount it!');
}
render() {
return(
<Content title="Promotions" breadcrumbs={breadcrumbs} fetched={skuRules.fetched}>
<Box title="Sku Promotion">
<ActionButtons buttons={actionButtons} />
<SkuRuleGrid
data={skuRules.payload}
fetch={props.fetchSkuRules}
/>
</Box>
</Content>
)
}
}
Function components are useful when you only want to render your Component without the need of extra logic.
You can use react-pure-lifecycle to add lifecycle functions to functional components.
Example:
import React, { Component } from 'react';
import lifecycle from 'react-pure-lifecycle';
const methods = {
componentDidMount(props) {
console.log('I mounted! Here are my props: ', props);
}
};
const Channels = props => (
<h1>Hello</h1>
)
export default lifecycle(methods)(Channels);
You can make your own "lifecycle methods" using hooks for maximum nostalgia.
Utility functions:
import { useEffect, useRef } from "react";
export const useComponentDidMount = handler => {
return useEffect(() => handler(), []);
};
export const useComponentDidUpdate = (handler, deps) => {
const isInitialMount = useRef(true);
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
return;
}
return handler();
}, deps);
};
export const useComponentWillUnmount = handler => {
return useEffect(() => handler, []);
};
Usage:
import {
useComponentDidMount,
useComponentDidUpdate,
useComponentWillUnmount
} from "./utils";
export const MyComponent = ({ myProp }) => {
useComponentDidMount(() => {
console.log("Component did mount!");
});
useComponentDidUpdate(() => {
console.log("Component did update!");
});
useComponentDidUpdate(() => {
console.log("myProp did update!");
}, [myProp]);
useComponentWillUnmount(() => {
console.log("Component will unmount!");
});
return <div>Hello world</div>;
};
Solution One:
You can use new react HOOKS API. Currently in React v16.8.0
Hooks let you use more of React’s features without classes.
Hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and lifecycle.
Hooks solves all the problems addressed with Recompose.
A Note from the Author of recompose (acdlite, Oct 25 2018):
Hi! I created Recompose about three years ago. About a year after
that, I joined the React team. Today, we announced a proposal for
Hooks. Hooks solves all the problems I attempted to address with
Recompose three years ago, and more on top of that. I will be
discontinuing active maintenance of this package (excluding perhaps
bugfixes or patches for compatibility with future React releases), and
recommending that people use Hooks instead. Your existing code with
Recompose will still work, just don't expect any new features.
Solution Two:
If you are using react version that does not support hooks, no worries, use recompose(A React utility belt for function components and higher-order components.) instead. You can use recompose for attaching lifecycle hooks, state, handlers etc to a function component.
Here’s a render-less component that attaches lifecycle methods via the lifecycle HOC (from recompose).
// taken from https://gist.github.com/tsnieman/056af4bb9e87748c514d#file-auth-js-L33
function RenderlessComponent() {
return null;
}
export default lifecycle({
componentDidMount() {
const { checkIfAuthed } = this.props;
// Do they have an active session? ("Remember me")
checkIfAuthed();
},
componentWillReceiveProps(nextProps) {
const {
loadUser,
} = this.props;
// Various 'indicators'..
const becameAuthed = (!(this.props.auth) && nextProps.auth);
const isCurrentUser = (this.props.currentUser !== null);
if (becameAuthed) {
loadUser(nextProps.auth.uid);
}
const shouldSetCurrentUser = (!isCurrentUser && nextProps.auth);
if (shouldSetCurrentUser) {
const currentUser = nextProps.users[nextProps.auth.uid];
if (currentUser) {
this.props.setCurrentUser({
'id': nextProps.auth.uid,
...currentUser,
});
}
}
}
})(RenderlessComponent);
componentDidMount
useEffect(()=>{
// code here
})
componentWillMount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
According to the documentation:
import React, { useState, useEffect } from 'react'
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
});
see React documentation
Short and sweet answer
componentDidMount
useEffect(()=>{
// code here
})
componentWillUnmount
useEffect(()=>{
return ()=>{
//code here
}
})
componentDidUpdate
useEffect(()=>{
//code here
// when userName state change it will call
},[userName])
You can make use of create-react-class module.
Official documentation
Of course you must first install it
npm install create-react-class
Here is a working example
import React from "react";
import ReactDOM from "react-dom"
let createReactClass = require('create-react-class')
let Clock = createReactClass({
getInitialState:function(){
return {date:new Date()}
},
render:function(){
return (
<h1>{this.state.date.toLocaleTimeString()}</h1>
)
},
componentDidMount:function(){
this.timerId = setInterval(()=>this.setState({date:new Date()}),1000)
},
componentWillUnmount:function(){
clearInterval(this.timerId)
}
})
ReactDOM.render(
<Clock/>,
document.getElementById('root')
)
if you using react 16.8 you can use react Hooks...
React Hooks are functions that let you “hook into” React state and lifecycle features from function components...
docs
import React, { useState, useEffect } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
const [count2, setCount2] = useState(0);
// componentDidMount
useEffect(() => {
console.log("The use effect ran");
}, []);
// // componentDidUpdate
useEffect(() => {
console.log("The use effect ran");
}, [count, count2]);
// componentWillUnmount
useEffect(() => {
console.log("The use effect ran");
return () => {
console.log("the return is being ran");
};
}, []);
useEffect(() => {
console.log(`The count has updated to ${count}`);
return () => {
console.log(`we are in the cleanup - the count is ${count}`);
};
}, [count]);
return (
<div>
<h6> Counter </h6>
<p> current count: {count} </p>
<button onClick={() => setCount(count + 1)}>increment the count</button>
<button onClick={() => setCount2(count2 + 1)}>increment count 2</button>
</div>
);
};
export default Counter;
Currently next/router exposes a singleton API where listening to its changes can be done via:
Router.onRouteChangeStart = myHandler // subscribe
Router.onRouteChangeStart = null // unsubscribe
This poses several architecture-related challenges as two unrelated components can't listen to route state changes at the same time.
Based on the discussion on https://github.com/zeit/next.js/issues/2033 there is no plan to convert next/router to an Event Emitter / Observable.
Given that, how can we implement a router with shared subscriptions in Next.js?
Good news! As from Version of Next.js 6.1.1 multiple router event listeners will be allowed:
Example
Router.events.on('routeChangeStart', (url) => {
console.log('App is changing to: ', url)
})
Docs
Pull Request
Documentation
The solution I've been happy with so far involves wrapping next/router listener methods in Observables and creating a HLA attaching router events to components on componentDidMount.
An example implementation using RxJS:
// I'm using recompose for and rxjs, but you should be able to modify this code easily
// 1. sharedRouter.js
import Router from 'next/router'
import { Observable } from 'rxjs'
export const routeChangeStart$ = Observable.create(
obs => {
console.log('route: start')
Router.onRouteChangeStart = url => {
obs.next(url)
}
}
).share() // note the .share() operator,
// it ensures that we don't reassign Router.onRouteChangeStart
// every time a new component subscribes to this observable
export const routeChangeComplete$ = Observable.create(
obs => {
Router.onRouteChangeComplete = () => {
console.log('route: complete')
obs.next()
}
}
).share()
export const routeChangeError$ = Observable.create(
obs => {
Router.onRouteChangeError = () => {
console.log('route: error')
obs.next()
}
}
).share()
// 2. appBar/withRouterEvents.js
// This one is attached to our AppNav component
import { lifecycle } from 'recompose'
import * as SharedRouter from './sharedRouter'
const withRouterEvents = lifecycle({
componentDidMount(){
const onStartLoadingSub = Router.routeChangeStart$
.subscribe(
() => {
// hide nav
// show loading indicator
}
)
const onFinishLoadingSub = Router
.routeChangeError$
.merge(Router.routeChangeComplete$)
.subscribe(
() => {
// hide loading indicator
}
)
this.subs = [
onStartLoadingSub,
onFinishLoadingSub
]
},
componentWillUnmount(){
if(!Array.isArray(this.subs)) return;
this.subs.forEach(
sub => sub.unsubscribe()
)
}
})
// 3. appBar/index.js
export default ({
isNavVisible,
isLoading,
children
}) => <nav className={
isNavVisible ? 'app-bar' : 'app-bar app-bar--hidden'
}>
<LoadingIndicator isActive={isLoading} />
{children}
</nav>