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>
);
}
Related
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)
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;
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
In React Navigation 5 Auth Flow, it says screen will automatically navigate when conditional state changes.
I have setup the screen to navigate to HomeScreen when the state of
isAuthenticated changes to true.
There is no error in the console. The isAuthenticated does change to
true, but the screen is not navigating to HomeScreen after state
change.
I also tried using alternatives like {NavigationActions} and
{CommonActions} in the action creators. to force navigation, but are
also not working.
AuthStackNav.js
import React from 'react';
import { connect } from 'react-redux';
import {createStackNavigator} from '#react-navigation/stack';
import AuthScreen from '../screens/AuthScreen';
import HomeScreen from '../screens/HomeScreen';
const AuthStackNav = ({isAuthenticated}) => {
const AuthStack = createStackNavigator();
return (
<AuthStack.Navigator initialRouteName='Auth'>
{
isAuthenticated ?
<AuthStack.Screen name='Home' component={HomeScreen} />
:
<AuthStack.Screen name='Auth' component={AuthScreen} />
}
</AuthStack.Navigator>
);
};
const mapStateToProps = ({isAuthenticated}) => {
return {isAuthenticated};
};
export default connect(mapStateToProps, null)(AuthStackNav);
userActions.js
import {LOGIN_WITH_FACEBOOK} from './types';
import {NavigationActions} from 'react-navigation';
import { CommonActions } from '#react-navigation/native';
export const loginWithFacebook = () => (dispatch) => {
dispatch({ type: LOGIN_WITH_FACEBOOK, payload: {isAuthenticated: true} });
dispatch(NavigationActions.navigate({routeName:'Home'}));
dispatch( CommonActions.navigate({ name: 'Home' }) );
};
userReducer.js
import {LOGIN_WITH_FACEBOOK} from '../actions/types.js';
const initialState = {
isAuthenticated: false
};
const userReducer = (state=initialState, action) => {
switch(action.type){
case LOGIN_WITH_FACEBOOK:
return {...state, ...action.payload};
default:
return state;
}
}
export default userReducer;
AuthScreen.js
import React from 'react';
import {Text, View, StyleSheet, Image, TouchableOpacity, SafeAreaView} from 'react-native';
import { connect } from 'react-redux';
import {loginWithFacebook} from '../actions/userActions';
const AuthScreen = ({loginWithFacebook}) => {
return (
<View style={styles.screenContainer}>
<TouchableOpacity
activeOpacity={0.5}
onPress={loginWithFacebook}
> Facebook Login</TouchableOpacity>
</View>
);
};
const mapDispatchToProps = {
loginWithFacebook
};
export default connect(null, mapDispatchToProps)(AuthScreen);
AppNav.js
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import AuthStackNav from './AuthStackNav';
const AppNav = () => {
return (
<NavigationContainer>
<AuthStackNav />
</NavigationContainer>
);
};
export default AppNav;
I was able to solve it with this guide.
RootNavigation.js
import * as React from 'react';
export const navigationRef = React.createRef();
export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
AppNav.js
import { navigationRef } from './RootNavigation.js';
const AppNav = () => {
return (
<NavigationContainer ref={navigationRef} >
<AuthStackNav />
</NavigationContainer>
);
};
export default AppNav;
userActions.js
export const loginWithFacebook = () => (dispatch) => {
dispatch({ type: LOGIN_WITH_FACEBOOK, payload: {isAuthenticated: true} });
RootNavigation.navigate('Home');
};
The answer of Kaizen Tamashi is correct.
In addition, if you want the goBack functionality just add this code inside RootNavigation.js
export function goBack() {
navigationRef.current?.goBack();
}
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
});