Redux React Native - Action not passing data to reducer - javascript

I am new to Redux and it might be some silly error. I am trying to make an Api call in Action and pass the data to the reducer. I can see the response from the api call but for some reason it's not sharing the data correctly with the reducer or I don't know how to pass and render the state properly to home.js. Please find below action - reducers - store.js - home.js
Action file
export const DATA_AVAILABLE = 'DATA_AVAILABLE';
export function getData(){
return (dispatch) => {
//Make API Call
fetch("MY API URL").then((response) => {
return response.json();
}).then((data) => {
var data = data.articles;
console.log(data)
dispatch({type: DATA_AVAILABLE, data:data});
})
};
}
REDUCERS
import { combineReducers } from 'redux';
import { DATA_AVAILABLE } from "../actions/" //Import the actions types constant we defined in our actions
let dataState = {
data: [],
loading:true
};
const dataReducer = (state = dataState, action) => {
switch (action.type) {
case DATA_AVAILABLE:
state = Object.assign({}, state, { data: action.data, loading:false });
console.log(dataState)
return state;
default:
return state;
}
};
// Combine all the reducers
const rootReducer = combineReducers({
dataReducer
// ,[ANOTHER REDUCER], [ANOTHER REDUCER] ....
})
export default rootReducer;
STORE.JS
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../app/reducers/index'; //Import the reducer
// Connect our store to the reducers
export default createStore(reducers, applyMiddleware(thunk));
HOME.JS
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
FlatList,
View,
Text,
ActivityIndicator
} from 'react-native';
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions'; //Import your actions
class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
this.renderItem = this.renderItem.bind(this);
}
componentDidMount() {
this.props.getData(); //call our action
}
render() {
if (this.props.loading) {
return (
<View style={styles.activityIndicatorContainer}>
<ActivityIndicator animating={true}/>
</View>
);
} else {
console.log(this.state)
return (
<View style={{flex:1, backgroundColor: '#F5F5F5', paddingTop:20}}>
<FlatList
ref='listRef'
data={this.props.data}
renderItem={this.renderItem}
keyExtractor={(item, index) => index}/>
</View>
);
}
}
renderItem({item, index}) {
return (
<View style={styles.row}>
<Text style={styles.title}>
{this.props.data.title}
</Text>
<Text style={styles.description}>
</Text>
</View>
)
}
};
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.dataReducer.loading,
data: state.dataReducer.date
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/home.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(Home);

You are not mutating the state righty.
Redux do only shallow comparison for optimisation.
Its only check reference.
Reference need to be update.
state = Object.assign({}, state, {
data: [
...state.data, //change previous state data reference
...action.data //update current state data reference
],
loading: false
});

Related

Fetch an API using Redux without the toolkit

I'm currently having trouble fetching an API using redux while displaying it a FlatList.
This is my actions code:
import { GET_API } from "../actionTypes";
import * as React from 'react';
const url = "https://data.binance.com/api/v3/ticker/24hr"
export const getAPI = () => {
React.useEffect(() => {
fetch(URL)
.then (x => x.json())
.then(json => {
return {
type: GET_API,
payload: {
title: json
}
}
})
}, [])
}
Here is my reducer code:
import { GET_API } from '../actionTypes/index';
var initialState = {
tasks: [],
};
export default function (state = initialState, action) {
if (action.type == GET_API) {
return {
...state,
tasks: [...state.tasks, { title: action.payload.title }]
}
}
return state;
}
And this is my ApiFetch code:
import { connect } from 'react-redux';
import {
View,
Text,
Button,
TextInput,
FlatList,
ScrollView
} from 'react-native';
import { useState } from 'react';
import { getAPI } from '../redux/actions/index';
const mapStateToProps = (state) => {
return { tasks: state.reducer.tasks };
};
const mapDispatchToProps = { getAPI };
function App({ tasks, get_API }) {
return (
<ScrollView>
<FlatList
data={tasks}
renderItem={({ item }) => (
<View>
<Text>{item.symbol}</Text>
</View>
)}
/>
</ScrollView>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
The way I learned is using a useState so I believe putting it in my actions is wrong but even without the useState it's still not displaying.
React.useEffect is a React hook, so it's only validly called in a React function component or custom React hook. If you are using "legacy redux", i.e. not the newer, better, and current redux-toolkit, then you'll need to ensure you have setup/configured your Redux store to include the Thunk middleware.
See Configuring the Store for how to install and apply the Thunk middleware.
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducer'
const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware))
// The store now has the ability to accept thunk functions in `dispatch`
const store = createStore(rootReducer, composedEnhancer)
export default store
getApi needs to be converted to a thunk, i.e. an asynchronous action. These are typically functions that return a thunk function that is passed both a dispatch and getState function. These are so the thunk can access current state, if necessary, and dispatch further actions to the store.
export const getApi = () => async (dispatch) => {
const response = await fetch(URL);
const title = await response.json();
dispatch({
type: GET_API,
payload: { title },
);
return title;
};
Dispatch the getApi action when the component mounts via a useEffect hook call.
import { useEffect } from 'react';
import { connect } from 'react-redux';
import {
View,
Text,
Button,
TextInput,
FlatList,
ScrollView
} from 'react-native';
import { getApi } from '../redux/actions/index';
function App({ tasks, getApi }) {
useEffect(() => {
getApi();
}, []);
return (
<ScrollView>
<FlatList
data={tasks}
renderItem={({ item }) => (
<View>
<Text>{item.symbol}</Text>
</View>
)}
/>
</ScrollView>
);
}
const mapStateToProps = (state) => ({
tasks: state.reducer.tasks,
});
const mapDispatchToProps = {
getApi,
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
The connect Higher Order Component has fallen a bit out of favor. It's preferable to use the useDispatch and useSelector hooks in modern React-Redux code.
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
View,
Text,
Button,
TextInput,
FlatList,
ScrollView
} from 'react-native';
import { getApi } from '../redux/actions/index';
function App() {
const dispatch = useDispatch();
const tasks = useSelector(state => state.reducer.tasks);
useEffect(() => {
dispatch(getApi());
}, []);
return (
<ScrollView>
<FlatList
data={tasks}
renderItem={({ item }) => (
<View>
<Text>{item.symbol}</Text>
</View>
)}
/>
</ScrollView>
);
}
export default App;
Given all this, it is highly recommended to update and start using redux-toolkit. If you are already familiar with Redux/React-Redux then it'll likely be a 10-15 integration and all the current/existing reducers and actions will continue to work. What this will allow you to do is to write modern React-Redux which is much less boiler-platey, also already has thunk middleware included and active by default so asynchronous actions will work right out of the box, and allows you to write mutable reducer updates.
For example, the above tasks state & reducer function, and getApi action using RTK:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
tasks: [],
};
export const getApi = createAsyncThunk(
"tasks/getApi",
async (_, { dispatch }) => {
const response = await fetch(URL);
const title = await response.json();
return title;
}
);
const tasksSlice = createSlice({
name: "tasks",
initialState,
extraReducers: builder => {
builder.addCase(getApi.fulfilled, (state, action) => {
const { title } = action.payload;
state.push({ title });
});
};
});
export default tasksSlice.reducer;

React Native 0.64.2, React-Redux 7.2.4, cannot get props to work "undefined is not an object"

I am trying to get React-Redux to work in my new React-Native app and am not succeeding. In my attempts to find online help, all of the examples are using Classes for each Component/Screen, but the beginning React Native template app is no longer using Classes. Everything is defined using "const" and the main App function is defined as:
`const App: () => Node = () => {...`
which are all new to me and I'm not sure if it has anything to do with my failures.
I have several Components/Screens all working nicely and do not have errors until I try to implement Redux.
Reducer:
const initState = {
syncedDate: '01/02/2022'
}
const projectsReducer = (state = initState, action) => {
switch (action.type) {
case 'PJS_SET_SYNCEDDATE':
return {
...state,
syncedDate: action.syncedDate
}
break;
default:
}
return state
}
export default projectsReducer;
Action:
export const pjsSetSyncedDate = (syncedDate) => {
return {
type: 'PJS_SET_SYNCEDDATE',
syncedDate
}
}
Store:
import { createStore, combineReducers } from 'redux';
import projectsReducer from '../reducers/projects';
const rootReducer = combineReducers({
projects: projectsReducer
});
const configureStore = () => {
return createStore(rootReducer);
};
export default configureStore;
App:
...
const store = configureStore();
...
const App: () => Node = () => {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<Provider store={store}>
<NavigationContainer>
<Tab.Navigator
...
</Tab.Navigator>
</NavigationContainer>
</Provider>
);
};
...
Component:
import React from 'react';
import type { Node } from 'react';
import {
Text,
View,
} from 'react-native';
import { connect } from 'react-redux';
export const DetailsScreen = ({ route, navigation }) => {
const { name } = route.params;
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Details List</Text>
<Text>Incoming param: {JSON.stringify(name)}</Text>
<Text>SyncedDate: {this.props.syncedDate}</Text>
</View>
);
};
const mapStateToProps = (state) => {
return {
syncedDate: state.projects.syncedDate
}
};
export default ConnectedDetailsScreen = connect(mapStateToProps)(DetailsScreen);
The error occurs in the Text block
"this.props.syncedDate" - undefined is not an object
You are mixing some old implementation with the new one. try to change their step by step:
As a best practice, use payload property to pass your data through your action, so add it in your action.js:
export const pjsSetSyncedDate = (syncedDate) => {
return {
type: 'PJS_SET_SYNCEDDATE',
payload: syncedDate // -----> added here
}
}
So, change your reducer to get new syncedData with payload:
const initState = {
syncedDate: '01/02/2022'
}
const projectsReducer = (state = initState, action) => {
switch (action.type) {
case 'PJS_SET_SYNCEDDATE':
return {
...state,
syncedDate: action.payload.syncedDate // -------> add payload here
}
// break;
default:
}
return state
}
export default projectsReducer;
Note: you don't need the break expression in the switch/case since you returning the result in the case. I commented out this line in the above reducer code.
Now, back to the component DetailsScreen:
import React from 'react';
import { Text,View } from 'react-native';
import { useSelector } from 'react-redux'; // -----> import useSelector instead of connect method
export const DetailsScreen = ({ route, navigation }) => {
const { name } = route.params;
const syncedData = useSelector(state => state.projects.syncedData)
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Details List</Text>
<Text>Incoming param: {JSON.stringify(name)}</Text>
<Text>SyncedDate: {syncedDate}</Text>
</View>
);
};
export default DetailsScreen;
Note: useSelector hook will get the state with a selector function. you define your reducer as projects in the combineReducers so your syncedData is in your state.projects
Note: with the above procedure, you don't need to connect your DetailsScreen to the store to get the state. useSelector hook will do that.

Calling an action triggers multiple reducers?

I just send one action in LoginAction, but it triggers three reducers. I don't know why...
Version:
"react": "16.9.0",
"react-native": "0.61.2",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
Here is my setting.
App.js:
import React from 'react';
import ReduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { compose, createStore, applyMiddleware } from 'redux';
// import { persistStore } from 'redux-persist';
// import { PersistGate } from 'redux-persist/integration/react';
import reducers from './src/reducers';
import AppContainer from './src/navigator' // It is my react-navigation route
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const App: () => React$Node = () => {
if (!__DEV__) {
console.log = () => {};
}
const store = createStore(reducers, {}, composeEnhancers(applyMiddleware(ReduxThunk)));
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
};
export default App;
My LoginAction.js:
export const testAction = () => {
return (dispatch) => {
dispatch( { type: 'TEST_ACTION' });
}
}
My reducer setting:
reducers/index.js
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
import StoreReducer from './StoreReducer';
import ReservationReducer from './ReservationReducer';
export default combineReducers({
LoginRedux: LoginReducer,
StoreRedux: StoreReducer,
ReservationRedux: ReservationReducer
});
My LoginReducer.js: (StoreReducer and ReservationReducer are the same as LoginReducer)
const INITIAL_STATE = {
...my state arguments
};
export default (state = INITIAL_STATE, action) => {
// Here is my issue !
// StoreReducer is 'StoreReducer reducer =>', action
// ReservationReducer is 'ReservationReducer reducer =>', action
console.log('Login reducer =>', action);
switch (action.type) {
case 'SOME_ACTION':
return {
// state setting from action
};
default:
return state;
}
};
Call the action component is LoginScreen.js:
import React, { Component } from 'react';
import {
// some view
} from 'react-native';
import { connect } from 'react-redux';
import { login, closeLoadingCheckModal, testAction } from '../actions';
class LoginScreen extends Component {
constructor(props) {
super(props);
this.state = {
// some states
};
}
render() {
return (
<View>
<TouchableOpacity onPress={() => { this.props.testAction() }>
<Text>Press Test Action</Text>
</TouchableOpacity>
</View>
);
}
const mapStateToProps = (state) => {
const { ...some store value } = state.LoginRedux;
return { ...some store value };
};
}
export default connect(mapStateToProps, { login, closeLoadingCheckModal, testAction })(LoginScreen);
When I trigger the action, all of the reducers will be triggered.
It should only console log Login reducer =>
Anyone knows what is my problem?
If you're using combineReducers, all your slice reducers will run for every dispatched action. It's a question of whether a given reducer actually calculates new state or not.
See the Redux FAQ entry on "is there a 1:1 mapping between reducers and actions?" for more details.
An action can indeed end update multiple reducers, if multiple reducers are implementing the same action type. In your case all three reducers listen for actions of type TEST_ACTION. If you don't want this behaviour you should make sure action types are unique throughout your app, for example by prefixing actions based on reducer.
export const testAction = () => {
return (dispatch) => {
dispatch( { type: 'LOGIN_TEST_ACTION' });
}
}

Async API Call, Redux React-Native

I have recently started to learn React-Native and I am trying to implement Redux to manage the state of my app.
As I have experience with React JS I implemented the state manage Redux the same way I would usually do with React JS. Everything seems to work except the async api call. The props in store change but it does not change in component props.
Here is how I set my redux store
import { createStore, compose, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import reducer from './reducers';
//create initial state
const initialState = {};
//create a variable that holds all the middleware
const middleware = [thunk, logger];
//create the store
const store = createStore(
reducer,
initialState,
compose(
applyMiddleware(...middleware)
)
);
export default store;
My reducer component
import { FETCH_REQUEST_BEGIN, FETCH_REQUEST_FAILED, DATA_FETCHED} from '../constants';
const initialState = {
movieResults: null,
fetching : false,
failed : false
}
export default (state = initialState, actions)=>{
switch(actions.type){
case FETCH_REQUEST_BEGIN:
return {
...state,
fetching : true
};
case DATA_FETCHED :
return {
...state,
movieResults : actions.payload,
fetchin : false
}
case FETCH_REQUEST_FAILED:
return {
...state,
failed : true
};
default :
return state;
}
}
This is the root reducer
import { combineReducers } from 'redux';
import movieReducer from './movieReducer';
export default combineReducers({
movie : movieReducer
});
action component
import axios from 'axios';
import { FETCH_REQUEST_BEGIN, FETCH_REQUEST_FAILED } from '../constants';
const apiRequest = (url, type) => {
return dispatch => {
dispatch({
type : FETCH_REQUEST_BEGIN
});
axios.get(url)
.then((results) => {
dispatch({
type : type,
payload : results.data
});
}).catch((error) => {
dispatch({
type : FETCH_REQUEST_FAILED,
payload : error
});
});
}
}
export default apiRequest;
Main component
import React, { Component } from 'react';
import { View, Text, Button, ActivityIndicator } from 'react-native';
import { API, DATA_FETCHED } from '../constants';
import { apiRequest } from '../actions';
import { connect } from 'react-redux';
class Home extends Component {
componentWillMount() {
const discoverUrlMovies =`https://jsonplaceholder.typicode.com/posts`;
this.fetchData(discoverUrlMovies, DATA_FETCHED);
}
fetchData = (url, type) => {
this.props.apiRequest(url, type);
}
shouldComponentUpdate(nextProps, prevProps){
return nextProps != prevProps
}
render() {
const { movie } = this.props;
//console out the props
console.log('this.props', this.props);
let displayMovies;
if(movie === undefined || movie === null ){
displayMovies = <ActivityIndicator size = 'large' color = '#121222'/>
}else {
displayMovies = <Text>Working</Text>
}
return (
<View style = {{flex: 1}}>
<Text>Home</Text>
{
//display result
displayMovies
}
</View>
);
}
}
const mapStateToProps = (state) => {
return {
movie : state.movieResults
}
}
export default connect(mapStateToProps, { apiRequest })(Home);
What am I missing / doing wrong?
You need to define your mapStateToProps func as
movie : state.movie.movieResults
since you're combining them as
export default combineReducers({
movie : movieReducer
});

Redux on React Native not able to render from reducer

I am new to redux and it might be some silly error. I am trying to make an Api call in Action and pass the data to the Reducer. I can see the data passed correctly in the reducer with action.data. I think the problem is in mapStateToProps in the component therefore I am not able to pass the state and render the component. Please find below action - reducers - store.js - home.js
ACTION.JS
export const DATA_AVAILABLE = 'DATA_AVAILABLE';
export function getData(){
return (dispatch) => {
//Make API Call
fetch("MY API URL").then((response) => {
return response.json();
}).then((data) => {
var data = data.articles;
console.log(data)
dispatch({type: DATA_AVAILABLE, data:data});
})
};
}
this is Reducers.JS
import { combineReducers } from 'redux';
import { DATA_AVAILABLE } from "../actions/" //Import the actions types constant we defined in our actions
let dataState = {
data: [],
loading:true
};
const dataReducer = (state = dataState, action) => {
switch (action.type) {
case DATA_AVAILABLE:
state = Object.assign({}, state, {
data: [
...action.data //update current state data reference
],
loading: false
});
console.log(action.data);
return state;
default:
return state;
}
};
// Combine all the reducers
const rootReducer = combineReducers({
dataReducer
// ,[ANOTHER REDUCER], [ANOTHER REDUCER] ....
})
export default rootReducer;
this is Store.js with Redux-thunk
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../app/reducers/index'; //Import the reducer
// Connect our store to the reducers
export default createStore(reducers, applyMiddleware(thunk));
and finally home.js component when I need to pass the new state and render it
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
FlatList,
View,
Text,
ActivityIndicator
} from 'react-native';
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions'; //Import your actions
class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.getData(); //call our action
}
render() {
if (this.props.loading) {
return (
<View style={styles.activityIndicatorContainer}>
<ActivityIndicator animating={true}/>
</View>
);
} else {
console.log(this.state)
return (
<View style={styles.row}>
<Text style={styles.title}>
{this.props.data}
fomrmo
</Text>
<Text style={styles.description}>
</Text>
</View>
);
}
}
};
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.dataReducer.loading,
data: state.dataReducer.data
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/home.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Assuming that:
case DATA_AVAILABLE:
console.log(action.data.length)
will console.log something more than 0
change your reducer action:
const dataReducer = (state = dataState, action) => {
switch (action.type) {
case DATA_AVAILABLE:
return {
...state,
data: action.data,
loading: false
});
default:
return state;
}
};
To address:
Objects are not valid as a React child(found objects with Keys
{source, author, title, description, url })
that's because you try to render Object:
{this.props.data}
but if you do:
{
this.props.data.map((el, i) =>
<p key={i}>Element nr {i}</p>
)
}
It should work.

Categories