So, I am working on a pretty straight forward mobile app that has these scenes:
a list of people
person profile
add form
now, what I do, when I first load the LIST scene, I make an API call (I have a list component that I populate once I get results from the API... state.people).
All good here... when I tap on a person he's profile opens, no extra API calls, just passing the person object from state.people array.
All good here as well.
When I open ADD NEW person and send the form I make another API call (I post the information and get the new Object back)...
now the bit that is confusing to me.
What I would like is to update the LIST scene state.people by making another API call (get all again) after I get the OK confirmation from the POST.
and then navigate to Person's profile.
but, I am outside the scope of the LIST scene (I am in ADD NEW form). So, what would be the correct redux logic for this one?
The LIST component is already mounted... how do I communicate to LIST if I am on different scene
all these binding actions to components properties is confusing too... why can't redux act like a global hub that would always be accessible and would always retain it's state (at least on mobile app)
There is really a lack of real app examples... so far I see only very simplified examples that are not very useful on the grand scale to understand the whole flow
the store I have
/**
* STORE
*/
'use strict';
import { createStore, applyMiddleware } from 'redux';
import reducer from './_reducer';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
const store = createStore(reducer, {}, applyMiddleware(
thunkMiddleware,
promiseMiddleware()
));
export default store;
and the actions I have:
import * as constants from '../../constants/constants';
import request from '../../utils/request';
export const getAll = () => ({
type: constants.PEOPLE_FETCH,
payload: request(constants.API_PATH + 'person', {method: 'GET'})
});
export const search = (data, searchTerm) => ({
type: constants.PEOPLE_SEARCH,
payload: _filter(data, searchTerm)
});
export const save = (data) => ({
type: constants.PERSON_SAVE,
payload: request(constants.API_PATH + 'person', {method: 'POST', body: JSON.stringify(data)})
});
This can be an example architecture for your app:
Make a Redux store with list of people.
On initial API call, update the store to contain the list fetched by API call.
Wrap your app inside Provider and pass the store to the Provider.
Use connect and mapStateToProps and mapDispatchToProps to connect the Redux store to React state.
Whenever you update or insert new person, and get the new object, you need to dispatch an action which then goes to the reducer function which finally returns the updated Redux store, and dont worry with the re-rendering as React does the re-rendering itself whenever there is a change in a state.
I'll give a small example of store/actions/reducer, with a react + redux app.
store.js
import { applyMiddleware, compose, createStore } from 'redux'
import reducer from './reducer'
import logger from 'redux-logger'
// TOOD: add middleware
let finalCreateStore = compose(
applyMiddleware(logger())
)(createStore)
export default function configureStore (initialState = { todos: [] }) {
return finalCreateStore(reducer, initialState)
}
actions.js
let actions = {
helloWorld: function(data){
return {
type: 'HELLO_WORLD',
data: data
}
}
};
export default actions
reducer.js // Please read from Redux docs that reducers need to be pure functions
export default function myReducer(state = [], action) {
switch (action.type) {
case 'HELLO_WORLD':
return 'welcome' + data;
default:
return state;
}
}
Component.js (the React App) //In component whenever you receive new object, dispatch an action which will modify the store.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actions from '../redux/actions'
class App extends Component {
handleClick() {
store.dispath(action.helloWorld("jimmy")); //this dispatches an action, which goes to the reducer to change the state and adds 'welcome' before 'jimmy'
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>
{store.getState()} //getState function to access store values
</div>
)
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch) //binds all the actions with dispatcher and returns them
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
This works like whenever you click the 'div' in the React Component, it calls the function, handleClick(), in which there is an action dispatch. This action then calls the reducer itself to update the store. I know you might get confused that how is store getting updated. Its a bit confusing but for that you need to follow a basic tutorial to explain React+Redux.
Please note this is not a runnable example, just a pseudocode. I recommend you to watch this youtube series to completely understand the redux stores+ react+webpack
Related
I've setup react native push notifications with Firebase and local push notifications - both work fine.
On receiving a notification when the app is in the foreground (opened and focused) I am calling a local push notification which works fine as well.
Then, when the user clicks on the local notification, in the utility class there is an OnNotification method that is called and I need to set some state there which of course doesn't work.
So instead, I am trying to callback a function on the parent file (App.js) which does has access to state management. problem is the function in App.js is not accessible in the utility class (it's not a class component).
How can this be achieved?
Thanks in advance :)
Below is an simplified example of both files.
// App.js
import React, {useState, useEffect} from 'react';
import Notifications from './src/utils/Notifications/Notifications'; // import the utility class.
const App = () => {
// handles remote notification when app is in the FOREGROUND
useEffect(() => {
const unsubscribe = messaging().onMessage(async (remoteMessage = {}) => {
// CALL FUNCTION ON UTILITY CLASS
Notifications.localNotification(remoteMessage.data);
});
return unsubscribe;
}, []);
// THE CALLBACK FUNCTION PART
const [someState, setSomeState] = useState('');
const [someMoreState, setSomeMoreState] = useState('');
const myFunc = (returnedData) => {
setSomeState(returnedData.value_1)
setSomeMoreState(returnedData.value_2)
}
return (
<></>
);
};
export default App;
// Notifications.js - utityly class.
import PushNotification from 'react-native-push-notification';
class Notifications {
constructor() {
PushNotification.configure({
onNotification: function (notification) {
if (notification.userInteraction) {
// THIS IS TRIGGERED AFTER THE USER CLICKS THE NOTIFICATION - WORKS FINE.
// HOW TO CALL FUNCTION HERE THAT NEEDS TO HANDLE STATE (function is in app.js)
// example:
const returnedData = {
value_1: 'A',
value_2: 'B',
}
myFunc(returnedData); // this function is in app.js
}
},
});
}
localNotification(data) {
}
}
export default new Notifications();
Accessing a function in App.js from components is not a good approach. As I understand it, you only need to access this function to set some state variables. A much better solution to your problem would be to use a shared state like Redux or Context API.
You can then access this state from anywhere in your project and change it accordingly
Answering my own question in case it might help somebody.
The only thing that worked for me is to refactor the Notifications class into App.js inside a useEffect()
I am new to React Native Programming. So, please tell me in detail. thank you.
calling use Selector
I am calling use Selector inside my functional component like this:
import { useDispatch, useSelector } from 'react-redux';
const AddAddressScreen = ({ navigation }) => {
const dispatch = useDispatch();
const data = useSelector(state => state);
console.log(data + "happy Coding");
return (
<View style={styles.container}>
<View>
);
}
export default AddAddressScreen;
My reducer looks like this
case types.API_LOGIN_SUCCESS:
if (action.result.result.mobile_verified === false) {
return {
...state,
onLoad: false,
result: action.result,
status: action.status,
error: null,
navigation: action.navigation.navigate("VerifyMNO")
};
} else {
return {
...state,
onLoad: false,
result: action.result,
status: action.status,
error: null,
navigation: action.navigation.navigate("AddAddress")
};
}
here my mobile number is verified so I move to the address screen.
where I use Use Selector which gives me an error. while I remove above two lines my code runs successfully.
My saga looks like this
export function* watchLoginUserInfo() {
yield takeLatest(types.LOGIN_USER, loginApiSaga)
}
My root saga
import { all, fork } from 'redux-saga/effects';
import { watchLoginUserInfo, } from './authenticationSagas';
function* rootSaga() {
yield all([
watchLoginUserInfo(),
])
}
export default rootSaga;
My Store looks like this
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../redux/reducers/root-reducer.js'
import createSagaMiddleware from 'redux-saga';
import rootSaga from '../redux/sagas/rootSaga';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
export {store};
when ever I use use Selector hook in my code it gives me the following error.
error 1
error 2, 3, 4
Use the select effect from redux-saga inside of a reducer: https://redux-saga.js.org/docs/api/#selectselector-args
For example const selectedState = yield select(state => state);.
The useSelector hook is for use inside of a function component.
EDIT: since the above doesn't seem to be the issue, I think the issue is that you're calling navigation functions from within your reducer. Reducer code can have no side effects, so you can't call navigation.navigate(...) from within the reducer. This will need to happen in the saga code instead. It might be able to be done in the loginApiSaga or in a dedicated saga that is triggered by API_LOGIN_SUCCESS.
I'm developing a Chrome extension using one of the Vue js boilerplates from Github. The default boilerplate setup is as follows:
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import mutations from './mutations';
import * as actions from './actions'; // all actions are imported as separate vars
Vue.use(Vuex);
export default new Vuex.Store({
state: { },
mutations,
actions
});
Then in actions.js
import * as types from './mutation-types';
export const setFoo = ({ commit }, payload) => {
commit(types.SET_FOO, payload); // SET_FOO is defined in the mutation-types file
};
I think the above approach lacks a fundamental reason why we want to use mutation types file - to avoid retyping the names for mutations and actions.
So instead, I came up with a different approach:
store/index.js
...
import actions from './actions'; // actions are imported as separate functions
...
Then in actions.js
import * as types from './mutation-types';
export default {
[types.UPDATE_FOO] ({commit}, payload) {
commit(types.UPDATE_FOO, payload);
}
}
Then anywhere in the extension, we could also import mutation-types and dispatch actions using const names like so:
store.dispatch(types.UPDATE_FOO, 'some value');
The second approach seems to be more practical in terms of naming and then dispatching/committing our actions/mutations. Or could there be any issues with the latest?
Which of the above, would be generally better practice?
The first approach is preferable, but it's completely up to you. Similar approach is used in official Vuex docs.
Refrence
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
// actions.js
actions: {
checkout ({ commit, state }, products) {
// save the items currently in the cart
const savedCartItems = [...state.cart.added]
// send out checkout request, and optimistically
// clear the cart
commit(types.CHECKOUT_REQUEST)
// the shop API accepts a success callback and a failure callback
shop.buyProducts(
products,
// handle success
() => commit(types.CHECKOUT_SUCCESS),
// handle failure
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
I am working at a React Native and Redux project with several reducers, that are combined through combineReducers().
I have one component, that doesn't dispatches any actions but only shows information from the redux state. It is connected to this state with the connect function:
class MyComponent extends React.Component {
componentWillMount() {
// some code
}
render() {
return(
<Text>
{ this.props.value }
</Text>
)
}
}
function mapStateToProps(state) {
return {
value: state.updaterReducer.value,
}
}
export default connect(
mapStateToProps,
) (MyComponent);
There's another part/component in the project, that changes the redux state and value:
import { makeUpdate } from './updater.actions.js';
import configureStore from '#redux/configureStore.js';
const store = configureStore();
// This function is can be called by some buttons in the app:
export default function update() {
console.log('Update function called');
store.dispatch(makeUpdate());
}
And here's my problem: when update is called, the updater-action and the updater-reducer are both called, so that the redux state changes, but 'MyComponent' never updates.
I could solve the problem on my own: The solution was very easy in the end, but couldn't be found on the basis of the code in the original question: Every time I needed the redux-store, I used a configureStore-function from a web-tutorial to create it on basis of the reducers. So I created multiple times the 'same' store. Unfortunately these stores were not connected to each other...
Sometimes that worked in the project, because a mapStateToProps-function and a mapDispatchToProps-function both were in the the same component and used one store, but sometimes (like in the example in the question) those functions used different stores and couldn't influence each other.
I am trying to integrate redux-persist with wix react-native-navigation. However, I am unable to find any examples or documentation stating the boilerplate code needed to integrate the both libraries.
I was wondering if anyone would like to share their solution if they have solved this issue ?
First of all, the basic setup should be the similar with or without react-native-navigation as described in the documentation in store.js:
import { persistStore, persistCombineReducers } from 'redux-persist'
import storage from 'redux-persist/es/storage' // default:
localStorage if web, AsyncStorage if react-native
import reducers from './reducers' // where reducers is an object of
reducers
const config = {
key: 'root',
storage,
}
const reducer = persistCombineReducers(config, reducers)
function configureStore () {
// ...
let store = createStore(reducer)
return store
// We'll skip persistStore for now
// let persistor = persistStore(store)
//return { persistor, store }
}
The persistStore call is commented out as we'll do it below. The persistStore method takes a callback in its third argument. The callback is executed after the state is restored/rehydrated. This is nice because this means we can delay starting the screen(s) until the state is rehydrated.
Let's assume you have the following bootstrap code in App.js:
store = configureStore()
registerScreens(store, Provider)
Navigation.startTabBasedApp({
tabs: [{...},]
})
Now we can add persistStore and wrap your bootstrap code in it like this:
store = configureStore()
persistStore(store, null, () => {
registerScreens(store, Provider)
Navigation.startTabBasedApp({
tabs: [{...},]
})
})
Note:
In v4, you pass config instead of null: persistStore(store, config, callback)
In case you're looking to integrate it with react-native-navigation v2, in App.js, make sure you call persistStore() inside the registerAppLaunchedListener() :
import { persistStore } from 'redux-persist';
...
Navigation.events().registerAppLaunchedListener(() => {
persistStore(store, null, () => {
Navigation.registerComponentWithRedux(...);
...
Navigation.setRoot({...})
...
})
})
Adding to his solution you can also use subscribe() to check if your user is still logged in. That way they don't need to sign in again if they completely close the app (for those users with a login system) and since it is only called once the store is persisted, you can start your app after this is checked.
import {Platform, AsyncStorage, AppState} from "react-native"
import {Navigation} from "react-native-navigation"
import {registerScreens} from "./routes"
import {Provider} from "react-redux"
import configureStore from "./stores/reduxStore"
import {Component} from "react"
const storage = configureStore()
registerScreens(Provider, storage.store)
let startapp = screen => {
Navigation.startSingleScreenApp({
screen: {
screen, // unique ID registered with Navigation.registerScreen
navigatorStyle: {
navBarHidden: true,
statusBarHidden: false,
statusBarColor: "white",
statusBarTextColorScheme: "dark"
}, // override the navigator style for the screen, see "Styling the navigator" below (optional)
navigatorButtons: {} // override the nav buttons for the screen, see "Adding buttons to the navigator" below (optional)
},
drawer: {
left: {
screen: "Drawer", // unique ID registered with Navigation.registerScreen
passProps: {} // simple serializable object that will pass as props to all top screens (optional)
}
},
tabsStyle: {
// optional, add this if you want to style the tab bar beyond the defaults
tabBarButtonColor: "#ffff00", // optional, change the color of the tab icons and text (also unselected). On Android, add this to appStyle
tabBarSelectedButtonColor: "#ff9900", // optional, change the color of the selected tab icon and text (only selected). On Android, add this to appStyle
tabBarBackgroundColor: "#551A8B", // optional, change the background color of the tab bar
initialTabIndex: 1 // optional, the default selected bottom tab. Default: 0. On Android, add this to appStyle
},
appStyle: {
orientation: "portrait"
}
})
}
storage.persistor.subscribe(() => {
storage.store.getState().user.logged
? startapp("mainscreen")
: startapp("loginscreen")
})
We actually dont need redux-persist. We can make our own redux-persist with:
redux + store.subscribe(handlechange)
handleChange function will run when ever something changes in our store.
Also Using aync-await(promise) we are not blocking the main execution thread.
So Inside create store add something like:
store.subscribe(async ()=>{
try {
await AsyncStorage.setItem("store", JSON.stringify(store.getState()));
} catch (error) {
// Error
}
})
Then inside App.js(first component to load). use AsyncStorage.getItem('store'). Then update the store before app starts.
localstorage on the web is a synchronous function which blocks the main thread.
AsynsStorage in react-native doesn't blocks the main thread.