I am trying to implement the following example in my project:
https://github.com/reactjs/redux/tree/master/examples/async
But I keep running into the error: dispatch is not function
when executing the dispatch function from my actions class.
It seems as if Dispatcher is not getting passed down to my actions but I followed the flow in the example:
actions/index.js file:
export const REQUEST_LOCAL_POSTS = 'REQUEST_LOCAL_POSTS';
export const RECEIVE_LOCAL_POSTS = 'RECEIVE_LOCAL_POSTS';
export const REQUEST_CURRENT_LOCATION = 'REQUEST_CURRENT_LOCATION';
export const RECEIVE_CURRENT_LOCATION = 'RECEIVE_CURRENT_LOCATION';
export const requestCurrentLocation = () => ({
type: REQUEST_CURRENT_LOCATION
})
export const receiveCurrentLocation = currentLocation => ({
type: RECEIVE_CURRENT_LOCATION,
currentLocation,
receivedCurrentLocationAt: Date.now()
})
export const requestLocalPosts = () => ({
type: REQUEST_LOCAL_POSTS
})
export const receiveLocalPosts = json => ({
type: RECEIVE_LOCAL_POSTS,
posts: json,
receivedAt: Date.now()
})
export const fetchLocalPosts = dispatch => {
dispatch(requestCurrentLocation())
navigator.geolocation.getCurrentPosition(
(position) => {
dispatch(requestLocalPosts()) // getting dispatch is not a function here
return fetch(`http://192.168.1.3:9000/posts?lng=${position.coords.longitude}&lat=${position.coords.latitude}&radius=1000`)
.then(response => response.json())
.then(json => dispatch(receiveLocalPosts(json)))
},
(error) => this.setState({ error: error.message }),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 },
)
home.js (component):
import React, { Component } from 'react';
import {Text, View, ActivityIndicator, FlatList, Image, TouchableHighlight} from 'react-native';
import styles from './styles';
import MapView from 'react-native-maps';
import images from '../../config/images';
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { fetchLocalPosts } from '../../actions'
class Home extends Component {
static navigationOptions = ({ navigation }) => {
//const {dispatch , state, setParams} = navigation;
return {
title:<Text style={styles.title}>Localized</Text>,
headerStyle: {backgroundColor: '#2c6da4'},
headerRight: (
<TouchableHighlight onPress={() => navigation.dispatch({ type: 'MAP_EXPLORE' })}>
<Image
source={images.icons.map}
/>
</TouchableHighlight>
),
};
};
static propTypes = {
posts: PropTypes.array.isRequired,
isFetching: PropTypes.bool.isRequired,
lastUpdated: PropTypes.number,
dispatch: PropTypes.func.isRequired
}
componentDidMount() {
console.log(this.props)
const { dispatch } = this.props
dispatch(fetchLocalPosts()) // calling action from here
}
render() {
return (
<View style={styles.container}>
// i know posts isnt accessed here yet, still trying
to get past error
<FlatList
data={this.state.data}
refreshing={this.state.refreshing}
showsVerticalScrollIndicator={false}
ListHeaderComponent={this.renderHeader}
onRefresh={this._onRefresh.bind(this)}
renderItem={({item}) => <View><FlatList
data={item.posts}
horizontal={true}
snapToAlignment='center'
showsHorizontalScrollIndicator={false}
renderItem={({item}) => <Text style={styles.item}>{item.title}{"\n"}{item.snippet}</Text>}/>
<Text style={styles.subItem}>{item.Name}{"\n"}{item.Address}</Text>
</View>}
/>
</View>
);
}
}
const mapStateToProps = state => {
const { postsByBusiness } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByBusiness || {
isFetching: true,
items: []
}
return {
posts,
isFetching,
lastUpdated
}
}
export default connect(mapStateToProps)(Home)
reducer/index.js:
import { combineReducers } from 'redux';
import { NavigationActions } from 'react-navigation';
import { AppNavigator } from '../navigators/AppNavigator';
import { RECEIVE_LOCAL_POSTS, REQUEST_LOCAL_POSTS } from '../actions';
const initialNavState=AppNavigator.router.getStateForAction(NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
routeName: 'Home',
}),
],
}));
const MAP_EXPLORE = 'MAP_EXPLORE';
const LIST_EXPLORE = 'LIST_EXPLORE';
function nav(state = initialNavState, action) {
let nextState;
switch(action.type) {
case MAP_EXPLORE:
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Map'}),
state
);
break;
case LIST_EXPLORE:
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'List'}),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
return nextState || state;
}
function postsByBusiness(state = { }, action) {
switch(action.type) {
case RECEIVE_LOCAL_POSTS:
return {
...state,
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAts
}
default:
return state
}
}
const AppReducer = combineReducers({
nav,
postsByBusiness
});
export default AppReducer;
fetchLocalPosts should be returning a function that takes dispatch as an argument. Changing it to the following should fix it.
export const fetchLocalPosts = () => dispatch => {
...
It's another way of doing this:
export const fetchLocalPosts = () => {
return function (dispatch) {
...
Related
I'm having problems filtering info consummed by an api. it is actually the nasa api.
What I Want
Look photos by
Rover's Name (input)
by camera name (input)
by date
I successfully completed the last task but the first input is not bringing the image if I look by the rovers name
Search filter
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { getPhotos } from "../../redux/actions/getPhotos";
import { ErrorAlert } from "./Alerts";
import './SecondFinder.css';
export default function SecondFinder() {
const dispatch = useDispatch();
const [term, setSearchTerm] = useState("");
const [alertERR, setAlertERR] = useState(false);
const submitHandler = (e) => {
e.preventDefault();
dispatch(getPhotos(term));
}
return (
<form onSubmit={submitHandler}>
<div className="searchPhotosByRover">
<label>Search by Rovers Name</label>
<input type="text" placeholder="Search by Rover" value={term} onChange={(e) => setSearchTerm(e.target.value)} />
<button className="btn-primary">Search</button>
{alertERR ? <ErrorAlert /> : ""}
</div>
</form>
)
}
Component:
import React from "react";
import { useSelector } from "react-redux";
import { LoadingAlert, PhotoNotFoundAlert } from "./Alerts";
import Photos from "./Photos";
export default function GridPhotos() {
const state = useSelector((state) => state.result);
const renderPhotos = () => {
if (state.loading) {
return <LoadingAlert />;
} if (state.photos.length === 0) {
return <PhotoNotFoundAlert />;
}
return state.photos.map((photo, index) => {
return <Photos key={index} photo={photo} index={index} />;
});
};
return <div className="gridPhotos">{renderPhotos()}</div>;
}
Actions:
import axios from "axios";
export const getPhotos = (date, term) => async (dispatch, getState) => {
dispatch({
type: "FETCH_PHOTOS_REQUEST"
})
try {
const response = await axios.get(`https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?earth_date=${date}&name=${term}&api_key=${process.env.REACT_APP_API_KEY}&page=1`)
dispatch({
type: "FETCH_PHOTOS_SUCCESS",
payload: response.data.photos
})
} catch (error) {
dispatch({
type: "FETCH_PHOTOS_FAILURE",
error
})
}
}
Reducer
const initialState = {
photos: [],
loading: false,
error: null
}
export const getPhotosReducer = (state = initialState, action) => {
console.log("action", action)
switch (action.type) {
case "FETCH_PHOTOS_REQUEST":
return {
...state,
loading: true,
error: null
}
case "FETCH_PHOTOS_SUCCESS":
return {
...state,
loading: false,
photos: action.payload
}
case "FETCH_PHOTOS_FAILURE":
return {
...state,
loading: false,
error: action.error,
photos: []
}
default:
return state
}
}
I just want to look by the rovers name and bring a picture based on this name
enter image description here
I am trying to use redux in my app for the first time and i set up whole boilerplate for it but i got an error which says Error: The slice reducer for key "AuthReducer" returned undefined during initialization. as it says i checked the state value passed in reducer but i can't figure it out, Here i am adding my code sample.
reducer.js
import {combineReducers} from 'redux'
import { SIGN_IN, SIGN_OUT, SIGN_UP, MOVIE_ADDED, MOVIE_REMOVED } from './ActionTypes'
const initialMovieState = {
savedMovies: [
{
id: '',
name: ''
}
]
}
const initialAuthState = {
userEmail: '',
userPassword: '',
userToken: '',
}
export const AuthReducer = (state=initialAuthState, action) => {
switch (action.type) {
case SIGN_IN:
return {
...state,
userToken: action.payload.token
}
case SIGN_UP:
return {
...state,
userEmail: action.payload.email,
userPassword: action.payload.password
}
case SIGN_OUT:
return {
...state,
userToken: ''
}
default:
state
}
}
export const MovieReducer = (state=initialMovieState, action) => {
switch (action.type) {
case MOVIE_ADDED:
return {
...state,
savedMovies: state.savedMovies.concat({
id: action.payload.movieId,
name: action.movieName
})
}
case MOVIE_REMOVED:
return state.filter(movie => movie.id !== action.payload.movieId)
default:
state
}
}
const RootReducer = combineReducers({
AuthReducer,
MovieReducer
})
export default RootReducer
actions.js
import {SIGN_IN, SIGN_OUT, SIGN_UP, MOVIE_ADDED, MOVIE_REMOVED} from './ActionTypes'
export const sign_in = (user) => (
{
type: SIGN_IN,
payload: {
token: user
}
}
)
export const sign_up = (user) => (
{
type: SIGN_UP,
payload: {
email: user.email,
password: user.password
}
}
)
export const sign_out = () => (
{
type: SIGN_OUT,
}
)
export const movie_added = (movie) => (
{
type: MOVIE_ADDED,
payload: {
movieId: movie.id,
movieName: movie.name,
}
}
)
export const movie_removed = () => (
{
type: MOVIE_REMOVED
}
)
store.js
import { createStore } from 'redux'
import {composeWithDevTools} from 'redux-devtools-extension'
import rootReducer from './Reducer'
const Store = createStore(rootReducer, composeWithDevTools())
export default Store
I've wrapped the whole app in Provider with store as an argument in App.js
SignUpScreen.js
import {useDispatch, useStore} from 'react-redux'
import {sign_up} from '../redux/Actions'
const SignUpScreen = () => {
const navigation = useNavigation()
const dispatch = useDispatch()
const Store = useStore()
...
const handleSubmit = () => {
if(isValidEmail === true && isValidPass === true && isPassMatched === true){
console.log(Store.getState())
dispatch(sign_up({email: email, password: password}))
console.log(Store.getState())
navigation.navigate('signin')
ToastAndroid.showWithGravity(
'Account created.',
ToastAndroid.LONG,
ToastAndroid.BOTTOM
)
} else {
ToastAndroid.showWithGravity(
'Invalid Email or Password',
ToastAndroid.LONG,
ToastAndroid.BOTTOM
)
}
}
...
return(
{/* register button */}
<TouchableOpacity style={styles.regBtnCont} onPress={() => handleSubmit()}>
<Text style={styles.regBtnText}>Register</Text>
</TouchableOpacity>
)
}
Any help will be appriciated, Thank you.
I am using react and redux to fetch data from an api, but when I try to retrieve the data in my view, I get an error :
TypeError: catData is undefined
I am having a hard time retrieving data from my api in my view, despite the fact that everything is fine on the backened. Any advise on what im doing wrong/ recommendations on how to retrieve data from the reducer will be highly appreciated.
My view looks like this
import React, { useState, useEffect } from "react";
import { connect} from "react-redux";
// #material-ui/core components
import Loader from 'react-loader-spinner'
import { fetchCategories } from "../../../actions/data"
const mapStateToProps = state => {
return {
catData: state.categories
}
}
const mapDispatchToProps = dispatch => {
return {
fetchCategories: () => dispatch(fetchCategories())
}
}
function CategoriesSection({ catData,fetchCategories}) {
useEffect(() => {
fetchCategories();
}, []);
return catData.loading ? (
<div xs={12} sm={10} md={10} lg={10} style={{marginTop: 10}} >
<Loader
type="Puff"
color="red"
height={200}
width={200}
style={{ display: "flex",
justifyContent: "center",
alignItems: "center" }}
/>
</div>
)
: catData.error ? (
<h2> {catData.error} </h2>
): (
<div>
<div className={classes.title} justify="center">
<h2 className={classes.title}>Our Categories</h2>
</div>
<div>
{
catData && catData.cat
}
</div>
</div>
)
}
export default connect(mapStateToProps, mapDispatchToProps)(CategoriesSection)
My reducer looks like this :
import {
CATEGORIES_FETCH_REQUEST,
CATEGORIES_SUCCESS,
CATEGORIES_FAIL
} from "../actions/types";
const initialState = {
loading: false,
categories: [],
businesses: [],
error: ''
}
export default function(state = initialState, action) {
switch(action.type) {
case CATEGORIES_FETCH_REQUEST:
return {
...state,
loading: true
};
case CATEGORIES_SUCCESS:
return {
...state,
loading: false,
categories: action.payload
};
case CATEGORIES_FAIL:
return {
loading: false,
categories: null,
error: action.payload
};
default:
return state;
}
}
And my actions looks like this :
import axios from 'axios';
import {
CATEGORIES_SUCCESS,
CATEGORIES_FAIL,
CATEGORIES_FETCH_REQUEST,
} from "./types";
export const fetchCategories = () => {
return (dispatch) => {
dispatch(fetchCategoryRequest)
axios.get('https://api.xxxxxxx.com/api/v1/categories/')
.then(response => {
const categories = response.data
dispatch(fetchCategorySuccess(categories))
})
.catch(error => {
const err = error.message
dispatch(fetchCategoryFailure(err))
})
}
}
const fetchCategoryRequest = () => {
return {
type: CATEGORIES_FETCH_REQUEST
}
}
const fetchCategorySuccess = categories => {
return {
type: CATEGORIES_SUCCESS,
payload: categories
}
}
const fetchCategoryFailure = err => {
return {
type: CATEGORIES_FAIL,
payload: err
}
}
I decide to reuse a component that I thought would work for my new application that is pulling in a third-party API.
The reusable component in question is iterating this.props.data.map() which is evaluating as being undefined in my components/Swipe.js file:
import React, { Component } from "react";
import {
View,
Animated,
PanResponder,
Dimensions,
LayoutAnimation,
UIManager
} from "react-native";
const SCREEN_WIDTH = Dimensions.get("window").width;
const SWIPE_THRESHOLD = 0.25 * SCREEN_WIDTH;
const SWIPE_OUT_DURATION = 250;
class Swipe extends Component {
static defaultProps = {
onSwipeRight: () => {},
onSwipeLeft: () => {}
};
constructor(props) {
super(props);
const position = new Animated.ValueXY();
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: (event, gestureState) => true,
onPanResponderMove: (event, gestureState) => {
position.setValue({ x: gestureState.dx, y: gestureState.dy });
},
onPanResponderRelease: (event, gestureState) => {
if (gestureState.dx > SWIPE_THRESHOLD) {
this.forceSwipe("right");
} else if (gestureState.dx < -SWIPE_THRESHOLD) {
this.forceSwipe("left");
} else {
this.resetPosition();
}
}
});
this.state = { panResponder, position, index: 0 };
}
componentWillReceiveProps(nextProps) {
if (nextProps.data !== this.props.data) {
this.setState({ index: 0 });
}
}
componentWillUpdate() {
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
LayoutAnimation.spring();
}
forceSwipe(direction) {
const x = direction === "right" ? SCREEN_WIDTH : -SCREEN_WIDTH;
Animated.timing(this.state.position, {
toValue: { x, y: 0 },
duration: SWIPE_OUT_DURATION
}).start(() => this.onSwipeComplete(direction));
}
onSwipeComplete(direction) {
const { onSwipeLeft, onSwipeRight, data } = this.props;
const item = data[this.state.index];
direction === "right" ? onSwipeRight(item) : onSwipeLeft(item);
this.state.position.setValue({ x: 0, y: 0 });
this.setState({ index: this.state.index + 1 });
}
resetPosition() {
Animated.spring(this.state.position, {
toValue: { x: 0, y: 0 }
}).start();
}
getCardStyle() {
const { position } = this.state;
const rotate = position.x.interpolate({
inputRange: [-SCREEN_WIDTH * 1.5, 0, SCREEN_WIDTH * 1.5],
outputRange: ["-120deg", "0deg", "120deg"]
});
return {
...position.getLayout(),
transform: [{ rotate }]
};
}
renderCards() {
console.log(this.props);
if (this.state.index >= this.props.data.length) {
return this.props.renderNoMoreCards();
}
return this.props.data
.map((item, i) => {
if (i < this.state.index) {
return null;
}
if (i === this.state.index) {
return (
<Animated.View
key={item[this.props.id]}
style={[this.getCardStyle(), styles.cardStyle]}
{...this.state.panResponder.panHandlers}
>
{this.props.renderCard(item)}
</Animated.View>
);
}
return (
<Animated.View
key={item[this.props.id]}
style={[styles.cardStyle, { top: 10 * (i - this.state.index) }]}
>
{this.props.renderCard(item)}
</Animated.View>
);
})
.reverse();
}
render() {
return <View>{this.renderCards()}</View>;
}
}
const styles = {
cardStyle: {
position: "absolute",
width: SCREEN_WIDTH
}
};
export default Swipe;
I am unclear why this is happening since I do get back a payload: data in my action creator:
export const fetchJobs = (region, callback) => async dispatch => {
try {
const url =
JOB_ROOT_URL +
JOB_QUERY_PARAMS.key +
"&method=" +
JOB_QUERY_PARAMS.method +
"&category=" +
JOB_QUERY_PARAMS.keyword +
"&format=" +
JOB_QUERY_PARAMS.format;
let { data } = await axios.get(url);
dispatch({ type: FETCH_JOBS, payload: data });
callback();
} catch (e) {
console.log(e);
}
};
So why is data evaluating as undefined in my reusable component?
It's being called here in DeckScreen.js:
import React, { Component } from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import { MapView } from "expo";
import { Card, Button } from "react-native-elements";
import Swipe from "../components/Swipe";
class DeckScreen extends Component {
renderCard(job) {
return (
<Card title={job.title}>
<View style={styles.detailWrapper}>
<Text>{job.company}</Text>
<Text>{job.post_date}</Text>
</View>
<Text>
{job.description.replace(/<span>/g, "").replace(/<\/span>/g, "")}
</Text>
</Card>
);
}
render() {
return (
<View>
<Swipe data={this.props.jobs} renderCard={this.renderCard} />
</View>
);
}
}
const styles = {
detailWrapper: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 10
}
};
function mapStateToProps({ jobs }) {
return { jobs: jobs.listing };
}
export default connect(mapStateToProps)(DeckScreen);
The button I am pressing that gives me this error is in the MapScreen screen:
import React, { Component } from "react";
import { View, Text, ActivityIndicator } from "react-native";
import { Button } from "react-native-elements";
import { MapView } from "expo";
import { connect } from "react-redux";
import * as actions from "../actions";
class MapScreen extends Component {
state = {
region: {
longitude: 30.2672,
latitude: 97.7431,
longitudeDelta: 0.04,
latitudeDelta: 0.09
}
};
onButtonPress = () => {
this.props.fetchJobs(this.state.region, () => {
this.props.navigation.navigate("deck");
});
};
getLocationHandler = () => {
navigator.geolocation.getCurrentPosition(pos => {
const currentCoords = {
longitude: pos.coords.longitude,
latitude: pos.coords.latitude
};
this.goToLocation(currentCoords);
});
};
goToLocation = coords => {
this.map.animateToRegion({
...this.state.region,
longitude: coords.longitude,
latitude: coords.latitude
});
this.setState(prevState => {
return {
region: {
...prevState.region,
longitude: coords.longitude,
latitude: coords.latitude
}
};
});
};
render() {
return (
<View style={{ flex: 1 }}>
<MapView
initialRegion={this.state.region}
style={{ flex: 1 }}
ref={ref => (this.map = ref)}
/>
<View style={styles.buttonContainer}>
<Button
title="Search This Area"
icon={{ name: "search" }}
onPress={this.onButtonPress}
/>
</View>
<View>
<Button
title="My Location"
icon={{ name: "map" }}
onPress={this.getLocationHandler}
/>
</View>
</View>
);
}
}
const styles = {
buttonContainer: {
position: "absolute",
bottom: 50,
left: 0,
right: 0
}
};
export default connect(
null,
actions
)(MapScreen);
This should be an array of objects as verified here:
And in my reducer I have:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listing: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
I added some verbose error handling and this is what I got back:
[02:25:28] fetchJobs Action Error: Given action "fetch_jobs", reducer
"jobs" returned undefined. To ignore an action, you must explicitly
return the previous state. If you want this reducer to hold no value,
you can return null instead of undefined.
So it seems like the problem is in the jobs_reducer:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listing: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
I don't know if I am just too exhausted at this point, but I have tried listings: [], I have tried listing: [], I am out of ideas of how to get this reducer to not return undefined because even when I do this:
import { FETCH_JOBS } from "../actions/types";
// const INITIAL_STATE = {
// listing: []
// };
export default function(state = null, action) {
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
I get the same error message.
My idea with creating an INITIAL_STATE and setting it to listing: [] is to ensure I could map over this array and never worry about the case where I have not yet fetched the list of jobs.
So I am perplexed as to exactly where I am getting this undefined since I did set the initial state to null and I was still getting that error.
So in the process of debugging I then tried this:
import { FETCH_JOBS } from "../actions/types";
// const INITIAL_STATE = {
// listing: []
// };
export default function(state = null, action) {
console.log("action is", action);
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
And got that the payload is undefined:
Please check your inputs.
[09:39:38] action is Object {
[09:39:38] "payload": undefined,
[09:39:38] "type": "fetch_jobs",
[09:39:38] }
I have hit a wall here. I did a whole refactor to my jobs action creator and logged out the payload property:
export const fetchJobs = (region, distance = 10) => async dispatch => {
try {
const url = buildJobsUrl();
let job_list = await axios.get(url);
job_list = locationify(
region,
console.log(job_list.data.listings.listing),
job_list.data.listings.listing,
distance,
(obj, coords) => {
obj.company.location = { ...obj.company.location, coords };
return obj;
}
);
dispatch({ type: FETCH_JOBS, payload: job_list });
} catch (e) {
console.log("fetchJobs Action Error:", e.message);
}
};
The console.log(job_list.data.listings.listing) logged out the data to my terminal successfully and yet my payload property is still undefined, how is that possible?
I got the action creator and reducer working by refactoring the action creator to just this:
import axios from "axios";
import { Location } from "expo";
import qs from "qs";
import { FETCH_JOBS } from "./types";
// import locationify from "../tools/locationify";
const JOB_ROOT_URL = "https://authenticjobs.com/api/?";
const JOB_QUERY_PARAMS = {
api_key: "<api_key>",
method: "aj.jobs.search",
perpage: "10",
format: "json",
keywords: "javascript"
};
const buildJobsUrl = zip => {
const query = qs.stringify({ ...JOB_QUERY_PARAMS });
return `${JOB_ROOT_URL}${query}`;
};
export const fetchJobs = (region, callback) => async dispatch => {
try {
let zip = await Location.reverseGeocodeAsync(region);
const url = buildJobsUrl(zip);
console.log(url);
let { data } = await axios.get(url);
dispatch({ type: FETCH_JOBS, payload: data });
callback();
} catch (e) {
console.error(e);
}
};
So the problem is no longer there in theory, right. Then, when I bring in the Swipe.js component, the problem returns, in particular the problem seems to be with this code here:
renderCards() {
if (this.state.index >= this.props.data.length) {
return this.props.renderNoMoreCards();
}
return this.props.data
.map((item, i) => {
if (i < this.state.index) {
return null;
}
if (i === this.state.index) {
return (
<Animated.View
key={item[this.props.id]}
style={[this.getCardStyle(), styles.cardStyle]}
{...this.state.panResponder.panHandlers}
>
{this.props.renderCard(item)}
</Animated.View>
);
}
return (
<Animated.View
key={item[this.props.id]}
style={[styles.cardStyle, { top: 10 * (i - this.state.index) }]}
>
{this.props.renderCard(item)}
</Animated.View>
);
})
.reverse();
}
This is where I start to hit a roadblock again.
Props are not available immediatelly from redux store on render, it comes asyncrounously.
To select data from redux store better to use save navigation:
const mapStateToProps = state => ({
jobs: state && state.jobs && state.jobs.listing
})
Than again on render to check if data exists or not:
...
render() {
const { jobs } = this.props;
return (
<View>
{jobs && <Swipe data={jobs} renderCard={this.renderCard} />}
</View>
}
...
renderCards() {
const { data } = this.props;
return data && data.map((item, index) => {
...
map function generally iterate through array-object. You are trying to iterate through a non-array object. So first check the type of the object using typeof(variable) then use the function.
Looks like what helped was refactoring my jobs_reducer file from:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listing: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
to this:
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
const { listings } = action.payload;
return { ...state, listing: listings.listing };
default:
return state;
}
}
all I am fairly new to React, and Redux and have been stuck with this issue for an entire day now. The data is dispatching from my component to my action creator, then to my reducer, and the state is being updated. However, when I change inputs and begin typing, it clears all other data in the form except for the data of the input I am currently typing in. If I take out the spread operator, the data then stays, but from every tutorial, I have seen this should not happen. Am I doing something wrong?
AddProject.js (form component)
import React, { useEffect } from "react";
import styles from "./AddProjects.module.css";
import { connect } from "react-redux";
import {
validateProjectId,
validateProjectDescription,
validateProjectName,
projectStartDate,
projectEndDate,
submitHandler
} from "../../Redux/createProject/action";
const AddProject = props => {
// useEffect(() => {
// console.log("aaa", props);
// }, [props]);
return (
<div className={styles.addProjectContainer}>
<h5>Create / Edit Project form</h5>
<hr />
<form>
<div>
<input
defaultValue=""
type="text"
placeholder="Project Name"
name="projectName"
style={
props.form.projectNameError
? { backgroundColor: "#F08080", opacity: "0.8" }
: { backgroundColor: "white" }
}
onChange={e => props.validateProjectName(e.target.value)}
/>
</div>
<div>
<input
type="text"
placeholder="Unique Project ID"
name="projectIdentifier"
value={props.form.projectIdentifier}
style={
props.form.projectIdentifierError
? { backgroundColor: "#F08080", opacity: "0.8" }
: { backgroundColor: "white" }
}
onChange={e => props.validateProjectId(e.target.value)}
/>
</div>
<div>
<textarea
placeholder="Project Description"
name="description"
value={props.form.description}
style={
props.form.descriptionError
? { backgroundColor: "#F08080", opacity: "0.8" }
: { backgroundColor: "white" }
}
onChange={e => props.validateProjectDescription(e.target.value)}
/>
</div>
<h6>Start Date</h6>
<div>
<input
type="date"
name="start_date"
value={props.form.start_date}
onChange={e => props.projectStartDate(e.target.value)}
/>
</div>
<h6>Estimated End Date</h6>
<div>
<input
type="date"
name="end_date"
value={props.form.end_date}
onChange={e => props.projectEndDate(e.target.value)}
/>
</div>
<button type="button" onClick={props.submitHandler}>
<span>Submit</span>
</button>
</form>
</div>
);
};
//state.form.projectName
const mapStateToProps = state => {
console.log(state.project);
return {
form: state.project
};
};
const mapDispatchToProps = dispatch => {
return {
validateProjectName: payload => dispatch(validateProjectName(payload)),
validateProjectId: payload => dispatch(validateProjectId(payload)),
validateProjectDescription: payload =>
dispatch(validateProjectDescription(payload)),
projectStartDate: payload => dispatch(projectStartDate(payload)),
projectEndDate: payload => dispatch(projectEndDate(payload)),
submitHandler: () => dispatch(submitHandler())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(AddProject);
action.js (action creator)
import {
PROJECT_NAME_CHANGE,
PROJECT_IDENTIFIER_CHANGE,
PROJECT_DESCRIPTION_CHANGE,
START_DATE_CHANGE,
END_DATE_CHANGE,
SUBMIT_HANDLER,
PROJECT_NAME_ERROR,
PROJECT_IDENTIFIER_ERROR,
PROJECT_DESCRIPTION_ERROR
} from "./constants";
export const projectNameChange = projectName => {
return {
type: PROJECT_NAME_CHANGE,
projectName
};
};
export const projectNameError = () => {
return {
type: PROJECT_NAME_ERROR
};
};
export const projectIdChange = projectIdentifier => {
return {
type: PROJECT_IDENTIFIER_CHANGE,
projectIdentifier
};
};
export const projectIdError = () => {
return {
type: PROJECT_IDENTIFIER_ERROR
};
};
export const projectDescriptionChange = description => {
return {
type: PROJECT_DESCRIPTION_CHANGE,
description
};
};
export const projectDescriptionError = () => {
return {
type: PROJECT_DESCRIPTION_ERROR
};
};
export const projectStartDate = start_date => {
return {
type: START_DATE_CHANGE,
start_date
};
};
export const projectEndDate = end_date => {
return {
type: END_DATE_CHANGE,
end_date
};
};
export const submitHandler = () => {
return {
type: SUBMIT_HANDLER
};
};
export function validateProjectName(payload) {
return (dispatch, getState) => {
if (payload.length <= 30) {
dispatch(projectNameChange(payload));
} else {
dispatch(projectNameError());
}
};
}
export function validateProjectId(payload) {
return (dispatch, getState) => {
if (payload.length < 6) {
dispatch(projectIdChange(payload));
} else {
dispatch(projectIdError());
}
};
}
export function validateProjectDescription(payload) {
return (dispatch, getState) => {
if (payload.length < 256) {
dispatch(projectDescriptionChange(payload));
} else {
dispatch(projectDescriptionError());
}
};
}
// thunk call passed project name
// validateProjectName(name){
// if(name.length>4 && ){
// dispatchEvent(setName)
// }
// else{
// dispatch(setNameError)
// }
// }
index.js (Reducer)
PROJECT_NAME_CHANGE,
PROJECT_IDENTIFIER_CHANGE,
PROJECT_DESCRIPTION_CHANGE,
START_DATE_CHANGE,
END_DATE_CHANGE,
SUBMIT_HANDLER,
PROJECT_NAME_ERROR,
PROJECT_IDENTIFIER_ERROR,
PROJECT_DESCRIPTION_ERROR
} from "./constants";
const initialState = {
projectName: "",
projectIdentifier: "",
description: "",
start_date: "",
end_date: "",
projectNameError: false,
projectIdentifierError: false,
descriptionError: false
};
const createProjectReducer = (state = initialState, action) => {
switch (action.type) {
case PROJECT_NAME_CHANGE:
// console.log("We changed project name!", state.projectName, action);
return {
...state,
projectName: action.projectName
};
case PROJECT_IDENTIFIER_CHANGE:
// console.log("We changed project id!", state, action.projectIdentifier);
return {
...state,
projectIdentifier: action.projectIdentifier,
projectIdentifierError: false
};
case PROJECT_DESCRIPTION_CHANGE:
// console.log("We changed project description", state, action.description);
return { ...state, description: action.description };
case START_DATE_CHANGE:
// console.log("We changed the start date", state, action.payload);
return { ...state, start_date: action.payload };
case END_DATE_CHANGE:
// console.log("We changed the end date", state, action.payload);
return { ...state, end_date: action.payload };
case PROJECT_NAME_ERROR:
// console.log("There was an error with the project name!", state);
return { ...state, projectNameError: true };
case PROJECT_IDENTIFIER_ERROR:
// console.log("There was an error with the project Id!", state);
return { projectIdentifierError: true };
case PROJECT_DESCRIPTION_ERROR:
// console.log("There was an error with the project description!", state);
return { ...state, descriptionError: true };
case SUBMIT_HANDLER:
console.log("We submitted yayy", state);
return initialState;
//const formData = state;
//console.log(formData);
default:
return state;
}
};
export default createProjectReducer;
constants.js
export const PROJECT_IDENTIFIER_CHANGE = "PROJECT_IDENTIFIER_CHANGE";
export const PROJECT_DESCRIPTION_CHANGE = "PROJECT_DESCRIPTION_CHANGE";
export const START_DATE_CHANGE = "START_DATE_CHANGE";
export const END_DATE_CHANGE = "END_DATE_CHANGE";
export const SUBMIT_HANDLER = "SUBMIT_HANDLER";
export const PROJECT_NAME_ERROR = "PROJECT_NAME_ERROR";
export const PROJECT_IDENTIFIER_ERROR = "PROJECT_IDENTIFIER_ERROR";
export const PROJECT_DESCRIPTION_ERROR = "PROJECT_DESCRIPTION_ERROR";
rootReducer.js
const rootReducer = (state = {}, action) => {
return {
project: createProjectReducer(state.createProject, action)
};
};
export default rootReducer;
index.js (store creator)
import ReactDOM from "react-dom";
import { createStore, compose, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import thunkMiddleware from "redux-thunk";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import rootReducer from "./Redux/rootReducer";
const composeEnhancers =
(typeof window !== "undefined" &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
compose;
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(thunkMiddleware))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
serviceWorker.unregister();
In redux reducer you should always return the entire new version of the state, not just the updated parameter
exemple
return Object.assign({},prevState,{field: action.value});
(We don't see your reducer in your post but I guess that this is the problem)
Good documentation here - https://redux.js.org/basics/reducers/
Using redux to manage the state of a component is very bad practice. What you should do instead is to use useState to save the state of each of your inputs and control them with the onChange
Redux should be used ONLY for variables which are UI related and which have to be transported between multiple components all around the website.
https://reactjs.org/docs/hooks-state.html for more information
You need to know that everytime you update your redux store, the store is first copied (fully) then the new values are added and then the current store is replaced by the new one. Which drives to a lot of performance issues and memory leak.
The error is in your store creation
project: createProjectReducer(state.createProject, action)
should be
project: createProjectReducer(state.project, action)
The state is lost by not being passed to the sub reducer