redux-saga: eventChannel and listener which react callback return - react-native

In react-native backhandler listener react to callback function and act appropriately.
I need to read my store and depending on it, return true or false.
But I cant use select effect in normal function and I cant affect listener callback function from "watchBackButton" function.
export function* backButtonListen() {
return eventChannel(emitter => {
const backHandlerListener = BackHandler.addEventListener(
"hardwareBackPress",
() => {
emitter("back pressed");
}
);
return () => {
backHandlerListener.remove();
};
});
}
export function* watchBackButton() {
const chan = yield call(backButtonListen);
try {
while (true) {
let back = yield take(chan);
}
}

Since event channels are not bidirectional, I don't think there is a way to get some current state from saga to event channel using the select effect.
However, it is possible to access the store directly. There are multiple ways to get the store instance to the event channel. See my other answer here.
Using e.g. the context method you could do something like this:
// redux.js
...
const store = createStore(...);
sagaMiddleware.runSaga(rootSaga, {store});
// root-saga.js
export default function * rootSaga(context) {
yield setContext(context);
yield fork(watchBackButton);
}
// watch-back-button.js
export function* backButtonListen() {
const store = yield getContext('store');
return eventChannel(emitter => {
const backHandlerListener = BackHandler.addEventListener(
"hardwareBackPress",
() => {
emitter("back pressed");
return store.getState().foo === 'bar';
}
);
return () => {
backHandlerListener.remove();
};
});
}
export function* watchBackButton() {
const chan = yield call(backButtonListen);
try {
while (true) {
let back = yield take(chan);
}
}

Related

observable and computed not being reflected in a functional component

I am learning mobx for react-native and not able to see changes to done to observables or computed.
Basically, I want to listen to changes to observable from the component.
My store is simple:
import { observable, action, computed } from 'mobx';
import AsyncStorage from '#react-native-async-storage/async-storage';
class ConfigStore {
rootStore = undefined;
#observable activeConfig = {group: 'starter', TC: false};
constructor(rootStore) {
this.rootStore = rootStore;
}
#computed get termsLoaded(){
return this.activeConfig.TC;
}
#action async loadPreviousConfig() {
const configDetails = { group: 'starter', TC: false};
try {
const response = await AsyncStorage.multiGet([
'group',
'TC'
]);
configDetails.group = response[0][1] || 'starter';
configDetails.TC = response[1][1] === undefined ? false : true;
console.log(configDetails);// shows correct previously saved config
this.activeConfig = configDetails;
} catch (error) {}
}
}
export default ConfigStore;
From my component, I want to load first previous configuration settings and have them reflect in my app. Basically, I want to check the value of TC ater calling loadPreviousConfig, they return false still:
import { inject, observer } from 'mobx-react';
const ConfigComponent = (props) => {
const { store } = props;
const { termsLoaded, activeConfig, loadPreviousConfig } = store.configStore;
useEffect(() => {
const init = async () => {
await loadPreviousConfig();
console.log(termsLoaded); //always false even though console from the store shows it is true.
};
init();
}, []); //tried [props]
return (
<View>
<Text>{activeConfig.group}</Text> //never changes
</View>
);
};
export default inject('store')(observer(ConfigComponent));

How to use async/await to retrieve value from AsyncStorage in react native by using function

I have created prefsManager.js - Use for storing and retrieve data from AsyncStorage but I have faced a problem like when print log it return always undefined because of it is Async but I want to print the actual value in a log by the call of the function.
import { AsyncStorage } from 'react-native';
import prefskey from '../utils/constants/prefskeys';
const setValue = async (key, value) => {
await AsyncStorage.setItem(key, value);
}
const getValue = async (key) => {
let value = '';
try {
value = await AsyncStorage.getItem(key) || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return value;
};
const prefsmanager = {
setValue,
getValue
}
export default prefsmanager;
I have used this in my Home.js when button press I'm calling this method.
_handlePress() {
await prefsManager.setValue(prefskey.username, this.state.username)
console.log("username =>>", await prefsManager.getValue(prefskey.username));
}
You need to use async keyword on your function like this.
import { AsyncStorage } from 'react-native';
import prefskey from '../utils/constants/prefskeys';
const prefsnamager = {
setValue: function (key, value) {
AsyncStorage.setItem(key, value)
},
getValue: async (key) => {
let value = '';
try {
value = await AsyncStorage.getItem(key) || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return value;
}
}
export default prefsnamager;
calling function
_handlePress = () => {
prefsManager.setValue(prefskey.username, this.state.username)
console.log("username =>>" , prefsManager.getValue(prefskey.username));
}
Set value in storage
AsyncStorage.setItem('data','Read Data')
Get value from storage
constructor(props) {
super(props)
this.state = {};
let self=this;
//this function is called everytime , when you visit this screen.
this.__didFocusSubscription = this.props.navigation.addListener('didFocus',payload => {
AsyncStorage.getItem('data').then((value)=>{
if(value==null){
self.setState({count:'no data found'})
}
else{
self.setState({count:value})
}
})
});
}
Actually, it is like localStorage in the web but with a little difference. in getting the item it acts asynchronously. pay attention to below:
AsyncStorage.setItem('key', value);
But in getting it is like below:
AsyncStorage.getItem('key')
.then( value => console.log(value) );

Async function support in Mithril.js for webpack dynamic imports?

I'm trying to figure out how to use dynamic import in webpack with mithril. To do that elegantly, I think I'll need to use an async function somewhere along the line. Right now this is how I have used the async function:
import m from 'mithril'
let App = async () => {
let { Component } = await import('./components.js')
return {
view () {
return m(Component)
}
}
}
App().then(app => m.mount(document.body, app))
Ideally, I want to use it like this:
import m from 'mithril'
let App = {
async view () {
let { Component } = await import('./components.js')
return m(Component)
}
}
}
m.mount(document.body, App)
Is there something I've been missing from the documentation to acheive what I'd like to do? I've tried to look at every mention of promise, but it's possible that I've missed this.
Any help would be appreciated.
One way that should work is this:
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
main();
(async () => {
const myModule = await import('./myModule.js');
})();
For further information follow the link below.
ES proposal: import() – dynamically importing ES modules
Try the following, which provides a simple component named DynamicComponent which can be used anywhere and with children:
App.js
import m from 'mithril'
import { DynamicComponent } from './DynamicComponent'
const App = {
view() {
return m( DynamicComponent, {
component: 'OtherComponent'
}, 'Hello world' ),
}
}
}
m.mount(document.body, App)
OtherComponent.js
import m from 'mithril'
export function OtherComponent() { return {
view({ children }) { return m( 'div', children )}
}}
DynamicComponent.js
import { hooks } from '/hooks'
export function DynamicComponent() { return {
...hooks,
attrs: null,
component: null,
view({ children }) { return (
// Await module resolution and process attributes.
// Use '&&' as a shortcut to only continue
// once 'this.component' isn't null.
// Pass a clone of attrs to the loaded component.
this.component && m( this.component.default, this.attrs, children )
)}
}}
hooks.js
async function oninit({ attrs }) {
// Preload -> Load immediately, in parallel
// Prefetch -> Load when browser is idle (Can be less responsive)
// See more: https://webpack.js.org/guides/code-splitting/#prefetching-preloading-modules
// Dynamically import component and append '.js' (Don't include '.js' in your imports).
if ( attrs.loadType = 'prefetch' ) {
// Lazy load
this.component = await import( /* webpackPrefetch: true */ `
${ attrs.component }.js`
)
} else {
// Immediate load
this.component = await import( /* webpackPreload: true */ `
${ attrs.component }.js`
)
}
/*
Process and pass along attributes
This clones the attributes to prevent any changes from affecting
the original attributes.
You can save memory if it becomes a problem by directly
assigning `v.attrs` to `newAttrs`, but you lose this immutability.
*/
const newAttrs = { ...v.attrs }
// Remove attributes used in `DynamicComponent`
delete newAttrs.component
delete newAttrs.loadType
// Assign to component
this.attrs = newAttrs
m.redraw()
}
export const hooks = {
oninit,
}

React Native redux saga yield not working on first action

I am using redux saga in my React Native app. It all works except for one thing. For one of my actions when it is dispatched the first time, it stops at 'const newUserLogAction = yield take('CREATE_USER_LOG')' in the code below. So the console log 'saga callCreateUserLog take' is not printed, nothing happens. But if I dispatch the same action again, it works. I have other actions and they all work fine the first time.
saga file:
function * createUserLog () {
yield takeEvery('CREATE_USER_LOG', callCreateUserLog)
}
export function * callCreateUserLog () {
try {
console.log('saga callCreateUserLog start')
const newUserLogAction = yield take('CREATE_USER_LOG')
console.log('saga callCreateUserLog take')
console.log('newUserLogAction' + FJSON.plain(newUserLogAction))
const data = newUserLogAction.data
const newUserLog = yield call(api.createUserLog, data)
yield put({type: 'CREATE_USER_LOG_SUCCEEDED', newUserLog: newUserLog})
} catch (error) {
yield put({type: 'CREATE_USER_LOG_FAILED', error})
}
}
action file:
export const createUserLog = (data) => {
console.log('action create user log data = ' + FJSON.plain(data))
return ({
type: 'CREATE_USER_LOG',
data}
)
}
Even on the first dispatch, the data is printed correctly here, so the payload is correct.
react native functions doing the dispatching:
clickContinue = () => {
var event = {
'userId': this.props.user.id,
'eventDetails': {
'event': 'Agreed to the ISA Declaration'
}
}
this.props.dispatch(createUserLog(event))
}
You don't need to use take effect. Action object will be passed by takeEvery.
export function * callCreateUserLog (newUserLogAction) {
try {
console.log('saga callCreateUserLog start')
console.log('newUserLogAction' + FJSON.plain(newUserLogAction))
const data = newUserLogAction.data
const newUserLog = yield call(api.createUserLog, data)
yield put({type: 'CREATE_USER_LOG_SUCCEEDED', newUserLog: newUserLog})
} catch (error) {
yield put({type: 'CREATE_USER_LOG_FAILED', error})
}
}

Promise isn't working in react component when testing component using jest

Good day. I have the following problem:
I have an item editor.
How it works: I push 'Add' button, fill some information, click 'Save' button.
_onSaveClicked function in my react component handles click event and call function from service, which sends params from edit form to server and return promise.
_onSaveClicked implements
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
})
function and waits for promise result. It works in real situation.
I created fake service and placed it instead of real service.
Service's function contains:
return Promise.resolve({data: 'test response'});
As you can see fake service return resolved promise and .then() block should work immediatly. But .then() block never works.
Jest test:
jest.autoMockOff();
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const expect = require('expect');
const TestService = require('./service/TestService ').default;
let testService = new TestService ();
describe('TestComponent', () => {
it('correct test component', () => {
//... some initial code here
let saveButton = TestUtils.findRenderedDOMComponentWithClass(editForm, 'btn-primary');
TestUtils.Simulate.click(saveButton);
// here I should see response in my console, but I don't
});
});
React component save function:
_onSaveClicked = (data) => {
this.context.testService.saveData(data)
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
});
};
Service:
export default class TestService {
saveData = (data) => {
console.log('I\'m in services saveData function');
return Promise.resolve({data: data});
};
}
I see only "I'm in services saveData function" in my console.
How to make it works? I need to immitate server response.
Thank you for your time.
You can wrap your testing component in another one like:
class ContextInitContainer extends React.Component {
static childContextTypes = {
testService: React.PropTypes.object
};
getChildContext = () => {
return {
testService: {
saveData: (data) => {
return {
then: function(callback) {
return callback({
// here should be your response body object
})
}
}
}
}
};
};
render() {
return this.props.children;
}
}
then:
<ContextInitContainer>
<YourTestingComponent />
</ContextInitContainer>
So your promise will be executed immediately.