Redux - mapStateToProps not working (React-Native) - reactjs

I am learning Redux and can't seem to get state to display in my home page. I get the error: 'undefined is not an object, evaluating this.props.titles.allTitles. The error is located in Home created by connect function' Here is the code, let me know if you need any other files. Thank you. I am adding more text to comply with stack overflow, thank you for your help.
home:
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import { connect } from 'react-redux'
class Home extends React.Component {
render() {
return (
<View>
<Text>Redux Test</Text>
<Button
title='+ new list'
onPress={() =>
this.props.navigation.navigate('New List')
}
/>
<Text>{this.props.titles.allTitles.length}</Text>
</View>
)
}
}
const mapStateToProps = (state) => {
const { titles } = state
return { titles }
};
export default connect(mapStateToProps) (Home);
```
reducer:
```
import { combineReducers } from 'redux';
const INITIAL_STATE = {
allTitles: []
};
const tagReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'NEW_LIST':
return {
...state,
allTitles: [...state.allTitles, action.payload.title]
}
default:
return state;
}
};
const reducers = combineReducers({
tagReducer
})
export default reducers;
```
import React from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
import { connect } from 'react-redux';
import { newList } from '../store/tagActions';
class List extends React.Component {
constructor(props){
super(props);
this.state = {
title: ''
}
}
render() {
return (
<View style={styles.container}>
<TextInput
style={styles.title}
placeholder='add Title..'
onChangeText={text => this.setState( {title: text} ) }
/>
<Button
title='done'
onPress={() => {
this.props.newList(this.state.title)
}
}
/>
<Text>{this.state.title}</Text>
</View>
)
}
}
const mapStateToProps = (state) => {
const { allTitles } = state
return { allTitles }
};
export default connect(mapStateToProps, { newList }) (List);

In your reducer, you have the following -
allTitles: [...state.allTitles, action.payload.title]
When you do, I don't see title in the redux state.
const mapStateToProps = (state) => {
const { titles } = state
return { titles }
};
You need to do
const mapStateToProps = (state) => {
const { allTitles } = state
return { allTitles }
};
Then do {this.props.allTitles.length} inside the render statement

Getting Redux setup can be pretty tricky in my opinion. After taking a look at your code I created a small React-Native project and setup Redux as closely as possibly to what you described in your question. Hopefully my answer helps. Please note that all three the files in my answer (App.js, Home.js, & titleReducer.js) are contained in the same directory.
App.js
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import titleReducer from './titleReducer';
// React-Redux
import {
createStore,
combineReducers,
} from 'redux';
import {
connect,
Provider
} from 'react-redux';
// Import Components (Screens)
import Home from './Home';
// Intialize Redux Store
const rootReducer = combineReducers({
titles: titleReducer
});
const store = createStore(rootReducer);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<Home/>
</Provider>
)
}
}
export default App;
titleReducer.js
const initialState = {
allTitles: [],
};
const titleReducer = (state, action) => {
// check for state undefined to prevent
// redux from crashing app on load
if (typeof state === 'undefined') {
return {...initialState};
}
switch (action.type) {
case 'ADD_TITLE':
const newState = {...state};
const newTitle = action.payload;
newState.allTitles.push(newTitle);
return newState;
default:
return {...state};
}
// If none of the conditions above are true,
// simply return a copy of the current state
return {...state};
};
export default titleReducer;
Home.js
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
import {
connect,
Provider
} from 'react-redux';
function randomTitle() {
return Math.random().toString();
}
class Home extends React.Component {
render() {
return (
<View>
<Text>Redux Test</Text>
<Button
title="Add Title"
onPress={ () => this.props.addTitle(randomTitle()) }/>
<Text>{this.props.titles.allTitles.length}</Text>
</View>
)
}
}
const mapDispatchToProps = dispatch => {
return {
addTitle: (payload) => dispatch({type: 'ADD_TITLE', payload: payload}),
};
};
const mapStateToProps = (state) => {
return {
titles: state.titles,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);

I think you've forgot to define a store for your app. Go to your root class (app.js or something) and define your reducers to your store:
const store = createStore(tagReducer)
or if you have multiple reducers you can combine them in one line:
const store = createStore(combineReducers({
tag: tagReducer,
someOther: otherReducer
}));
Hope that it fixes your problem.

Related

Rendering text on a page from the redux-store

I have a text input in React-Native component page and I send the input text to the store.
I am trying to render the input text on the page from the redux store but it will not display.
Below are my component and my redux store. Am I not calling the value correctly in my component with this.props.email?
COMPONENT
import React, { Component} from 'react';
import { StyleSheet, View, Button, Text, StatusBar, TextInput, TouchableOpacity, Image } from 'react-native';
import { connect } from 'react-redux';
class Counter extends Component {
email(value) {
this.props.email(value);
}
toggleCounterHandler() {}
render() {
return (
<View>
<View ><Text style={styles.welcometext}>{this.props.email}</Text></View>
<View>
<TextInput
style={styles.input}
onChangeText = {(value) => this.props.email(value)}
value={this.props.value}
/>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
value: state.value
};
}
const mapDispatchToProps = dispatch => {
return {
email: (value) => dispatch({
type: 'email',
payload: value
})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);
REDUX-STORE
import React from 'react';
import { createStore } from 'redux';
const counterReducer = (state = { email:'email' }, action) => {
if (action.type === 'email') {
return {
email: action.payload,
};
}
return state;
};
const store = createStore(counterReducer);
export default store;

Add a load indicator based on the Redux state - react native

How can I add a charging indicator based on the Redux status?
I need to place a loading screen while sending the data.
charging indicator component
import React from 'react';
import { StyleSheet } from 'react-native';
import AnimatedLoader from "react-native-animated-loader";
import {connect} from 'react-redux'
class Loader extends React.Component {
constructor(props) {
super(props);
this.state = {visible: false };
}
// componentDidMount() {
// setInterval(() => {
// this.setState({
// visible: !this.state.visible
// });
// }, 2000);
// }
render() {
const { visible } = this.props;
if (!visible) return outVisible();
return (
<AnimatedLoader
visible={visible}
overlayColor="rgba(255,255,255,0.75)"
source={require("./loader.json")}
animationStyle={styles.lottie}
speed={1}
>
<Text>Carregando...</Text>
</AnimatedLoader>
);
}
}
const styles = StyleSheet.create({
lottie: {
width: 200,
height: 200
}
});
const mapStateToProps = (state) => ({visible: state.visible});
const mapDispatchToProps = dispatch => {
return {outVisible: () => dispatch(setVisible({visible: false}))}
}
export default connect(mapStateToProps,mapDispatchToProps)(Loader)
Action
import { SET_VISIBLE } from './actionsTypes'
export const setVisible = visible => {
return {
type: SET_VISIBLE,
payload: visible
}
}
Reducer
import { SET_VISIBLE } from '../actions/actionsTypes'
const initialState = {
visible: false
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case SET_VISIBLE:
return {
...state,
visible: action.payload.visible
};
default:
return state;
}
}
export default reducer
Store Config
import {
createStore,
combineReducers,
compose,
applyMiddleware
} from 'redux'
import thunk from 'redux-thunk'
import postReducer from './reducers/post'
import userReducer from './reducers/user'
import messageReducer from './reducers/message'
import loadingReducer from './reducers/loading'
const reducers = combineReducers({
user: userReducer,
post: postReducer,
message: messageReducer,
visible: loadingReducer
})
const storeConfig = () => {
return createStore(reducers, compose(applyMiddleware(thunk)))
}
export default storeConfig
actions types
export const USER_LOGGED_IN = 'USER_LOGGED_IN'
export const USER_LOGGED_OUT = 'USER_LOGGED_OUT'
export const SET_MESSAGE = 'SET_MESSAGE'
export const LOADING_USER = 'LOADING_USER'
export const USER_LOADED = 'USER_LOADED'
export const CREATING_POST = 'CREATING_POST'
export const POST_CREATED = 'POST_CREATED'
export const SET_POSTS = 'SET_POSTS'
export const SET_VISIBLE = 'SET_VISIBLE'
app.js
import React, { Component } from 'react'
import { Alert } from 'react-native'
import { connect } from 'react-redux'
import Routes from "./routes";
import { setMessage } from './store/actions/message'
class App extends Component {
componentDidUpdate = () => {
if(this.props.text && this.props.text.toString().trim())
{
Alert.alert(this.props.title || 'Mensagem',this.props.text.toString())
this.props.clearMessage()
}
}
render() {
return (
<Routes />
)
}
}
const mapStateToProps = ({ message}) => {
return {
title: message.title,
text: message.text,
}
}
const mapDispatchToProps = dispatch => {
return {
clearMessage: () => dispatch(setMessage({ title: '', text: '' }))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Right after the data is sent to the API, I need to return a load indicator to the user, until that data is stored.

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)

react HOC and losing reference to this

I'm trying to understand and use HOC with react and redux. I have the following setup. I will have multiple buttons using the hoc and passing in their own onClick. and would like to know:
a. Is this a good usage of the HOC pattern?
b. with this setup inside of the render function of FooterButton, this references DesignatedWorkerGlobalScope and iconHeigh, iconWidth, and iconColor inside HomeButton become either undefined or unexpected values.
Any advice would be appreciated.
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { getColors, getStylesheet} from "../../styles/StyleManager";
const FooterButtonWrapper = (FooterButtonWrapped, onClick) => {
return class FooterButton extends React.Component {
constructor(props) {
super(props);
this.state = {
Theme: getStylesheet(),
colors: getColors()
}
}
_onClick = () => {
onClick(this.props.dispatch);
}
render() {
const { Theme, colors } = this.state;
return(
<TouchableOpacity onPress={this._onClick}>
<FooterButtonWrapped iconWidth={15} iconHeight={15} iconColor={"white"}/>
</TouchableOpacity>
)
}
}
}
const mapStateToProps = (state, ownProps) => ({});
const composeFooterButton = compose(
connect(mapStateToProps),
FooterButtonWrapper
);
export default composeFooterButton;
and then a button that uses it:
import React from 'react';
import { View, Text } from 'react-native';
import { push as gotoRoute } from 'react-router-redux';
import { FooterButtonWrapper } from './';
import { Home } from '../../assets';
const HomeButton = ({iconHeight, iconWidth, iconColor}) => (
<View>
<Home width={ iconWidth } height={ iconHeight } color={ iconColor }/>
<View><Text>Home</Text></View>
</View>
);
const _onClick = dispatch => {
dispatch( gotoRoute( '/' ));
}
export default FooterButtonWrapper(HomeButton, _onClick);
It happens so, as you use
const composeFooterButton = compose(
connect(mapStateToProps),
FooterButtonWrapper
);
export default composeFooterButton;
for HOC function instead of Component.
Both compose and connect can wrap your Component.
So your HOC could be:
import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { getColors, getStylesheet } from '../../styles/StyleManager';
export default function FooterButtonWrapper(FooterButtonWrapped, onClick) {
class FooterButton extends React.Component {
constructor(props) {
super(props);
this.state = {
Theme: getStylesheet(),
colors: getColors(),
};
}
_onClick = () => {
onClick(this.props.dispatch);
};
render() {
const { Theme, colors } = this.state;
return (
<TouchableOpacity onPress={this._onClick}>
<FooterButtonWrapped
iconWidth={15}
iconHeight={15}
iconColor={'white'}
/>
</TouchableOpacity>
);
}
}
const mapStateToProps = (state, ownProps) => ({});
return connect(mapStateToProps)(FooterButton);
}

action does not modify state

I am trying to add user metadata to my store when mounting a screen. However, when I send the action to the reducer, the store is not modified.
I would expect props after sending the action to be as follows:
{addUserMetaData: ƒ addUserMetaData(user_object),
user: {firestore_doc: {name: "Joe"}}
}
What am i missing here?
To reproduce, react-native-init mwe then add the following code. I've added an image of the app logs below.
App.js
import React, { Component} from 'react';
import { View } from 'react-native';
import Screen from './src/screen';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const userReducer = function userReducer(state = {}, action) {
console.log('action', action);
switch (action.type) {
case "ADD_USER_METADATA":
return { ...state, firestore_doc: action.payload };
default:
return { ...state };
}
};
const store = createStore(userReducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<View>
<Screen />
</View>
</Provider>
);
}
};
src/screen.js
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
const addUserMetaData = (user) => ({
type: "ADD_USER_METADATA",
payload: user
})
class Screen extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const user = { name: "Joe" };
console.log('props', this.props);
this.props.dispatch(addUserMetaData(user));
console.log('props after action', this.props);
}
render() {
return (
<View>
<Text>Welcome to react native</Text>
</View>
)
}
}
const mapStateToProps = state => {
return { user: state };
};
export default connect(mapStateToProps)(Screen);
Fixed https://snack.expo.io/#janithar/c3RhY2
Lines I changed
return { ...state, firestore_doc: action.payload };
Please added state.firestore_doc instead of state because in reducer action.payload assign the data in firestore_doc state so you are not getting data from state.user
const mapStateToProps = state => {
return { user: state.firestore_doc };
};

Resources