React Native with Redux : data is loaded, render shows empty - reactjs

I have a react native app with react-redux , redux-persist and redux-thunk.
in the Component, I'm rendering the data from props, if the data length is less than one, i show an error, no data available.
it's always showing 'no data available' but actually data is in the props. as i check the console logs, ( using redux-logger ) data is available in the props.
if i put forceUpdate() at componentDidMount doesnt even help.
but if i put the forceUpdate() with a timeout it will load the data.
setTimeout(()=>{
this.forceUpdate();
}, 1000);
What could be the problem? Is render happening before data loads from props?
CoursesPage.js
import {bindActionCreators} from "redux";
import {connect} from "react-redux";
import Courses from "./components/Courses";
import {Actions as routes} from "react-native-router-flux";
import * as courseActions from "./courses.actions";
function mapStateToProps(state) {
return {
user: state.auth.user,
users: state.auth.users,
courses: state.courses.courses,
lectures: state.courses.lectures,
courseDetails: routes.courseDetails,
openProfile: routes.profilePage
}
}
function dispatchToProps(dispatch) {
return bindActionCreators({
getCourses: courseActions.getCourses
}, dispatch);
}
export default connect(mapStateToProps, dispatchToProps)(Courses);
Courses.js
import React, {Component, PropTypes} from "react";
import {
ActivityIndicator,
ListView,
StyleSheet,
Text,
View,
Image,
NetInfo,
Alert,
TouchableOpacity,
ScrollView,
Dimensions,
Platform,
RefreshControl
} from 'react-native';
import { Loader, Accordion, I18n, CustomNavBar, CustomAccordion } from "../../common/components";
import styles from "../../common/styles";
let DeviceInfo = require('react-native-device-info');
import Icon from 'react-native-vector-icons/Ionicons';
let { width, height } = Dimensions.get('window');
export default class Courses extends Component {
static propTypes = {
user: PropTypes.string.isRequired,
users: PropTypes.object.isRequired,
courseDetails: PropTypes.func.isRequired,
courses: PropTypes.object.isRequired,
getCourses: PropTypes.func.isRequired,
openProfile: PropTypes.func.isRequired
};
constructor(props) {
super(props);
this.state = {
isLoading: false,
isRefreshing: false
};
this._isMounted = false;
}
componentWillMount(){
this._isMounted = true;
const { users, getCourses } = this.props;
getCourses(users);
}
componentWillUnmount(){
this._isMounted = false;
}
componentWillReceiveProps(){
setTimeout(()=>{
this.forceUpdate();
}, 1000);
}
componentDidMount(){
setTimeout(()=>{
this.forceUpdate();
}, 1000);
setTimeout(()=>{
this.forceUpdate();
}, 2000);
}
async loadData(){
await this.props.getCourses(this.props.users);
setTimeout(()=>{
this.forceUpdate();
}, 1000);
}
selectRow(courseData) {
this.props.courseDetails({
courseData: courseData
});
}
renderData(containerList){
/* rendering .... */
}
render() {
const {user, users, getCourses, courses, openProfile} = this.props;
const data = courses[user];
let containerList = [];
Object.keys(data).forEach((d)=>{
let courseList = [];
Object.keys(data[d].courses).forEach((c)=>{
courseList.push(data[d].courses[c]);
});
containerList.push({
id: data[d].id,
title: data[d].title,
courses: courseList
});
});
return (
<View style={styles.container}>
<View style={{ width: width, height: Platform.OS == "ios" ? 64 : 54}}>
<CustomNavBar
width={width}
height={Platform.OS == "ios" ? 64 : 54}
title={I18n.t("details_page_book_button")}
titleSize={18}
buttonSize={15}
background={"#00a2dd"}
color={"#FFF"}
rightIcon={"ios-person-outline"}
rightIconSize={30}
rightAction={()=> { openProfile(); }}
/>
</View>
<View style={{ height: Platform.OS == "ios" ? height - 114 : height - 130 }}>
{!this.state.isLoading ?
<ScrollView
refreshControl={
<RefreshControl
refreshing={this.state.isRefreshing}
onRefresh={this.loadData.bind(this)}
tintColor="#00a2dd"
title=""
titleColor="#00a2dd"
colors={['#00a2dd', '#00a2dd', '#00a2dd']}
progressBackgroundColor="#FFFFFF"
/>
}
>
{this.renderData(containerList)}
</ScrollView>
:<ActivityIndicator
animating={true}
style={{ paddingTop: Platform.OS == "ios" ? (height - 114)/2 : (height - 130)/2 }}
color={'#00a2dd'}
size={'small'}
/>}
</View>
</View>
);
}
}

I think you dont change state so it is seen same data.So I suggest you should change code like following.Also you should immutable js to change state.
courseActions:
export function getCoursesRequest () {
return {
type: "GET_COURSES_REQUEST"
}
}
export function getCoursesSuccess (json) {
return {
type: "GET_COURSES_SUCCESS",
payload: json
}
}
export function getCoursesFailure (json) {
return {
type: "GET_COURSES_FAILURE",
payload: json
}
}
export function getCourses (sessionToken) {
return dispatch => {
dispatch(getCoursesRequest())
// store or get a sessionToken
return appAuthToken.getSessionToken(sessionToken)
.then((token) => {
return BackendFactory(token).getCourses()
})
.then((json) => {
dispatch(getCoursesSuccess(json))
})
.catch((error) => {
dispatch(getCoursesFailure(error))
})
}
}
coursesInitialState
const {Record} = require("immutable");
var InitialState = Record({
courses: {}
});
export default InitialState;
reducer:
const InitialState = require("./authInitialState").default;
const initialState = new InitialState();
export const courseReducer = (state = initialState, action) => {
if (!(state instanceof InitialState)) return initialState.mergeDeep(state);
switch (action.type) {
case "GET_COURSES_SUCCESS":
const {value} = action.payload;
let nextState = state.setIn(["courses"], value;
return nextState;
case "GET_COURSES_FAILURE":
}
}

Related

Issues with Context for React Native login

I have managed to get my login authentification working, but I am having an issue with the conditional navigator in App.js. If I log in using the LoginStackNavigator, I then need to refresh the app to be able to use the DrawerNavigator. I would like it so that as soon as I log in, App.js realises this, and takes me to the drawer navigator.
I've tried to use the Context API but it isn't working. What am I doing wrong?
Here is my AppContext.js
import { createContext } from "react";
const AppContext = createContext({
isloggedIn: {},
setLoggedIn: () => {},
});
export default AppContext;
Here is my App.js:
import AppContext from "./src/Component/AppContext";
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
isloggedIn: false,
};
this.loginStatusCheck();
}
loginStatusCheck = async () => {
const userToken = await AsyncStorage.getItem("#storage_Key");
if (userToken) {
this.setState({ isloggedIn: true });
} else {
this.setState({ isloggedIn: false });
}
};
render() {
return (
<AppContext.Provider
value={{
isloggedIn: this.state.isloggedIn,
setLoggedIn: this.setLoggedIn,
}}
>
<NavigationContainer>
{this.state.isloggedIn ? (
<DrawerNavigator />
) : (
<LoginStackNavigator />
)}
</NavigationContainer>
</AppContext.Provider>
);
}
}
And here is my LoginScreen.js:
import AppContext from "../../Component/AppContext";
const LoginCall = () => {
const { setLoggedIn } = useContext(AppContext);
return setLoggedIn(true);
};
export default class LoginScreen extends Component {
login = async () => {
const { email, password, confirmpassword } = this.state;
console.log(email);
axios
.post("http://127.0.0.1:8002/rest-auth/login/", {
username: "username",
email: "default#email.com",
password: "password",
})
.then((response) => {
console.log(response.data.key);
this.storeKey(response.data.key);
LoginCall;
})
//.then(this.props.navigation.navigate("HomeScreen"))
.catch((error) => {
console.log(error);
});
};
storeKey = async (value) => {
try {
await AsyncStorage.setItem("#storage_Key", value);
} catch (e) {
console.log("error" + e);
} finally {
console.log("done");
}
};
render() {
return (
<View
style={{
backgroundColor: "#fff",
paddingTop: 40,
alignItems: "center",
flex: 1,
}}
>
<TouchableOpacity onPress={() => this.login()}>
<Text>Login</Text>
</TouchableOpacity>
</View>
);
}
}
I think its because you're not importing axios on LoginSreen.
Try to add import axios from 'axios' with others importations

React navigation doesn't change navigation screen after redux state changes

Navigation does not change to MainScreen even after redux state changes. I have verified that authState changes from {"isAuthenticated": false, "isLoading": true, "token": null} to {"isAuthenticated": true, "isLoading": false, "token": "some_token"}, but the navigation page stays at login page (inside LandingNavigator) instead of going to MainNavigator.
AppNavigation.js
const Navigator = () => {
var [authStat, setAuthStat] = useState({})
useEffect(()=> {
var authState = store.getState().auth
setAuthStat(authState)
}, [authStat])
console.log(authStat);
if(authStat.isLoading){
return(
<Stack.Navigator>
<Stack.Screen
options={{headerShown: false}}
name="Splash"
component={ShowSplash}
/>
</Stack.Navigator>
)
}
else{
return(
<Stack.Navigator>
{ authStat.isAuthenticated ?
(<Stack.Screen
options={{headerShown: false}}
name="Main"
component={MainNavigator}
/>)
:
(<Stack.Screen options={{headerShown: false}} name="Landing" component={LandingNavigator} />)
}
</Stack.Navigator>
)
}
};
AuthAction.js
export function loginRequest() {
return {
type: "LOGIN_REQUEST",
};
}
export function loginSuccess(data) {
return {
type: "LOGIN_SUCCESS",
payload: data
};
}
export function loginFailure(data) {
return {
type: "LOGIN_FAILURE",
payload: data
};
}
export function restoreToken(data) {
return {
type: "RESTORE_TOKEN",
payload: data
};
}
export function logOut() {
return {
type: "LOGOUT",
};
}
AuthReducer.js
/* eslint-disable comma-dangle */
const authState = {
isLoading: true,
isAuthenticated: false,
token: null
};
export const authReducer = (state = authState, action) => {
const newState = JSON.parse(JSON.stringify(state));
switch (action.type) {
case 'LOGIN_REQUEST': {
return {
isLoading: true, // Show a loading indicator.
isAuthenticated: false
}
}
case 'RESTORE_TOKEN': {
return {
isLoading: false, // Show a loading indicator.
isAuthenticated: true,
token: action.payload
}
}
case 'LOGIN_FAILURE':
return {
isLoading: false,
isAuthenticated: false,
error: action.error
}
case 'LOGIN_SUCCESS':
return {
isLoading: false,
isAuthenticated: true, // Dismiss the login view.
token: action.payload
}
case 'LOGOUT': {
return {
isLoading: false, // Show a loading indicator.
isAuthenticated: false,
token: null
}
}
default:
return newState;
}
return newState;
};
Auth.js
import AsyncStorage from '#react-native-async-storage/async-storage';
import { useDispatch } from 'react-redux';
import {loginRequest, loginSuccess, loginFailure, logOut} from '../redux/actions/authAction';
export const storeToken = async (value) => {
try {
await AsyncStorage.setItem('token', value)
} catch (e) {
// saving error
}
}
export const getToken = async () => {
try {
const value = await AsyncStorage.getItem('token')
if(value !== null) {
return value
} else{
return null
}
} catch(e) {
// error reading value
console.log(e);
}
}
export const removeToken = async () => {
try {
await AsyncStorage.removeItem('token')
} catch(e) {
// remove error
}
console.log('token removed.')
}
export const isLoggedIn = async () => {
if(await getToken() != null){
return true
}
return false
}
export const signOut = () => {
removeToken()
}
export default {storeToken, getToken, removeToken, isLoggedIn, signOut }
LoginScreen.js
/* eslint-disable comma-dangle */
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
TouchableHighlight,
Text,
TextInput,
TouchableWithoutFeedback,
Keyboard,
ScrollView
} from 'react-native';
import {login} from '../../api/apiQueries'
import {storeToken} from '../../auth/auth'
import store from '../../redux/store';
import styles from './styles';
import { useDispatch, useSelector } from 'react-redux';
const authState = store.getState().auth;
const LogInScreen = ({route, navigation}) => {
const [userName, setUserName] = useState("")
const [password, setPassword] = useState("")
const dispatch = useDispatch()
onPressLogButton = () => {
dispatch(login(userName, password))
}
return (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<ScrollView style={styles.container}>
<View>
<Text style={styles.title}>Sign in</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="User Name"
onChangeText={text => setUserName(text)}
value={userName}
/>
</View>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="Password"
onChangeText={text => setPassword(text)}
value={password}
/>
</View>
<View style={styles.logContainer}>
<TouchableHighlight
style={styles.loginContainer}
onPress={() => onPressLogButton()}
>
<Text style={styles.logTxt}>Log in</Text>
</TouchableHighlight>
{/* <Text style={styles.orTxt}>OR</Text> */}
{/* <TouchableHighlight
style={styles.facebookContainer}
onPress={() => this.onPressFacebookButton()}
>
<Text style={styles.facebookTxt}>Facebook Login</Text>
</TouchableHighlight> */}
</View>
</View>
</ScrollView>
</TouchableWithoutFeedback>
);
}
export default LogInScreen
As discussed in the comments, the solution is to either make use of the useSelector hook, or to subscribe your component to store updates using the mapStateToProps parameter of the connect method. That way, it will run whenever the store updates through a dispatched action.
From the docs:
useSelector() will also subscribe to the Redux store, and run your
selector whenever an action is dispatched. Link
This means, for your AppNavigation.js, for example, you can change the code to:
import { useSelector } from 'react-redux';
const Navigator = () => {
const authStat = useSelector((state) => state.auth);
if(authStat.isLoading){
return(
...
Reading from the store by direct access will do just that, but it does not imply a subscription for future changes.

How to make a Search Bar using React Native with Redux

I want to make a search bar for searching items from the list using react native with redux
I have tried this :
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Text, View, FlatList, Image, ScrollView, StyleSheet, TouchableOpacity, TextInput} from 'react-native';
import { SearchBox, Spinner } from './common';
import { listShow, searchResult} from './actions';
class flatRedux extends Component {
componentWillMount() {
this.props.listShow();
}
_onSearchChange = text => {
this.props.searchResult(text)
this.props.listShow(text)
}
render() {
console.log(this.props);
return (
<View style={styles.MainContainer}>
<SearchBox
placeholder="Search..."
onChangeText={this._onSearchChange}
/>
<FlatList
data={this.props.flatlist}
ItemSeparatorComponent = {this.FlatListItemSeparator}
keyExtractor={(item, index) => index.toString()}
renderItem={({item}) =>
<Text key={item.id} style={styles.FlatListItemStyle} >
{item.cl_name} </Text>}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
search: state.searchResult.search,
flatlist: state.listShow.flatlist
};
};
export default connect(mapStateToProps, { listShow, searchResult })(flatRedux);
This is the SearchAction file
import { SEARCH_DATA } from './types'
export const searchResult = (text) => {
return {
type: SEARCH_DATA,
payload: text
};
}
And this one is SearchReducer File
import {SEARCH_DATA} from "../actions";
const INITIAL_STATE = {
search: ''
}
export default (state = INITIAL_STATE, action) => {
console.log(action);
switch(action.type) {
case SEARCH_DATA:
return {
...state,
search: action.payload
}
default:
return state;
}
}
And files who are fetching items from the localhost server are below :
flatAction.js
import axios from 'axios';
import { FLAT_DATA } from './types';
export const listShow = () => {
return (dispatch) => {
axios.post('http://192.168.48.228/reactTest/list.php')
.then((response) => {
dispatch({
type: FLAT_DATA,
payload: response.data
});
})
.catch((error) => {
console.log(error);
});
};
};
flatReducer.js
import {FLAT_DATA } from '../actions/types';
const INITIAL_STATE = {
flatlist: '',
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FLAT_DATA:
return { ...state, flatlist: action.payload };
default:
return state;
}
};
All items are fetching but 'Search' is not working for them.
'Search' is not working for them?
it's not detailed enough to give a specific answer.
To debug the workflow try changing the endpoint of listShow to search and then calling this.props.listShow(text) on input onChange handler the see if the redux part is properly connected
assuming,
All items are fetching
If it is not working need to see if your keyListners are working or not (need to see the code for searchbox), and finally, if the flatlist is rerendered (It doesn't rerender if shallow comparison fails)

Rerender component child after after state change in parent component

Hello am trying to refresh the graph after changing the value of select option but it shows the first graph and when I change the select option the state is changed but the graph didn't change I think the problem is in lifecycle component when the state changes didn't change only rendred for one time how can I fix it and thank you
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Select from "react-select";
import Graph from "../graph/Graph";
class Home extends Component {
state = {
selectedOption: null
};
handleChange = selectedOption => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
};
render() {
const { user } = this.props.auth;
const { organization } = user;
console.log(organization);
//const organization = user.organization;
console.log(user);
//let organization = user.organization[0];
const options = organization.map(org => ({
value: org.conceptPrefix,
label: org.name
}));
const { selectedOption } = this.state;
let graphObject;
if (selectedOption == null) {
graphObject = <h4>Choose Organization</h4>;
} else {
graphObject = (
<div>
<Graph org={this.state.selectedOption.value} />
</div>
);
}
return (
<div>
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
{graphObject}
</div>
);
}
}
Home.propTypes = {
auth: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph
});
export default connect(
mapStateToProps,
{}
)(Home);
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { graphGet } from "../../actions/graphActions";
import GraphImp from "./GraphImp";
class Graph extends Component {
constructor(props) {
super(props);
this.state = {
org: props.org
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.errors) {
this.setState({ errors: nextProps.errors });
}
}
componentDidMount() {
this.props.graphGet(this.props.org);
}
render() {
// {this.props.graph.graph && this.state.formSubmitted
// ? this.createList()
// : "wait Graph"}
const { graph, loading } = this.props.graph;
let graphContent;
if (graph == null || loading) {
graphContent = <h4>Loading ...</h4>;
} else {
graphContent = <GraphImp grapheData={graph} />;
}
return <div>{graphContent}</div>;
}
}
Graph.prototypes = {
graphGet: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired,
graph: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
auth: state.auth,
graph: state.graph,
errors: state.errors
});
export default connect(
mapStateToProps,
{ graphGet }
)(Graph);
There are 2 ways to achieve your goal.
First option: Implement componentDidUpdate in Graph
componentDidUpdate(prevProps) {
if(prevProps.org !== this.props.org) {
this.setState({ org: this.props.org });
this.props.graphGet(this.props.org);
}
}
Second option: Force react to fully remount&render your graph whenever you change the option by changing the key (Make sure the key is not an object/array)
<Graph key={this.state.selectedOption.value} org={this.state.selectedOption.value} />

Cannot read property onSubmit of undefined

Trying to pass down a function as a prop:
import {
...
} from "react-native";
import {
...
} from "../actions/events";
import { isLoading, isLoadingCredentials, hasErrored } from "../actions/loader";
class HomeScreen extends React.PureComponent {
static navigationOptions = {
title: "",
headerMode: "screen",
header: null
};
constructor(props) {
super(props);
}
componentWillMount() {
const {
navigation: { navigate },
setCredentials
} = this.props;
}
onSubmit(e) {
...
}
render() {
const {
...
} = this.props;
if (!authenticated) {
return <Login onPress={this.onAuthenticate} />;
}
return (
<View
style={styles.container}
onLayout={this.onLayout.bind(this)}
>
<Pickers
onSubmit={this.onSubmit}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
categories: state.fetchCategories,
isLoading: state.isLoading,
hasErrored: state.hasErrored,
credentials: state.setCredentials,
isLoadingCredentials: state.isLoadingCredentials,
authenticated: state.authenticated
};
};
const mapDispatchToProps = {
....
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(HomeScreen);
Child:
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Picker from "./common/Picker";
import {
..
} from "react-native";
import {
...
} from "../actions/events";
export class Pickers extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
const {
credentials: { year, group, student, showStudent }
} = this.props;
return (
<View>
<TouchableHighlight
onClick={() => this.props.onSubmit()}
>
<Text style={styles.buttonText}> Submit</Text>
</TouchableHighlight>
</View>
);
}
}
const mapStateToProps = state => {
return {
credentials: state.setCredentials
};
};
const mapDispatchToProps = {
...
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Pickers);
What am I doing wrong?
Change it to:
render() {
const { onSubmit } = this.props;
return <TouchableHighlight
onClick={this.props.onSubmit}
/>
}
Your onSubmit method is being passed from the Props not on the class.
So it is undefined.
Hence You need to call this.props.onSubmit while submitting.
It will call the function which is passed as the prop from your Parent Component.
And yeah now you should switch to the ES6 syntax.
class HomeScreen extends React.PureComponent {
static navigationOptions = {
title: "",
headerMode: "screen",
header: null
};
constructor(props) {
super(props);
}
onSubmit= (e) => {
...
}
render(){
<Pickers onSubmit={this.onSubmit}/>}

Resources