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);
}
Related
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.
I'm struggling to get a basic api call setup with redux and axios in React Native.
This is my reducer index.js
import { combineReducers } from 'redux'
import LibaryReducer from './LibraryReducer'
import ImportLibraryReducer from './ImportLibraryReducer'
let defaultState = {
card: null
}
const mainReducer = (state = defaultState, action) => {
if(action.type === "CHANGE_CARDS") {
return {
...state,
card: action.card
}
} else {
return {
...state
}
}
}
export default mainReducer
This is my action index.js
import axios from "axios"
export function loadCards(){
return(dispatch)=>{
return axios.get('http://localhost:4000/reports')
.then(response => {
dispatch(changeCards(response.data))
})
}
}
export function changeCards(cards) {
return{
type: "CHANGE_CARDS",
card: card
}
}
This is my app.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import MainPage from './components/MainPage'
import { Header } from "native-base"
import Card from './components/Card'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import reducers from './reducers'
const store = createStore(reducers, applyMiddleware(thunk))
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<Provider store={store}>
<View>
<Header ><Text>hello</Text></Header>
<Card />
</View>
</Provider>
);
}
}
And, finally, this is where I'm trying to retrieve the data from the api call:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import {Collapse,CollapseHeader, CollapseBody, AccordionList} from 'accordion-collapse-react-native';
import { connect } from 'react-redux'
import * as actions from '../actions'
class Card extends Component {
render() {
const titleStyle = {
backgroundColor: '#edeeef',
fontWeight: "bold",
color: '#454647',
fontSize: 16,
left: 8,
fontFamily: 'Ionicons',
top: 10
}
const descMarkStyle = {
left: 8,
top: 4,
fontFamily: 'Ionicons',
color: '#454647',
fontSize: 16
}
console.log('in the render', this.props)
return (
<View>
<Collapse >
<CollapseHeader>
<View
style={{
backgroundColor: '#edeeef',
height: 38,
postion: 'absolute',
borderBottomWidth: .5,
borderBottomColor: '#black'
}}
>
<Text style={titleStyle}>
test
</Text>
</View>
</CollapseHeader>
<CollapseBody>
<Text style={descMarkStyle}>test</Text>
<Text style={descMarkStyle}>test</Text>
</CollapseBody>
</Collapse>
</View>
);
}
}
function mapStateToProps(state) {
return {
state
};
}
export default connect(mapStateToProps)(Card);
When I try to console log this.props in the component above, I get the default state of card: null without the api running: https://imgur.com/a/acB40KU
I'm new to redux, and I feel like there is something obvious that I'm missing.
You should trigger your action in the componentDidMount lifecycle method in your Card component. Also, you can destructure your actions in your imports and in your connect.
import { loadCards } from '../actions'
class Card extends Component {
componentDidMount() {
this.props.loadCards()
}
And in connect:
export default connect(mapStateToProps, { loadCards })(Card);
Also in the changeCards action:
card: cards
Here is how to set up axios with redux hooks and react-native in 4 steps:
source code: https://github.com/trackmystories/Redux-hooks-counter-app-with-axios.
Step 1:
create an actions.js file:
actions.js
export const TOTAL_COUNT = "TOTAL_COUNT";
export const totalCount = (data) => ({
type: TOTAL_COUNT,
data,
});
Step 2:
define and combine your reducers:
reducer.js
import { combineReducers } from "redux";
import { TOTAL_COUNT } from "./actions";
let dataState = { data: [] };
const total_counts = (state = dataState, action) => {
switch (action.type) {
case TOTAL_COUNT:
return { ...state, data: action.data };
default:
return state;
}
};
const counter = (state = 0, action) => {
switch (action.type) {
case "ADD":
return state + 1;
case "SUBTRACT":
return state - 1;
default:
return state;
}
};
const rootReducer = combineReducers({
counter,
total_counts,
});
export default rootReducer;
Step 3
create an axios get request and put request as defined in the example below and dispatch and get data.
With hooks you don't need to use connect mapStateToProps and dispatchStateToProps with redux hooks instead use { useDispatch, useSelector }.
We can pass the actions "ADD" and "SUBTRACT" inside of the button directly, without defining an action.js file.
CounterComponent.js
import React, { useEffect, useState } from "react";
import { StyleSheet, Text, View, ActivityIndicator } from "react-native";
import ActionButton from "./ActionButton";
import SubmitButton from "./SubmitButton";
import { useDispatch, useSelector } from "react-redux";
import axios from "axios";
import { totalCount } from "../actions";
export default function CounterComponent() {
const dispatch = useDispatch();
const [isFetching, setIsFetching] = useState(false);
const total_counts = useSelector((state) => state.total_counts);
const counter = useSelector((state) => state.counter);
const { data } = total_counts;
useEffect(() => getTotalCount(), []);
const getTotalCount = () => {
setIsFetching(true);
let url = "https://url.firebaseio.com<name>.json";
axios
.get(url)
.then((res) => res.data)
.then((data) => dispatch(totalCount(data)))
.catch((error) => alert(error.message))
.finally(() => setIsFetching(false));
};
const onSubmit = (counterState) => {
let url = "https://url.firebaseio.com<name>.json";
axios.put(url, counterState).then((response) => {
console.log(response);
});
};
return (
<View>
<ActionButton
onPress={() =>
dispatch({
type: "SUBTRACT",
})
}
title="subtract"
/>
<View>
{isFetching ? (
<ActivityIndicator />
) : (
<View>
<Text>
Current state:
{data.counter ? data.counter : counter}
</Text>
</View>
)}
</View>
<ActionButton
onPress={() =>
dispatch({
type: "ADD",
})
}
title="add"
/>
<SubmitButton
onPress={onSubmit({
counterState: counter,
})}
title="Submit"
/>
</View>
);
}
Step 4:
Lastly link your RootReducer to the createStore and pass it to the Provider.
import React from "react";
import { Text, View } from "react-native";
import { Provider } from "react-redux";
import { createStore } from "redux";
import CounterComponent from "./src/components/CounterComponent";
import rootReducer from "./src/reducer";
const store = createStore(rootReducer);
export default function App() {
return (
<View>
<Text>Counter example with Redux Hooks and Axios</Text>
<Provider store={store}>
<CounterComponent />
</Provider>
</View>
);
}
Before Calling createReduxContainer please call createReactNavigationReduxMiddleware so that we know when to trigger your listener
I am trying to integrate react-navigation v3 with redux and this above problem is showing on my simulator
This is a tested sample of react-navigation(v3) and Redux. Here the react-navigation is integrated into Redux. However, I declined to use this method anymore. I believe integrating is not necessary when it is going to make things more complicated.
App.JS file :
import React, {Component} from 'react';
import { Provider } from "react-redux";
import { createStore, applyMiddleware, combineReducers } from "redux";
import { createStackNavigator } from "react-navigation";
import {
reduxifyNavigator,
createReactNavigationReduxMiddleware,
createNavigationReducer
} from "react-navigation-redux-helpers";
import MainScreen from "./Screens/MainScreen";
import SignUpScreen from "./Screens/SignUpScreen";
import rootReducer from "./src/rootReducer";
import ReduxNavigation from "./src/ReduxNavigation";
import { Directions } from 'react-native-gesture-handler';
const AppNavigator = createStackNavigator(
{
// LoginScreen: { screen: LoginScreen },
MainScreen: { screen: MainScreen },
SignUpScreen: { screen: SignUpScreen }
},
{
initialRouteName: "SignUpScreen"
}
);
const navReducer = createNavigationReducer(AppNavigator);
const appReducer = combineReducers({
nav: navReducer,
app: rootReducer
});
const middleware = createReactNavigationReduxMiddleware(
"root",
state => state.nav
);
export const AppNav = reduxifyNavigator(AppNavigator, "root");
const store = createStore(appReducer, applyMiddleware(middleware));
type Props = {};
export default class App extends Component<Props> {
render() {
return (
<Provider store={store}>
<ReduxNavigation />
</Provider>
);
}
}
ReduxNavigation.js :
import React from "react";
import { BackHandler } from "react-native";
import { connect } from "react-redux";
import { NavigationActions } from "react-navigation";
import { AppNav } from "../App";
class ReduxNavigation extends React.Component {
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.onBackPress);
}
componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.onBackPress);
}
onBackPress = () => {
const { nav, dispatch } = this.props;
if (nav.index === 0) {
return false;
}
dispatch(NavigationActions.back());
return true;
};
render() {
const { nav, dispatch } = this.props;
return <AppNav state={nav} dispatch={dispatch} />;
}
}
const mapStateToProps = state => ({
nav: state.nav
});
export default connect(mapStateToProps)(ReduxNavigation);
this code also support back button feature for Android devices.
How can I get access to props of a root component?
import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import cartReducer from './store/cartReducer'
import AddToCart from './cart/addToCart.jsx'
const store = createStore(cartReducer);
render(<Provider store={store}><AddToCart clicked={this.props.onIncrementCounter} />
</Provider>, document.getElementById('addToCart'));
Is it possible? Because I have an error: " Cannot read property 'props' of undefined".
I am new in React.
Here is AddToCart component
import React, { Component } from 'react';
import { connect } from 'react-redux';
class AddToCart extends Component {
constructor(props) {
super(props);
}
}
const mapStateToProps = state => {
return {
items: state.cartItems,
count: state.cartItemCount
};
};
const mapDispatchToProps = dispatch => {
return {
onIncrementCounter: () => dispatch({ type: 'INCREMENT' })
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AddToCart);
you got this error " Cannot read property 'props' of undefined" because in that part of your code : <AddToCart clicked={this.props.onIncrementCounter} />
this is referencing anything
you can try this approach:
import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import cartReducer from './store/cartReducer'
import AddToCart from './cart/addToCart.jsx'
const store = createStore(cartReducer);
render(<Provider store={store}><AddToCart/>
</Provider>, document.getElementById('addToCart'));
AddToCart.jsx
import React, { Component } from 'react';
import { connect } from 'react-redux';
class AddToCart extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div onClick={this.props.onIncrementCounter}>
Increment
</div>
)
}
}
const mapStateToProps = state => {
return {
items: state.cartItems,
count: state.cartItemCount
};
};
const mapDispatchToProps = dispatch => {
return {
onIncrementCounter: () => dispatch({ type: 'INCREMENT' })
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AddToCart);
So I'm new to Redux and am having problems accessing the STATE. When I add the constructor() method into my Component (Welcome) I get a Cannot find module /Welcome error. I'll paste my code below as I'm really struggling!
I'm simply trying to print out the text state!
Components/Welcome/index.jsx
import React, { Text, View, StyleSheet } from 'react-native';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
const Welcome = React.createClass({ //() => (
constructor() {
super(props, context);
console.log("context", this.context);
}
render () {
// state = super(props);
// console.log(state);
return (
<View style={ styles.container }>
<Text style={ styles.welcome }>
React Native Redux Starter Kit
</Text>
<Text style={ styles.instructions }>
{ this.props.text }
</Text>
<Text style={ styles.instructions }>
Press Cmd+R to reload,{'\n'}
Cmd+D or shake for dev menu
</Text>
</View>
)
}
// );
});
containers/app.jsx:
import React, { Component } from 'react-native';
import { Provider } from 'react-redux';
import configureStore from 'configStore';
import ExNavigator from '#exponent/react-native-navigator';
import routes from 'routes';
export default class App extends Component{
/**
* Render
*
* #return {jsx} Render <Provider /> component
*/
render(){
return (
<Provider store={ configureStore() }>
<ExNavigator
initialRoute={ routes.getHomeRoute() }
style={{ flex: 1 }}
sceneStyle={{ paddingTop: 64 }} />
</Provider>
);
}
}
reducers/bar.jsx:
This is setting the state of text = "testssters". I'm trying to print this out in my component.
import Immutable from 'immutable';
import { BAR } from 'constants/ActionTypes';
const initialState = Immutable.fromJS({
text: "hi"
});
export default function bar(state = {
text: "testssters",
}, action)
{
switch(action.type) {
case BAR:
state = state;
break;
}
return state;
}
routes.jsx:
const routes = {};
/**
* Homepage
*
*/
// Shows the homepage (Welcome/index.js)
routes.getHomeRoute = () => ({
getSceneClass() {
return require('Welcome/').default;
},
getTitle() {
return 'Welcome';
}
});
configStore.jsx:
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from 'reducers/';
const createStoreWithMiddleware = compose(
applyMiddleware(thunk)
// Add remote-redux-devtools
)(createStore);
export default function configureStore() {
return createStoreWithMiddleware(combineReducers(reducers));
}
React.createClass() expects an object and not a function.
If you're using createClass(), in order to set an initial state use getInitialState.
const Welcome = React.createClass({
getInitialState: function () {
return { myState: 1 };
},
render: function () {}
});
If you want to use ES6 classes then the same would look like this
class Welcome extends React.Component {
constructor() {
super();
this.state = { myState: 1 };
}
render() {
}
}