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

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.

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;

items not getting added in cart using react-redux

I want to add items to the cart using redux, its not getting added neither its showing any error.
please help me out. Below is my code
this is cartItem.js
export const ADD_TO_CART = 'ADD_TO_CART'
export const REMOVE_FROM_CART = 'REMOVE_FROM_CART'
const initialState = []
const cartItemsReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART:
return [...state, action.payload]
case REMOVE_FROM_CART:
return state.filter(cartItem => cartItem.id !== action.payload.id)
}
return state
}
export default cartItemsReducer
this is store.js
import { createStore } from 'redux'
import cartItemsReducer from './CartItem'
const store = createStore(cartItemsReducer)
export default store
this is App.js
import React from 'react'
import MainStackNavigator from './src/navigation/AppNavigator'
import { Provider as StoreProvider } from 'react-redux'
import store from './src/redux/Store'
export default function App() {
return (
<StoreProvider store={store}>
<MainStackNavigator />
</StoreProvider>
)
}
this is screen where I am dispatching
import ADD_TO_CART from '../redux/CartItem'
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
const addItemToCart = item => dispatch({ type: ADD_TO_CART, payload: item })
<TouchableOpacity onPress={() => addItemToCart(item)} style={styles.button}>
<Text style={styles.buttonText}>Add +</Text>
</TouchableOpacity>
You're invoking the function with an item parameter, but where is it coming from?
You must define the parameter and then pass it.
<TouchableOpacity onPress={(item) => addItemToCart(item)} style=
{styles.button}>

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.

Redux React Native - Action not passing data to 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 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
});

How to open and close an inline dialog in a react redux app

I have a working inline dialog using react state. The working code is below.
import React, { PureComponent } from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import Button from '#atlaskit/button';
import InlineDialog from '#atlaskit/inline-dialog';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
class ButtonActivatedDialog extends PureComponent {
static propTypes = {
content: PropTypes.node,
position: PropTypes.string,
}
state = {
isOpen: false,
};
handleClick = () => {
this.setState({
isOpen: !this.state.isOpen,
});
}
handleOnClose = (data) => {
this.setState({
isOpen: data.isOpen,
});
}
render() {
return (
<InlineDialog
content={this.props.content}
position={this.props.position}
isOpen={this.state.isOpen}
onClose={this.handleOnClose}
>
<Button
onClick={this.handleClick}
isSelected
>
The Button
</Button>
</InlineDialog>
);
}
}
const App = () => (
<ButtonActivatedDialog
content={
<div>
<h5>
Displaying...
</h5>
<p>
Here is the information I need to display.
</p>
</div>}
position='bottom right'
/>
);
render(<App />, document.getElementById('root'));
I would like to have the same behavior with the button but using redux to maintain the state of the dialog.
After reading some material I believe I need to dispatch an action that will activate a reducer witch in turn will help me update the state of the dialog. However, I don't believe I fully understand how this should be put together.
Here is my work in progress but for some reason my codeSanbox does not like the format in which I'm creating the store.
mport React, { PureComponent } from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';
import Button from '#atlaskit/button';
import InlineDialog from '#atlaskit/inline-dialog';
import { connect, createStore } from 'react-redux'
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
const mapStateToProps = state => {
return {
isDialogOpen: false,
}
}
const mapDispatchToProps = dispatch => {
return {
toggleDialog: () => dispatch({
type: 'TOGGLE_DIALOG'
})
}
}
// action:
const tottleDialog = 'TOGGLE_DIALOG';
//action creator
const toggleDialog = (e) => ({
type: 'TOGGLE_DIALOG',
e,
})
class ButtonActivatedDialog extends PureComponent {
static propTypes = {
content: PropTypes.node,
position: PropTypes.string,
}
state = {
isOpen: false,
};
handleClick = () => {
this.setState({
isOpen: !this.state.isOpen,
});
}
handleOnClose = (data) => {
this.setState({
isOpen: data.isOpen,
});
}
render() {
return (
<InlineDialog
content={this.props.content}
position={this.props.position}
isOpen={this.state.isOpen}
onClose={this.handleOnClose}
>
<Button
onClick={this.handleClick}
isSelected
>
The Button
</Button>
</InlineDialog>
);
}
}
const App = () => (
<ButtonActivatedDialog
content={
<div>
<h5>
Displaying...
</h5>
<p>
Info here
</p>
</div>}
position='bottom right'
/>
);
const store = createStore(toggleDialog, {})
//need and action
//need an action creator - a function that returns an action:
//
// render(<App />, document.getElementById('root'));
render(
<Provider store={store}>
<App />
</Provider>, document.getElementById('root')
);
Ok so first you have to set up your redux state. We usually do this according to the re-ducks pattern here: https://github.com/alexnm/re-ducks
This means you will create a directory for each "part" of your application. Each part then has a:
Operation: To perform tasks on the state (like open up or close your inline menu)
Selector: To get some value of the state (like is the inline menu open?)
Action: To perform an action on the state (like set isOpen to true/false)
Reducer: To apply an action to the state (like the one from above)
Type: Any type of state change. Any action has a type, and the type decides which part in the reducer is executed.
So in your example, I would create a state/inlineMenu folder and inside it the following files:
actions.js:
import types from './types';
const toggleState = {
type: types.TOGGLE_STATE
};
export default {
updateMenuState
}
operations.js:
import actions from './actions';
const toggleState = actions.toggleState;
export default {
updateMenuState
};
reducers.js:
import types from './types';
const initialState = {
isOpen: false // closed per default
};
const inlineMenuReducer = (state = initialState, action) => {
switch (action.type) {
case types.TOGGLE_STATE:
return { ...state, isOpen: !state.isOpen }
default:
return state;
}
};
export default inlineMenuReducer;
selectors.js:
const isMenuOpen = state => state.inlineMenu.isOpen;
export default {
isMenuOpen
};
types.js:
const TOGGLE_STATE = 'inlineMenu/TOGGLE_STATE';
export default {
TOGGLE_STATE
};
index.js:
import reducer from './reducers';
export { default as inlineMenuSelectors } from './selectors';
export { default as inlineMenuOperations } from './operations';
export default reducer;
You also have to set up the default provider. Your path to the isOpen property in the selectors should probably be adjusted.
Now you have your global redux state set up.
We need to get the data in it now to the view. We need to use redux' connect function for this, in which it will take the operations and selectors and map them to the default react props.
So your connected component could look like this:
import React, { PureComponent } from 'react';
import { render } from 'react-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Button from '#myKitkit/button';
import InlineDialog from '#mykit/inline-dialog';
import { inlineMenuOperations, inlineMenuOperations } from '../path/to/state/inlineMenu';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
class ButtonActivatedDialog extends PureComponent {
static propTypes = {
content: PropTypes.node,
position: PropTypes.string,
toggleState: PropTypes.func.isRequired
}
handleClick = () => {
const { toggleState } = this.props;
// This will dispatch the TOGGLE_STATE action
toggleState();
}
handleOnClose = () => {
const { toggleState } = this.props;
// This will dispatch the TOGGLE_STATE action
toggleState();
}
render() {
return (
<InlineDialog
content={this.props.content}
position={this.props.position}
isOpen={this.props.isOpen}
onClose={this.handleOnClose}
>
<Button
onClick={this.handleClick}
isSelected
>
The Button
</Button>
</InlineDialog>
);
}
}
// You need to add the provider here, this is described in the redux documentation.
const App = () => (
<ButtonActivatedDialog
content={
<div>
<h5>
Displaying...
</h5>
<p>
Here is the information I need to display.
</p>
</div>}
position='bottom right'
/>
);
const mapStateToProps = state => ({
isOpen: inlineMenuSelectors.isOpen(state);
});
const mapDispatchToProps = dispatch => ({
toggleState: () => dispatch(inlineMenuOperations.toggleState())
}
const ConnectedApp = connect(mapStateToProps, mapDispatchToProps);
render(<ConnectedApp />, document.getElementById('root'));

Categories