I have been using Redux for the past two days, i'm getting to understand it more, however I encountered a problem which has stopped my progress.
I have an API which has interchangeable parameters.
e.g. api.example.com/data/{date}/.. and api.example.com/more-data/{regId}/..
My <Picker /> selects a value and that value should be passed to the URL, which calls the API and gives the selected data; in my case regionId.
The problem is changing the params without causing errors or getting CORS problem with the Api call. I also want to be able to set the regionId to have an initialState, so I can begin the request with a parameter in the url.
ReqService.js (just for async api calling)
class ReqService {
async getRequest(url) {
try {
let response = await (await fetch(url));
let responseJson = await response.json();
return responseJson;
} catch (error) {
console.error('Error: ', error);
}
}
}
export default new ReqService()
actions.js
import ReqService from '../ReqService';
export const IS_FETCHING = 'IS_FETCHING';
export const DATA_FETCHED = 'DATA_FETCHED';
export const ERROR_FETCHING_DATA = 'ERROR_FETCHING_DATA';
const BASE_URL = 'https://api.example.com/';
const DATE_TODAY = new Date().toISOString();
export const getTheData = (regionId) => {
// The regionId is the param i want to pass to the url
const url = `${BASE_URL}/${DATE_TODAY}/${regionId}`;
const request = ReqService.getRequest(url);
return dispatch => {
dispatch({ type: IS_FETCHING });
request
.then((data ) => {
dispatch({ type: DATA_FETCHED, payload: data });
})
.catch(error => {
dispatch({ type: ERROR_FETCHING_DATA, payload: error });
});
};
};
reducer.js
import { IS_FETCHING, DATA_FETCHED, ERROR_FETCHING_DATA } from '../Actions/actions';
const initialState = {
data: [],
fetching: false,
fetched: false,
error: null
};
export const myReducer = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case IS_FETCHING:
return { ...state, fetching: true };
case DATA_FETCHED:
console.log('The Data Fetched ', action.payload);
return {
...state,
fetched: true,
fetching: false,
data: action.payload.data
};
case ERROR_FETCHING_DATA:
return { ...state, fetching: false, error: action.payload.error };
default:
return state;
}
};
The component where the param changes here:
import React, { Component } from 'react'
import {View, Text, Picker} from 'react-native'
import { connect } from '../../node_modules/react-redux';
import { getTheData } from './Actions/actions';
import { bindActionCreators } from "redux";
class FrontPage extends Component {
constructor(props){
super(props);
this.state = {
regionId:0
};
}
changeRegion = (regId) => {
this.props.getTheData(regId);
}
componentDidMount() {}
render() {
return (
<View>
<Text>Front Page</Text>
<Picker selectedValue={this.props.regionId}
onValueChange={itemValue => this.changeRegion(itemValue)}>
<Picker.Item label="One" value='1' />
<Picker.Item label="Two" value='2' />
</Picker>
</View>
)
}
}
const mapStateToProps = state => {
return {
data: state.data,
fetching: state.fetching,
error: state.error
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ getTheData }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(FrontPage);
I dont know if I am doing this correct, I looked at different examples and implemented what seems right. Any help will be great.
From what you are sharing it looks like a good implementation of React and Redux.
If you'd like the Picker component initially have a selected value, then set your state to what it should be. In your case, set the state regionId in your FrontPage component.
this.state = {
regionId: 1 // this will pre-select the first value.
};
"The problem is changing the params without causing errors or getting CORS problem with the Api call."
I'm unsure which problems you have when the params are changed. Can you elaborate or include a screenshot?
As for the CORS error message. Have a look at the article How to fix CORS problems to gain a better understanding of it and what you need to change. When getting this error the problem isn’t in the client application but in the server application. To fix it, you need to enable CORS support at the server level.
You can do this by setting the Access-Control-Allow-Origin header. e.g.
Access-Control-Allow-Origin: *
This will allow any host to access the API, even when they are on a different domain or post.
Related
Hello, I am new to redux and I am struggling with a problem. I am trying to access and map over the comments within my post array. However, I am not sure how to do this. So far, I've tried changing the actions and reducers in order to solve this issue. I think the problem is within the react and redux. I can't tell if my mapStateToProps is working correctly. Also, the state is being fetched from my express server and it seems to be working properly as you can see in the picture.
My getPost action:
export const getPost = (group_id, post_id) => async dispatch => {
try {
const res = await axios.get(`/api/groups/${group_id}/${post_id}`);
dispatch({
type: GET_POST,
payload: res.data
});
} catch (error) {
dispatch({
type: POST_ERROR,
payload: { msg: error.response.statusText, status: error.response.status }
});
}
};
The initial state:
const initialState = {
groups: [],
group: [],
loading: true,
error: {}
};
The reducer:
case GET_POST:
return {
...state,
post: payload,
loading: false
};
Where I'm trying to map over the comments:
import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getPost } from '../../../redux/actions/group';
const Post = ({ getPost, post, match }) => {
useEffect(() => {
getPost(match.params.group_id, match.params.post_id);
}, [getPost, match.params.group_id, match.params.post_id]);
// I want to map over the comments here
return (
{post.comments.map(comment => ({ comment }))}
);
};
Post.propTypes = {
getPost: PropTypes.func.isRequired,
group: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
post: state.post
});
export default connect(mapStateToProps, { getPost })(Post);
You can access nested object with some tricks using redux, we have use this way in our prod env for some time.
First the reducer (you can make this reducer even more complex)
const LocalStorageReducer = createReducer<Store['localStorage']>(
new LocalStorage(),
{
saveLocalStorageItem(state: LocalStorage, action: any) {
return {...state, [action.payload.item]: action.payload.value}; // <= here
},
}
);
For Actions
export const actions = {
saveLocalStorageItem: (payload: InputAction) => ({type: 'saveLocalStorageItem', payload}),
};
For the type InputAction
export class InputAction {
item: string;
value: string | Array<string> | null | boolean;
constructor() {
this.item = '';
this.value = null;
}
}
For the handler in component
this.props.saveLocalStorage({ item: 'loading', value: false });
In this way you can go one way done to the nested redux store.
For complex (4-5 levels) and multiple (> 2 times) data structure, there are other ways, but in most situations, it's good enough.
First, I made a small application on the React.js. Using the fetch method, I take the API
And these are the main files of my application:
Index.js:(action)
export const SHOW_AIRPLANES = "SHOW_AIRPLANES";
export function showAirplanes() {
return (dispatch, getState) => {
fetch("https://api.iev.aero/api/flights/25-08-2019").then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.data });
});
};
}
airplanes.js:(reducer)
import { SHOW_AIRPLANES } from '../actions'
const initialState = {
list: []
}
export function showAirplanes(state = initialState, action) {
switch (action.type) {
case SHOW_AIRPLANES:
return Object.assign({}, state, {list: action.payload})
default:
return state
}
}
index.js(reducer):
import { combineReducers } from "redux";
import { showAirplanes } from "./airplanes";
const rootReducer = combineReducers({
user: showAirplanes
});
export default rootReducer;
First, you should use the createStore function like so:
const initialData = {}; // whatever you want as initial data
const store = createStore(reducers, initialData, applyMiddleware(thunk));
Then pass it to your provider
<Provider store={store}>
{...}
</Provider
next, when you map your reducers inside the combineReducers function, each key in this object represents a piece of your state. So when you do user: showAirplanes it means that you intend to use it in the mapStateToProps with state.user.list so I think you meant to call it airplane: showAirplanes.
Then, your reducer name is not informative enough, I would suggest to change it to airplanesReducer.
Next issue, the call to fetch returns a response that has JSON that must be resolved.
Change this:
fetch("https://api.iev.aero/api/flights/25-08-2019").then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.data });
});
To this:
fetch("https://api.iev.aero/api/flights/25-08-2019")
.then(res => res.json())
.then(response => {
dispatch({ type: SHOW_AIRPLANES, payload: response.body.departure });
});
Note that I've changed the value that you need to resolve from the response as well.
Inside your App.js component you need to create a constructor and bind the renderAirplaneList function to this
// Inside the App class
constructor(props) {
super(props);
this.renderAirplaneList = this.renderAirplaneList.bind(this);
}
And finally (I hope I didn't miss anything else), you map your state in the App.js component to { airplanes: state.airplanes.list} so the name of the prop you expect inside your component is props.airplanes.
renderAirplaneList() {
if (!this.props.airplanes.length) {
return null;
}
const arr = this.props.airplanes || [];
return arr.map(airplane => {
return (
<tr key={airplane.id}>
<td>{airplane.ID}</td>
<td>{airplane.term}</td>
<td>{airplane.actual}</td>
<td>{airplane["airportToID.city_en"]}</td>
</tr>
);
});
}
Make sure you go over the documentation of React and Redux, they have all the information you need.
Good luck.
aren't you suppose to send some parameters to this call?
this.props.showAirplanes()
it seems that it has 2 parameters: state and action, although state seems to have already it's default value
TypeError: Cannot read property '' of undefined ı have no idea why ı am getting this error while I do check the code below everything seems fine :( trying to learn the way how react works :)
So what is the purpose of this since all the properties I wrap on contextprovider suchas contacts loading and the functions I need
import React, { useState, useContext } from 'react'
import ContactContext from '../context/contactContext'
export default function ContactForm() {
const name = useFormInput('')
const email = useFormInput('')
const contactContext = useContext(ContactContext)
const { addContact } = contactContext
const onSubmit = () => {
addContact(name.value, email.value)
name.onReset()
email.onReset()
}
return (
SOME HTML CODE HERE
)
}
//contactState.js
import React, { useReducer } from 'react'
import _ from 'lodash'
import ContactContext from './contactContext'
import ContactReducer from './contactReducer'
const ContactState = props => {
const initialState = {
contacts: [
{
id: '098',
name: 'Diana Prince',
email: 'diana#us.army.mil'
}
],
loading: false,
error: null
}
const [state, dispatch] = useReducer(ContactReducer, initialState)
const [contacts, loading] = state
const addContact = (name, email) => {
dispatch({
type: 'ADD_CONTACT',
payload: { id: _.uniqueId(10), name, email }
})
}
const delContact = id => {
dispatch({
type: 'DEL_CONTACT',
payload: id
})
}
return (
<ContactContext.Provider
value={{
contacts,
loading,
addContact,
delContact
}}
>
{props.children}
</ContactContext.Provider>
)
}
export default ContactState
//contactReducer.js
export default (state, action) => {
switch (action.type) {
case 'ADD_CONTACT':
return {
contacts: [...state, action.payload]
}
case 'DEL_CONTACT':
return {
contacts: state.contacts.filter(
contact => contact.id !== action.payload
)
}
case 'START':
return {
loading: true
}
case 'COMPLETE':
return {
loading: false
}
default:
throw new Error()
}
}
//contactContext.js
import { createContext } from 'react'
const contactContext = createContext()
export default contactContext
In your reducer, when adding a contact, you're spreading the wrong state key. This should fix it:
case 'ADD_CONTACT':
return {
contacts: [...state.contacts, action.payload]
}
I can't see where you are using ContactState in your app. If you don't use it and render your ContactForm component with it then you can't reach any context value. You should render it as:
<ContactState>
<ContactForm />
</ContactState>
in a suitable place in your app. Also, you can't get contacts and loading like that:
const [ contacts, loading ] = state;
state is not an array, it is an object here. You should use:
const { contacts, loading } = state
You can find a simplified version of your code below. I removed/changed some parts in order to run it as much as possible. You should fix your reducer as #Asaf David mentioned in their answer, but this is not the main problem here. After fixing the context issue, you can try to fix your reducer.
About your questions, if you try to understand how React works by looking at this example you can easily get confused. Because Context is an advanced concept (at least for the beginners). Also, the code uses useReducer with Context and this makes the things more complicated. If your intent is to understand the React itself then start with the beginner guide.
By using Context we can pass the data top-down to the deepest components. But, in order to use that data those components should be rendered as children of the context provider.
In your code, you are doing this in ContactState but you never use it. Also, in that component, you are defining a state with useReducer and feed your context with this state by value.
Finally, in your ContactForm component, you are using useContext hook to get the context data. In your current code since you don't render this component in a provider, contactContext is undefined and you are getting the error. You can't get addContact from undefined.
In my example, I'm retrieving the contacts to show something. Again, I've changed/removed some parts from your code.
const { createContext, useContext, useReducer } = React;
const ContactContext = createContext();
function ContactForm() {
// Changed those values
const name = "";
const email = "";
const contactContext = useContext(ContactContext);
// changed addContact -> contacts
const { contacts } = contactContext;
const onSubmit = () => {
addContact(name.value, email.value);
name.onReset();
email.onReset();
};
// added some data to render
return <div>{contacts[0].name}</div>;
}
function ContactReducer(state, action) {
switch (action.type) {
case "ADD_CONTACT":
return {
contacts: [...state, action.payload]
};
case "DEL_CONTACT":
return {
contacts: state.contacts.filter(
contact => contact.id !== action.payload
)
};
case "START":
return {
loading: true
};
case "COMPLETE":
return {
loading: false
};
default:
throw new Error();
}
}
const ContactState = props => {
const initialState = {
contacts: [
{
id: "098",
name: "Diana Prince",
email: "diana#us.army.mil"
}
],
loading: false,
error: null
};
const [state, dispatch] = useReducer(ContactReducer, initialState);
const { contacts, loading } = state;
const addContact = (name, email) => {
dispatch({
type: "ADD_CONTACT",
// removed lodash part, added a static id
payload: { id: 1, name, email }
});
};
const delContact = id => {
dispatch({
type: "DEL_CONTACT",
payload: id
});
};
return (
<ContactContext.Provider
value={{
contacts,
loading,
addContact,
delContact
}}
>
{props.children}
</ContactContext.Provider>
);
};
// added the relevant render part
ReactDOM.render(
<ContactState>
<ContactForm />
</ContactState>,
document.getElementById("root")
);
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root" />
In the last couple of days I have been working on my Redux api call. I am actually having a problem getting the data back to the view component. Currently I'm able to see the data in the in the action generator, so I know at least I'm able to get it. However, nothing is showing in the view. I imagine it may have something to do with when it's loading. This is why I tried to load it when the component is rendering.
https://djangoandreact.herokuapp.com/user/1 is what is not loading.
codesandbox: https://codesandbox.io/s/zlor60q3jm?from-embed
Should be able to go to /user/1 at the end similar to going to /1 brings up an article(Tough Hope)
Heres the view component:
import React from "react";
import { connect } from "react-redux";
import { fetchUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
const userID = this.props.match.params.userID;
fetchUser(userID); //fixed
}
render() {
const { user } = this.props.user;
console.log(user);
return (
<div>
<h3>{user.username}</h3>
</div>
);
}
}
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchUser: dispatch(fetchUser(ownProps.match.params.userID))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserDetailView);
Action generator
import axios from "axios";
import { thunk } from "react-redux";
export function fetchUser(userID) {
console.log(userID);
return dispatch => {
return axios.get(`/api/user/${userID}`).then(res => {
dispatch(fetchUserSuccess(res.data));
console.log(res.data); // loads data
});
};
}
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const FETCH_USER_BEGIN = "FETCH_USER_BEGIN";
export const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
export const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";
export const fetchUserBegin = () => ({
type: FETCH_USER_BEGIN
});
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: { user }
});
export const fetchUserFailure = error => ({
type: FETCH_USER_FAILURE,
payload: { error }
});
Reducers(which are probably fine):
import {
FETCH_USER_BEGIN,
FETCH_USER_SUCCESS,
FETCH_USER_FAILURE
} from "../actions/actionTypes";
const initialState = {
user: {},
loading: false,
error: null
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case FETCH_USER_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
case FETCH_USER_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
user: {}
};
default:
return state;
}
}
folks. I found it.
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
user is supposed to be user:action.payload
Also, the user action was supposed to be
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: user
})
WOOOOW. But, honestly, I learned so much about Redux in the last two sleepless nights, it was worth the pain. Really was. Now, instead of copy pasta, I know what an action generator is and does, and reducer (obvi)
I'm loading data from an API using Redux & React. Despite successfully pulling the data and applying it to the state, it's throwing an error:
Uncaught TypeError: Cannot read property 'payload' of undefined.
This occurs after the FETCH_PRODUCT_LISTINGS_PENDING action type in the console.
React Component:
import React from 'react';
import { connect } from 'react-redux';
import store from '../../../store';
import * as ProductListingActions from '../actions/ProductListingActions';
#connect((store) => {
return {
productListing: store.productListing.products
}
})
export default class ProductListingContainer extends React.Component {
constructor(data) {
super();
this.props = data;
this.props.dispatch(ProductListingActions.fetchProductListings());
}
render() {
return <div></div>;
}
}
Reducer:
import CookieHandler from '../../../modules/CookieHandler';
const cookieHandler = new CookieHandler;
export default function reducer(
state = {
products: [],
fetching: false,
fetched: false,
error: null
}, action) {
switch(action.type) {
case "FETCH_PRODUCT_LISTINGS_PENDING":
return {
...state,
fetching: true,
}
break;
case "FETCH_PRODUCT_LISTINGS_REJECTED":
return {
...state,
fetching: false,
error: action.payload
}
break;
case "FETCH_PRODUCT_LISTINGS_FULFILLED":
return {
...state,
fetching: false,
fetched: true,
products: action.payload.data.default
}
break;
}
return state;
}
Actions:
import Config from '../../../Config.js';
import store from '../../../store.js';
import axios from 'axios';
export function fetchProductListings() {
store.dispatch({
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
})
}
Any help would be appreciated
You're dispatching a call to dispatch, rather than dispatching an object.
this.props.dispatch(ProductListingActions.fetchProductListings());
function fetchProductListings() {
store.dispatch({
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
})
}
if you inline this:
this.props.dispatch(
store.dispatch({
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
})
)
Your action creator should not be calling dispatch, it should just return an action:
export function fetchProductListings() {
return {
type: "FETCH_PRODUCT_LISTINGS",
payload: axios.get(Config.getConfigAPIUrl() + '/cartel/products')
}
}
Keep in mind though, axios.get is asynchronous, so payload will be promise. You may want to consider adding redux-thunk to handle the fulfillment of the promise.
I was recently using redux-toolkit for fetching api, and I faced the same problem. When I checked the api result, I saw my payload value was undefined.
I solved this problem by simply returning the result of my api data.
export const getPosts = createAsyncThunk("posts/getPosts", async ()=> {
const res = await axios.get(`${baseURL}/posts/1`)
return res.data;
});