i want to start with redux and the connect function. When i use the useSelector hook, everything is fine, so my redux-state works. But with connect and the maptStateToProps function not. The number variable is undefined.
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { connect } from 'react-redux'
import { incrementNumber } from '../actions/exampleAction'
const Incrementor = ({ number, increment }) => {
return (
<View>
<Text onPress={increment}>{number}</Text>
</View>
)
}
export default connect(mapStateToProps, mapDispatchToProps)(Incrementor)
const styles = StyleSheet.create({})
const mapStateToProps = (state) => {
return {
number: state.exampleReducer.number,
}
};
const mapDispatchToProps = (dispatch) => {
return {
increment: () => dispatch(incrementNumber()),
}
};
Here is the working solution with react Hooks:
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { useSelector , useDispatch} from 'react-redux'
import { connect } from 'react-redux'
import { incrementNumber } from '../actions/exampleAction'
const Incrementor = () => {
const dispatch = useDispatch();
const number = useSelector(state => state.exampleReducer.number)
return (
<View>
<Text onPress={() => {dispatch({type: "INCREMENT"})}}>{number}</Text>
</View>
)
}
const styles = StyleSheet.create({})
export default Incrementor
Here is my reducer function:
import { INCREMENT } from "../actions/exampleAction";
let initialState = { number: 0 };
export default function reducer (state = initialState, action) {
switch (action.type) {
case INCREMENT:
return { ...state, number: state.number+1, };
default:
return state;
}
}
And here the action:
export const INCREMENT = "INCREMENT";
export const incrementNumber = () => {
return {
type: INCREMENT,
}
};
Here i combine my reducers:
import { combineReducers } from 'redux'
import exampleReducer from "./exampleReducer"
const reducers = combineReducers({
exampleReducer,
});
export default reducers;
And here is my App.js
import React from 'react';
import { StyleSheet, View } from 'react-native'
import { createStore, combineReducers } from 'redux'
import { Provider } from 'react-redux';
import Incrementor from './src/components/Incrementor';
import reducer from "./src/reducers/index"
let store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
export default function App() {
return (
<Provider store={store}>
<View style={styles.container}>
<Incrementor />
</View>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
first of all, check you've imported incrementNumber() from actions, after that you have to use connect function after defining all mapStateToProps & mapDispatchToProps, your code will be like this
const mapStateToProps = (state) => ({
number: state
})
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch(incrementNumber())
})
export default connect(mapStateToProps, mapDispatchToProps)(Incrementor)
Related
I am building a React Native app and I am using Redux. Redux Persist is working fine but when I add Redux Thunk, the store's state is not persisted. The value of the state.test variable does change to the one fetched from the api but when I restart the app the value is again equal to the initial value. I have created a simple example to reproduce this behaviour. The example consists of 5 files and I have also created a git repository with the sample code https://github.com/JenteBeerten/reduxPersistProblem
Store/Actions/test.js
export const SET_TEST = 'SET_TEST';
export const fetchTest = () => {
return async dispatch => {
fetch('http://dummy.restapiexample.com/api/v1/employee/1',{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then((response) => response.json())
.then((json) => {
dispatch({
type: SET_TEST, test: json.data.employee_name
});
})
.catch((error) => {
console.error(error);
});
}
};
export const setTest = (test) => {
return { type: SET_TEST, test: test };
};
Store/reducers/test.js
import { SET_TEST } from "../actions/test";
const initialState = {
test: 'testInitialValue'
}
const testReducer = (state = initialState, action) => {
switch (action.type) {
case SET_TEST:
state.test = action.test;
return state;
default:
return state;
}
}
export default testReducer;
Store/configureStore.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import AsyncStorage from '#react-native-community/async-storage';
import ReduxThunk from 'redux-thunk';
import testReducer from './reducers/test';
const rootReducer = combineReducers({
test: testReducer
});
const persistConfig = {
key: 'root',
storage: AsyncStorage
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default () => {
let store = createStore(
persistedReducer,
applyMiddleware(ReduxThunk)
);
let persistor = persistStore(store)
return { store, persistor }
}
App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import returnStoreAndPersistor from './store/configureStore';
import TestComponent from './TestComponent';
const {store, persistor} = returnStoreAndPersistor();
export default function App() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<View style={styles.container}>
<TestComponent></TestComponent>
</View>
</PersistGate>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
TestComponent.js
import React, { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import * as testActions from './store/actions/test';
const TestComponent = props => {
const dispatch = useDispatch();
var test = useSelector(state => state.test.test);
console.log('test='+test);
useEffect(() => {
dispatch(testActions.fetchTest());
}, [dispatch]);
return (
<View style={styles.container}>
<Text> Test is {test} </Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}
});
export default TestComponent;
In reducer you have to return copy of previous state as
import { SET_TEST } from "../actions/test";
const initialState = {
test: 'testInitialValue'
}
const testReducer = (state = initialState, action) => {
switch (action.type) {
case SET_TEST:
return {
...state,
test: action.test;
}
default:
return state;
}
}
export default testReducer;
Now it will persist.
thanks
i'm currently working on an app using react-native for the first time,
but i'm struggling to add redux.
i get this error TypeError: Cannot read property 'getState' of undefined and i don't know how to fix it.
this my code :
import React from "react";
import Home from "./home";
import { store } from "./redux/store";
import { Provider } from "react-redux";
/* #flow */
import { View, StyleSheet } from "react-native";
class App extends React.Component {
render() {
return (
<Provider store={store}>
<View style={styles.container}>
<Home />
</View>
</Provider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
store.js :
import { applyMiddleware, createStore } from "redux";
import logger from "redux-logger";
import reducer from "./reducer";
const middlewares = [logger];
const store = createStore(reducer, applyMiddleware(...middlewares));
export default store;
reducer.js :
import { combineReducers } from "redux";
import mapReducer from "../redux/maps/maps-reducer";
export default combineReducers({
map: mapReducer
});
maps-action.js:
import MAPSActionTypes from './maps-action-types';
export const currentlocation = () => ({
console.log(${location});
type : MAPSActionTypes.GET_CURRENT_LOCATION
});
maps-reducer.js:
import MAPSActionTypes from "./mapsactiontypes";
const INITIAL_STATE = {
location: {}
};
const mapReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case MAPSActionTypes.GET_CURRENT_LOCATION:
return {
...state,
location: action.payload
};
default:
return state;
}
}
export default mapReducer;
home.js:
import React from 'react';
import {
StyleSheet,
View,
Text,
} from 'react-native';
import {connect} from 'react-redux'
const Home = (props) => {
return (
<View style={styles.container}>
<Text>welcome</Text>
<Text>{props.location}</Text>
</View>
);
};
const styles = StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center'
}
});
const mapStateToProps = (state) => {
return {
location: state.map.location
}
}
export default connect(mapStateToProps)(Home);
i'm all ears from more clarification or more details.
i will be very thankful if there's anyone who can help me to fix that problem.
The problem is you export default in store.js file.
export default store;
But in App you import it with {}. So the store you imported is undefined.
import { store } from "./redux/store";
The correct import is
import store from "./redux/store";
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>
);
}
I am trying to refresh a react component state based on the props.
I have this file which is the main a child component for a screen:
RoomsList.js
import React from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import {connect} from "react-redux";
import {getRooms} from "../../store/actions";
import RoomIcon from "../RoomIcon/RoomIcon";
class RoomList extends React.Component {
componentDidMount() {
this.props.onGetRooms();
}
renderRooms() {
return this.props.rooms.map(room => {
return (
<RoomIcon key={room.id} room={room} />
)
});
}
render() {
return (
<View style={styles.container}>
{ this.props.rooms.length ? this.renderRooms() : <ActivityIndicator /> }
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
width: '100%',
justifyContent: 'space-between',
flexWrap: 'wrap',
}
});
const mapStateToProps = state => {
return {
rooms: state.rooms.rooms
}
};
const mapDispatchToProps = dispatch => {
return {
onGetRooms: () => dispatch(getRooms())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(RoomList);
Rooms Reducer
import { SET_ROOMS } from '../actions/actionTypes';
const initialState = {
rooms: []
};
const roomsReducer = (state = initialState, action) => {
switch (action.type) {
case SET_ROOMS:
return {
...state,
rooms: action.rooms
};
default:
return state;
}
};
export default roomsReducer;
When the state is getting updated within the mapStateToProps function, which I can confirm it is doing as I put a console log inside of there to get the rooms and the object is the updated object.
However, it appears the the render isn't actually getting updated although the state is getting updated. I have tried to do a componentWillReceiveProps and assign the state but the state is never actually updated within here.
Rooms Action
import {SET_ROOMS} from './actionTypes';
import store from "../index";
export const getRooms = () => {
return dispatch => {
fetch("http://localhost/rooms").catch(err => {
console.log(err)
}).then(res => {
res.json();
}).then(parsedRes => {
dispatch(setRooms(parsedRes));
})
}
};
export const addRoom = (roomName, roomDescription) => {
const rooms = store.getState().rooms.rooms;
const room = {
room_name: roomName,
room_description: roomDescription
};
return dispatch => {
fetch("http://localhost/rooms", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(room)
}).catch(err => {
console.log(err)
}).then(res => res.json())
.then(parsedRes => {
rooms.push(parsedRes);
dispatch(setRooms(rooms));
})
}
};
export const setRooms = rooms => {
return {
type: SET_ROOMS,
rooms: rooms
}
};
Initialising Redux Store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
const composeEnhancers = compose;
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
export default store;
Initializing Reducers
import {combineReducers} from "redux";
import lightsReducer from "./lights";
import roomsReducer from "./rooms";
import modalsReducer from "./modals";
export default combineReducers({
lights: lightsReducer,
rooms: roomsReducer,
modals: modalsReducer
});
My Login Component:
import React, { Component } from 'react'
import {
View,
Text,
StyleSheet,
TextInput,
TouchableOpacity
} from 'react-native'
export class Login extends Component {
onChangeText = (key, value) => {
this.props.setUserDetails({
...this.props.user,
[key]: value
})
}
render() {
const { user, onSubmitForm } = this.props
console.log(this.props.user) // user undefined here
return (
<View style={styles.container}>
<Text style={styles.heading}>Login</Text>
<TextInput
placeholder='Email'
onChangeText={val => this.onChangeText('email', val)}
style={styles.input}
value={user.email}
/>
<TextInput
placeholder='Password'
onChangeText={val => this.onChangeText('password', val)}
style={styles.input}
value={user.password}
/>
<TouchableOpacity onPress={() => onSubmitForm(user)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Submit</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
My Login container:
import React, { Component } from 'react'
import { setUserDetails } from '../actions/loginActions'
import { connect } from 'react-redux'
import loginReducer from '../reducers/loginReducer'
import { Login } from '../components/login'
export class LoginContainer extends Component {
onSubmitForm = () => {
// Checking Validations
const { name, email } = this.props;
if (!name || !email) {
alert('Please fill the form')
console.log(this.props.user) // It says undefined
return;
}
}
render () {
return (
<Login
user={this.props.user}
setUserDetails={this.props.setUserDetails}
onSubmitForm={this.onSubmitForm}
/>
)
}
}
const mapStateToProps = (state) => ({
user: state.loginReducer.user,
})
const mapDispatchToProps = dispatch => ({
setUserDetails: payload => dispatch(setUserDetails(payload)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Login)
My login Reducer:
const initialState = {
user: {
email: '',
password: '',
}
}
const loginReducer = (state = initialState, action) => {
switch(action.type) {
case SET_USER_DETAILS:
return Object.assign({}, state, {
user: action.user
})
default:
return state
}
return state
}
export default loginReducer
My root Reducer:
import { combineReducers } from 'redux';
import loginReducer from './loginReducer'
const rootReducer = combineReducers({
loginReducer
})
export default rootReducer
MY store configuration:
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import rootReducer from './reducers'
const persistConfig = {
key: 'mykey',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer)
const persistedStore = persistStore(store)
export default store
I am learning React native and trying to implement some features.
The problem is that I just can't access my this.props.user in the Login container when the submit is called. What am I missing in this scenario?
Any help is appreciated.
I've noticed some weird thing. Your default export of LoginContainer.js is connected Login component. I guess what you really meant is instead of this:
// ...imports
export class LoginContainer extends Component {
// ...
}
//...
export default connect(mapStateToProps, mapDispatchToProps)(Login)
to use this:
// ...imports
// no need to 'export class ...' here.
class LoginContainer extends Component {
// ...
}
// ...
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
In the container you used:
const mapStateToProps = (state) => ({
user: state.loginReducer.user,
})
But in your initial state there is not loginReducer. Maibe it works:
const mapStateToProps = (state) => ({
user: state.user
})