Redux | How do I properly connect to the redux store - react-native

I got my redux store to work but only if I import the store in the action creator file and call store.dispatch() from the action creator. Apparently calling store.dispatch() isn't a good practice and if I connect the function properly to the store, then I can just call dispatch() instead of store.dispatch(). That said, when I tried to just use dispatch(), I got the error: "Can't find variable: dispatch".
My best guess is that my issue is that I'm not using Redux's connect() function properly at the bottom of this code:
import React, {useState} from 'react'
import { connect } from 'react-redux'
import { StyleSheet, View, Text, TextInput, Image, TouchableOpacity } from 'react-native';
import { signup } from '../../actions/authAction'
const ApprovedScreen = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
return (
<View>
<TextInput
style={styles.textInput}
value={email}
onChangeText={text=>setEmail(text)}
/>
<TextInput
value={password}
onChangeText={text=>setPassword(text)}
/>
<TouchableOpacity onPress={()=>signup({email, password})}>
<Text>Create An Account</Text>
</TouchableOpacity>
</View>
)
}
export default connect(null, { signup })(ApprovedScreen)
And here's the { signup } function code:
import axios from 'axios'
import { LOG_IN } from './types'
export function signup ({email, password}) {
console.log('singup function ran')
axios
.post('https://MYAPI.com/signup', {email, password})
.then(response => dispatch({
type: LOG_IN,
payload: response.data.token
}))
.catch(error => {
console.log(error)
})
}

Since you're using redux-thunk, the action needs to return another function, which takes dispatch as an argument:
export function signup({ email, password}) {
return function(dispatch) {
return axios
.post('https://MYAPI.com/signup', {
email,
password
})
.then(response => dispatch({
type: LOG_IN,
payload: response.data.token
}))
.catch(error => {
console.log(error)
})
}
}
Also you need to pass signup function as the prop to your component:
const ApprovedScreen = ({signup}) => {

Related

undefined is not an object (evaluating 'userInfo._name') help me

I'm asking you this question because an error occurred again.
We're now receiving data through axios and storing that data through useState().
So if you create it on the screen and render it right away, you can see the data, but if you go to the previous page and go back in, an error occurs.
Please let me know how to solve it.
my Error
TypeError: undefined is not an object (evaluating 'userInfo._name')
my Code
import React, { useState, useEffect } from "react";
import { withNavigation } from "react-navigation";
import { Text, View, StyleSheet, SafeAreaView, Image } from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp,
} from "react-native-responsive-screen";
import axios from "axios";
const MyPage = ({ navigation, info }) => {
const [userInfo, setUserInfo] = useState();
const getData = async () => {
try {
axios
.get(
"http://everyweardev-env.eba-azpdvh2m.ap-northeast-2.elasticbeanstalk.com/api/v1/user"
)
.then((res) => res)
.then((data) => {
setUserInfo(data.data.data.result);
})
.catch((err) => console.log(err));
} catch (error) {}
};
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
getData();
});
return unsubscribe;
}, [navigation]);
return (
<View>
{/* <Image source={require("../../images/profile.png")} /> */}
<Text>{userInfo._name}</Text>
<Text>{userInfo._mail}</Text>
</View>
);
};
export default withNavigation(MyPage);
The problem is happening in the initial render where the userInfo object is null.
Do something like below, where you access the property only when there is a value for userInfo
<Text>{userInfo?._name}</Text>
<Text>{userInfo?._mail}</Text>

useEffect returns unhandled promise

I have been for several hours trying to get an API to be called in ReactNative useEffect hook. Sometimes when I restart my app the value is resolved. But most of the time, I have an Unhandled promise rejection. I googled and tried various methods. I tried using .then etc.. I just can't figure it out.
import React, { useState, useContext, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, FlatList } from 'react-native';
import { EvilIcons } from '#expo/vector-icons';
import jsonServer from '../api/jsonServer';
const ShowScreen = ({ navigation }) => {
const id = navigation.getParam('id');
const [post, setPost] = useState([]);
const getBlog = async () => {
const result = await jsonServer.get(`http://0.0.0.0/blog/docroot/jsonapi/node/article/${id}`);
return result;
}
useEffect(() => {
async function setToState() {
const val = await getBlog();
setPost(val);
}
setToState();
},[]);
return (
<View>
<Text>Here { console.log(post) }</Text>
</View>
);
};
ShowScreen.navigationOptions = ({ navigation }) => {
return {
headerRight: (
<TouchableOpacity
onPress={() =>
navigation.navigate('Edit', { id: navigation.getParam('id')
})}
>
<EvilIcons name="pencil" size={35} />
</TouchableOpacity>
)
};
};
const styles = StyleSheet.create({});
export default ShowScreen;
What you could do is something like this:
....
....
const [post, setPost] = useState([]);
const [isMounted, setIsMounted] = useState(false);
const getBlog = async () => {
const result = await jsonServer.get(`http://0.0.0.0/blog/docroot/jsonapi/node/article/${id}`);
return result;
}
useEffect(() => {
setIsMounted(true)
async function setToState() {
// using try catch I'm handling any type of rejection from promises. All errors will move to catch block.
try{
const val = await getBlog();
// checking if component is still mounted. If mounted then setting a value. We shouldn't update state on an unmounted component.
if(isMounted){
setPost(val);
}
} catch(err){
console.log("Error", err)
}
}
setToState();
return () => {
// Setting is mounted to false as the component is unmounted.
setIsMounted(false)
}
},[]);
I believe this will solve your Unhandled promise rejection error. Please try if it still doesn't solve the issue will create the same in Sanck.
I think my issue was not just promise, the issue is also seems to be me not handling undefined/null in the state. The below code is working for me.
import React, { useState, useContext, useEffect } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, FlatList } from 'react-native';
import { EvilIcons } from '#expo/vector-icons';
import jsonServer from '../api/jsonServer';
const ShowScreen = ({ navigation }) => {
const id = navigation.getParam('id');
const [post, setPost] = useState([]);
const getBlog = async () => {
const result = await jsonServer.get(`http://hello.com/jsonapi/node/article/${id}`).then(
res => {
setPost(res)
return res;
}, err => {
console.log(err);
});
}
useEffect(() => {
setPost(getBlog());
},[]);
return (
<View>
<Text>{ post.data ? post.data.data.id : "" }</Text>
</View>
);
};
export default ShowScreen;
Note: I am setting the state in useEffect as well as in the request. I am yet to check if I can just do it once.

Use redux action the dispatch is not working

I have combined my react redux.
Here is my App.js
import React from 'react';
import ReduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { compose, createStore, applyMiddleware } from 'redux';
import reducers from './src/reducers';
import AppContainer from './src/navigator'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const App: () => React$Node = () => {
const store = createStore(reducers, {}, composeEnhancers(applyMiddleware(ReduxThunk)));
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
};
export default App;
src/reducers/index.js
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
export default combineReducers({
LoginRedux: LoginReducer
});
If I use my action login(), I can see login action start, but I can't see dispatch start
import React from 'react';
import {
Text,
View,
TouchableOpacity,
} from 'react-native';
import { connect } from 'react-redux';
import { login } from '../actions';
const LoginScreen = ({ navigation }) => {
// console.log('see my test value', testValue)
return (
<View>
<TouchableOpacity
onPress={() => {
login();
}
}>
<View>
<Text>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
const mapStateToProps = (state) => {
const { testValue } = state.LoginRedux;
console.log('mapStateToProps testValue =>', testValue);
return { testValue };
};
export default connect(mapStateToProps, { login })(LoginScreen);
If I console.log(dispatch), it will show dispatch is not defined.
import { LOGIN } from './types';
export const login = () => {
console.log('login action start')
return (dispatch) => {
console.log('dispatch start');
// console.log(dispatch);
dispatch({ type: LOGIN, testValue: 'I am test' });
};
};
src/reducers/LoginReducer.js
import { LOGIN } from '../actions/types';
const INITIAL_STATE = {
testValue: ''
};
export default (state = INITIAL_STATE, action) => {
console.log('reducer =>', action); // I can't see the console.log
switch (action.type) {
case LOGIN:
return {
...state,
testValue: action.testValue
};
default:
return state;
}
};
I have no idea why my action dispatch is not working. Do I set something wrong ?
Any help would be appreciated.
According to Zaki Obeid help, I update like this:
the action code:
export const login = () => {
console.log('login !');
return { type: LOGIN };
};
the function component code:
import { login } from '../../actions';
export const SettingScreen = ({ navigation, login }) => {
// return view code
}
const mapDispatchToProps = dispatch => ({
// you will use this to pass it to the props of your component
login: () => dispatch(login),
});
connect(null, mapDispatchToProps)(SettingScreen);
In LoginScreen component
you will need to add mapDispatchToProps
const mapDispatchToProps = dispatch => ({
// you will use this to pass it to the props of your component
login: () => dispatch(login()),
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
Then
you will need to destructure from the props as:
const LoginScreen = ({ navigation, login }) => {
// your code
}
In actions.js
the way you use dispatch here requires a library redux-thunk and it's used for async calls.
and the normal action should do the job for you:
export const login = () => ({
type: LOGIN,
testValue: 'I am test'
})
I hope this is useful and will solve your problem,
Have a good day.
In a react-redux app, you obtain the dispatch function either from getting a hold of the store object directly (store.dispatch), or via the react-redux connect function, which will provide dispatch as an argument to a function you write and then later hook up to a component
import { connect } from 'react-redux';
const mapStateToProps = ...
const mapDispatchToProps = (dispatch) => {
return {
someHandle: () => dispatch(myActionCreator())
}
}
export const connect(mapStateToProps, mapDispatchToProps)(MyComponent)
You can't just call dispatch out of thin air -- it's not a global function.
It seems you are using the login function directly. you will have to use the props. Just change the name for confusing and use through props.
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
export default combineReducers({
LoginRedux: LoginReducer
});
If I use my action login(), I can see login action start, but I can't see dispatch start
import React from 'react';
import {
Text,
View,
TouchableOpacity,
} from 'react-native';
import { connect } from 'react-redux';
import { login } from '../actions';
const LoginScreen = ({ navigation, userLogin }) => {
// console.log('see my test value', testValue)
return (
<View>
<TouchableOpacity
onPress={() => {
userLogin();
}
}>
<View>
<Text>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
const mapStateToProps = (state) => {
const { testValue } = state.LoginRedux;
console.log('mapStateToProps testValue =>', testValue);
return { testValue };
};
export default connect(mapStateToProps, { userLogin:login })(LoginScreen);

Connect react-redux to stateless component. Properties inside the function are not updated

Problem:
I have very simple todo app. There is one action - add todo. When I add a task, I simulate sending it to the server using a setTimeout.
When I receive a response from the server, I immediately check to see if there is an error to avoid further action. In stateful component, everything works, and in stateless component it doesn't.
See the code to better understand the problem.
Environment:
"react": "16.8.6",
"react-native": "0.60.5",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
№ 1. Stateful component:
import React, {Component} from 'react';
import {View, Button, ActivityIndicator} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
class MainScreen extends Component {
todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
sendTodoToServer = async () => {
const todo = this.todoGenerator();
const {addTodo} = this.props;
await addTodo(todo);
// this
const {error} = this.props;
if (error) {
console.log('error', error);
}
};
render() {
const {isLoading} = this.props;
return (
<View>
<Button title="Generate todo" onPress={this.sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
}
}
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreen);
№ 1. Stateful component. Console:
As you can see,
const {error} = this.props;
if (error) {
console.log('error', error);
}
it's work. Okay, let's move on to functional components
№ 2. Stateless component with redux connect:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenFC = ({isLoading, addTodo, error}) => {
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await addTodo(todo);
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenFC);
№ 2. Stateless component with redux connect. Console:
The error did not display in the console, although it is in the reducer
№ 3. Stateless component with redux HOOKS:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect, shallowEqual, useDispatch, useSelector} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenReduxHooks = () => {
const todos = useSelector((state: AppState) => state.todos, shallowEqual);
const error = useSelector((state: AppState) => state.error, shallowEqual);
const isLoading = useSelector(
(state: AppState) => state.isLoading,
shallowEqual,
);
const dispatch = useDispatch();
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await dispatch(addTodoAction(todo));
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenReduxHooks);
№ 3. Stateless component with redux HOOKS. Console:
It's the same here, as in the second example.
Questions:
Can redux be connected to a stateless component?
How do you make the second and third example work the same way as the first?
Other code:
App.js
import React from 'react';
import {Provider} from 'react-redux';
import {MainScreen, MainScreenFC, MainScreenReduxHooks} from './src/screens';
import store from './src/redux';
const App = () => {
return (
<Provider store={store}>
<MainScreenFC />
</Provider>
);
};
export default App;
store.js:
import {applyMiddleware, createStore} from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducer';
export default createStore(rootReducer, applyMiddleware(thunk, logger));
reducer.js:
const ADD_TODO_REQUEST = 'ADD_TODO_REQUEST';
const ADD_TODO_SUCCESS = 'ADD_TODO_SUCCESS';
const ADD_TODO_FAILURE = 'ADD_TODO_FAILURE';
const initialState = {
todos: [],
isLoading: false,
error: undefined,
};
export const addTodo = data => async dispatch => {
dispatch({
type: ADD_TODO_REQUEST,
payload: {
isLoading: true,
},
});
try {
const todo = await new Promise((resolve, reject) => {
setTimeout(() => {
reject('Ooops, error');
}, 3000);
});
dispatch({
type: ADD_TODO_SUCCESS,
payload: {
todo,
isLoading: false,
},
});
} catch (e) {
dispatch({
type: ADD_TODO_FAILURE,
payload: {
isLoading: false,
error: e,
},
});
}
};
export default function(state = initialState, {type, payload}) {
switch (type) {
case ADD_TODO_REQUEST: {
return {
...state,
isLoading: true,
};
}
case ADD_TODO_SUCCESS: {
return {
...state,
isLoading: false,
todos: [...state.todos, payload.todo],
};
}
case ADD_TODO_FAILURE: {
return {
...state,
isLoading: false,
error: payload,
};
}
default:
return state;
}
}
I think the problem with your code is that you try to wait for a response in the component, it's a bad idea for both stateful and stateless components. What would I recommend you to do, is to handle error somewhere in your middleware (redux-thunk, redux-saga, etc). In this case, your component should just represent data, and if you have to display an error, just take it from props, I believe it is stored somewhere in redux.
In any way, stateless components shouldn't be an async function, because the result of an async function is a promise but not a component. There are some libraries like async-reactor, but personally, I prefer to go with another approach.
If you tell me your use case, what would you like to do once you got an error, I'll give you a more useful answer.
Update:
export const addTodo = data => dispatch => {
// tell your application that request is sending,
// so you can handle it in UI (show a progress indicator)
dispatch({
type: ADD_TODO_REQUEST,
payload: data
});
try {
const response = await createTodo(data);
dispatch({
type: ADD_TODO_SUCCESS,
payload: response
});
// here you can dispatch navigation action as well
} catch (error) {
dispatch({
type: ADD_TODO_FAILURE,
error
});
// and here you can dispatch action with a toast
// to notify users that something went wrong
}
};

The redux is giving "is not a function" error - redux sauce

I'm using redux sauce. When I dispatch an action, it is giving me "_HelpRedux.default.request is not a function" error. Any help is appreciated. How can I make it right? I'm out of ideas. Thanks in advance
HelpRedux.js
import { createReducer, createActions } from 'reduxsauce';
import Immutable from 'seamless-immutable';
const { Types, Creators } = createActions({
helpPostt: null,
dashboardRequest: ['data'],
})
export const HelpTypes = Types
export default Creators
export const INITIAL_STATE = Immutable({
fetching: false,
data: null,
})
export const helpRequestPost = (state) => {
return state;
}
export const request = (state) => state.merge({ fetching: true });
export const reducer = createReducer(INITIAL_STATE, {
[Types.HELP_POSTT]: helpRequestPost,
[Types.DASHBOARD_REQUEST]: request,
})
HelpScreen.js
import React, { Component } from 'react'
import { View, Text, KeyboardAvoidingView, ScrollView, TouchableOpacity, TextInput, Linking, Platform } from 'react-native'
import { connect } from 'react-redux';
import HelpRedux from '../Redux/HelpRedux';
class HelpScreen extends Component {
tryLogin = () => {
this.props.request(); //_HelpRedux.default.request is not a function
//this.props.helpRequestPost(); //_HelpRedux.default.helpRequestPost is not a function
}
render () {
console.log("help", this.props);
return (
<View>
<TouchableOpacity onPress={this.tryLogin}>
<Text>abc</Text>
</TouchableOpacity>
</View>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
request: () => dispatch(HelpRedux.request()),
helpRequestPost: (key) => dispatch(HelpRedux.helpRequestPost(key)),
}
}
export default connect(null, mapDispatchToProps)(HelpScreen)
. .
OK I made a mistake here. Instead of calling an action, I was calling reducer.
request: () => dispatch(HelpRedux.helpPostt()),