i am working on React,Redux and Redux-sagas, am getting infinite loop on the appliaition, please help out to fix this issue.
Item.js
import React from "react";
import ReactDOM from "react-dom";
import { Link } from "react-router-dom";
import { gateway as MoltinGateway } from "#moltin/sdk";
import getList from "./../Action/Action";
import { connect } from "react-redux";
//import data from "./data";
export class Item extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.pickItem = this.pickItem.bind(this);
}
pickItem(pickedItem, id) {
//this.props.getList();
//pickedItem.push(id);
//this.setState({ pickItem: pickedItem });
}
componentWillMount() {
this.props.getList();
}
render() {
const { pickedItem } = this.state;
//const data = this.props.getList()
console.log(this.props);
return (
<div className="ItemPage">
<header>
<h1>Online shopping</h1>
<h2>Visit | Pick | Pay</h2>
</header>
<div
onClick={this.pickItem.bind(this, pickedItem, 2)}
className="item-list"
>
<div className="logoWarapper">
<img
src="https://rukminim1.flixcart.com/image/660/792/jmdrr0w0/shirt/q/q/r/xxl-tblwtshirtful-sh4-tripr-original-imaf9ajwb3mfbhmh.jpeg?q=50"
width="100"
height="100"
alt=""
/>
</div>
<div className="itemWarapper">
<h3>Item Name</h3>
<p>
<span>₹</span>
<span>3000</span>
</p>
</div>
</div>
<div onClick={this.pickItem} className="item-list">
<div className="logoWarapper">
<img
src="https://rukminim1.flixcart.com/image/660/792/jmdrr0w0/shirt/q/q/r/xxl-tblwtshirtful-sh4-tripr-original-imaf9ajwb3mfbhmh.jpeg?q=50"
width="100"
height="100"
alt=""
/>
</div>
<div className="itemWarapper">
<h3>Item Name</h3>
<p>
<span>₹</span>
<span>3000</span>
</p>
</div>
</div>
<Link to="/payment">
<button className="button">Make Payment</button>
</Link>
</div>
);
}
}
const mapStateToProps = state => ({
list: state.list
});
const mapDispatchToProps = {
getList
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Item);
Action JS
export const ADD_TODO = "GET_LIST";
export const getList = () => ({
type: "GET_LIST"
});
export default getList;
Reducer JS
const Reducer = (state = [], action) => {
switch (action.type) {
case "GET_LIST":
return [
...state,
{
list: action
}
];
default:
return state;
}
};
export default Reducer;
Sagas JS
import { put, takeLatest, all, call } from "redux-saga/effects";
function* fetchNews() {
const json = yield fetch(
"https://api.themoviedb.org/3/movie/550?api_key=258ca659445121cb5d52f31961635ba7"
).then(response => response.json());
yield put({ type: "GET_LIST", json: json.articles });
}
function* actionWatcher() {
yield takeLatest("GET_LIST", fetchNews);
}
export default function* rootSaga() {
yield all([actionWatcher()]);
}
This API which used in the Sagas will get the list of movies. so i was to get the list of movies when Item.js components rendered. currently, it's seems infinite loop on the application
You're putting same action from saga, which you are "watching" for.
Usually, you should have some action with typeGET_LIST_REQUEST for dispatching from your component, and then, put action with type GET_LIST_SUCCESS from saga to get it in reducer.
So, your Action JS should looks like:
export const ADD_TODO_REQUEST = "GET_LIST_REQUEST";
export const ADD_TODO_SUCCESS = "GET_LIST_SUCCESS";
export const getList = () => ({
type: "GET_LIST_REQUEST"
});
export default getList;
Your Reducer
const Reducer = (state = [], action) => {
switch (action.type) {
case "GET_LIST_SUCCESS":
return {
...state,
list: action.json
};
default:
return state;
}
};
export default Reducer;
Your Saga
import { put, takeLatest, all, call } from "redux-saga/effects";
function* fetchNews() {
const json = yield fetch(
"https://api.themoviedb.org/3/movie/550?api_key=258ca659445121cb5d52f31961635ba7"
).then(response => response.json());
yield put({ type: "GET_LIST_SUCCESS", json: json.articles });
}
function* actionWatcher() {
yield takeLatest("GET_LIST_REQUEST", fetchNews);
}
export default function* rootSaga() {
yield all([actionWatcher()]);
}
Related
I'm making a MERN stack online store website and I'm fetching my products from a useEffect hook in my Shoes.js component. But I'm only getting the initial state from redux instead of the updated state.
The data is being fetched just fine but I can only access the initial state. So the values being passed to the ProductsArea component are false and null How do I get the updated state?
Here's my Shoes.js file:
import React, { useEffect } from 'react';
import './Shoes.css';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getProducts } from '../../../actions/productsActions';
import ProductsArea from './ProductsArea';
import Navbar from '../landing/Navbar';
import Search from './Search';
export const Shoes = (props) => {
useEffect(() => {
props.getProducts();
console.log(props.products);
console.log(props.loading);
}, []);
if(props.loading) {
return (
<h1>loading</h1>
)
}
else {
return (
<div>
<Navbar />
<div className="shoes">
<Search />
<h1 className="productsTitle">Our Selection</h1>
<ProductsArea loading={props.loading} products={props.products} />
{/* {
props.products.map(product => (
<ProductCard key={product._id} product={product} />
))
} */}
</div>
</div>
)
}
}
const mapStateToProps = state => ({
products: state.products.products,
loading: state.products.loading
})
export default connect(mapStateToProps, { getProducts })(Shoes);
Here's my productsActions file
import {GET_PRODUCTS, SET_LOADING, SET_ERROR} from './types';
export const getProducts = () => async (dispatch) => {
try{
setLoading();
const res = await fetch('http://localhost:5000/products');
const data = await res.json();
console.log(data);
dispatch({
type: GET_PRODUCTS,
payload: data
});
}
catch(err) {
dispatch({
type: SET_ERROR,
payload: err
})
}
}
export const setLoading = () => {
console.log('Loading true');
return {
type: SET_LOADING
}
}
This is the getProductsReducer file:
import {GET_PRODUCTS, SET_LOADING, SET_ERROR} from '../actions/types';
const initialState = {
products: [],
loading: false,
error: null
}
export default (state = initialState, action) => {
switch (action.type) {
case GET_PRODUCTS:
console.log(action.payload);
return {
...state,
products: action.payload,
loading: false
}
case SET_LOADING:
return {
...state,
loading: true
};
case SET_ERROR:
console.log(action.payload);
return {
...state,
error: action.payload
};
default: return state;
}
}
Here's my index.js file for redux :
import {combineReducers} from 'redux';
import getProductReducer from './getProductReducer';
export default combineReducers({
products: getProductReducer
});
And the Store.js file:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const initialState = {};
const middleware = [thunk];
const store = createStore(rootReducer, initialState, composeWithDevTools(applyMiddleware(...middleware)));
export default store;
So I checked the redux extension and the state is showing up on my Home.js page but not on the Shoes.js file
Here's the Home.js file:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { getProducts, setLoading } from '../../../actions/productsActions';
import { connect } from 'react-redux';
import {Link} from 'react-router-dom';
import './Home.css';
import Navbar from './Navbar';
export const Home = (props) => {
useEffect(() => {
props.setLoading();
props.getProducts();
//eslint-disable-next-line
console.log(props.products);
console.log(props.loading);
}, []);
if(props.loading) {
return <div>loading</div>
}
else {
return (
<div>
<Navbar />
<div className="home">
<div className="group-1">
<div className="branding">
<div className="brandName">
The
<br/>
Sole
<br/>
Store
</div>
<div>
<p>The finest designs and fits.</p>
</div>
</div>
<div className="viewProducts">
<div>
<p>
Check out our latest and greatest models
</p>
<Link className="productsBtn" to="/shoes">GO <i className="fas fa-arrow-right"/></Link>
</div>
</div>
</div>
<div className="group-2">
<div className="products">
<div className="product"></div>
<div className="product"></div>
<div className="product"></div>
<div className="product"></div>
</div>
<div className="something"></div>
</div>
</div>
</div>
)
}
}
Home.propTypes = {
products: PropTypes.object.isRequired,
loading: PropTypes.bool.isRequired
}
const mapStateToProps = state => ({
products: state.products.products,
loading: state.products.loading
});
export default connect(mapStateToProps, {getProducts, setLoading})(Home);
Although, I'm still only getting the initial state and not the updated state in the console from Home.js too.
I've made the changes that #Kalhan.Toress suggested and this is the updated Shoes.js file
import React, { useEffect } from 'react';
import './Shoes.css';
// import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { getProducts } from '../../../actions/productsActions';
import ProductsArea from './ProductsArea';
import Navbar from '../landing/Navbar';
import Search from './Search';
export const Shoes = (props) => {
useEffect(() => {
props.fetchData();
console.log(JSON.parse(props.products.products));
}, []);
if(props.loading) {
return (
<h1>loading</h1>
)
}
else {
return (
<div>
<Navbar />
<div className="shoes">
<Search />
<h1 className="productsTitle">Our Selection</h1>
<ProductsArea loading={props.loading} products={JSON.parse(props.products.products)} />
{/* {
props.products.map(product => (
<ProductCard key={product._id} product={product} />
))
} */}
</div>
</div>
)
}
}
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(getProducts())
};
};
const mapStateToProps = state => ({
products: state.products.products,
loading: state.products.loading
})
export default connect(mapStateToProps, mapDispatchToProps)(Shoes);
I can click on the link to the Shoes page from Home and everything works perfectly, but as soon as I reload the Shoes.js page or go to it directly, this is the error I get:
Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development.
This is my App.js file for the server side where I do have CORS enabled:
const express = require('express');
const app = express();
const bodyParser = require('body-parser')
const productRoute = require('./products/productRoute');
const orderRoute = require('./orders/orderRoute');
const userRoute = require('./users/userRoute');
const adminRoute = require('./admins/adminRoute');
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin','*');
res.header('Access-Control-Allow-Headers','Origin, X-Requested-With, Content-Type, Authorization, Accept');
if(res.method === 'OPTIONS') {
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, PATCH, DELETE');
}
next();
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use('/products', productRoute);
app.use('/orders', orderRoute);
app.use('/users', userRoute);
app.use('/admin', adminRoute);
app.use((req, res, next) => {
const error = new Error();
error.status = 404;
next(error);
});
app.use((error, req, res, next) => {
res.status(error.status || 500 ).json({
error: error
})
});
module.exports = app;
I'd really appreciate any help!
Thank you!
I think the way you dispatch the sync action is incorrect
by invoking props.getProducts(); it will return a sync function, that's will not trigger any dispatch action as i see
const getProducts = () => async (dispatch) => {
try{
....
to make sure it put a console.log as below and check it
useEffect(() => {
const returnedFromAction = props.getProducts();
console.log(returnedFromAction); // this should prints the returned function and it will not get dispatched
....
}
Here you need to dispatch a sync action by by executing returning function as below
You have to add a mapDispatchToProps as below
....
const mapDispatchToProps = dispatch => {
return {
fetchData: () => dispatch(getProducts())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
and then inside the useEffect use this fetchData function to dispatch the fetch action so now in useEffect
useEffect(() => {
props.fetchData();
}, []);
That will do the job for you, i have created a sample demo for you, check it out here
This will align with your approach by not using redux hooks, but theres another way that you can easily do as below.
import { useDispatch } from 'react-redux'; // import the dispatcher
const App = props => {
const dispatch = useDispatch(); // get a reference to dispatch
useEffect(() => {
dispatch(getProducts()); // dispatch the action
}, []);
see it in here
I'm new to React/Redux. I'm making an app using an API but the code doesn't work. When I run the code it says "this.props.recipes.map is not a function" and doesn't render anything.
If I change payload to: "payload: response.data.recipes" then the error changes to "Given action "FETCH_RECIPE", reducer "recipes" returned undefined." but no errors on screen (only in console). I thought writing "(state = [], action)" would solve the problem but it seems not. What's the problem and how do I fix this error?
Action Creator
import recipe from '../apis/recipe';
export const fetchRecipe = () => async dispatch => {
const response = await recipe.get('');
dispatch({ type: 'FETCH_RECIPE', payload: response.data })
};
Reducer
import { combineReducers } from 'redux';
const recipeReducer = (state = [], action) => {
switch(action.type) {
case 'FETCH_RECIPE':
return action.payload;
default:
return state;
}
};
export default combineReducers({
recipes: recipeReducer
});
import React from 'react';
import { connect } from 'react-redux';
import { fetchRecipe } from '../actions';
class Recipe extends React.Component {
componentDidMount() {
this.props.fetchRecipe();
console.log("This doesn't work", this.props.recipes)
}
renderList() {
return this.props.recipes.map(recipe => {
return (
<div>
<p>{recipe.publisher}</p>
</div>
)
})
}
render() {
console.log("First loaded: empty, second time: data fetched", this.props.recipes)
return (
<div>
{this.renderList()}
</div>
);
}
}
const mapStateToProps = (state) => {
return { recipes: state.recipes }
};
export default connect(mapStateToProps,{
fetchRecipe
})(Recipe);
API Request
import axios from 'axios';
import { key } from './config';
export default axios.create({
baseURL: `https://cors-anywhere.herokuapp.com/https://www.food2fork.com/api/search?key=${key}&q=pizza`
});
I want to list posts in PostList.js component from JSON file
I use react-redux for state managment and redux-saga to get json file
My components are Post.js and PostList.js:
Post.js
const Post = ({ post }) => {
<li>
{post}
</li>
}
export default Post
PostList.js
class PostList extends React.Component {
componentDidMount() {
console.log('did mount');
this.props.fetchPosts();
}
render() {
return (
<div>
<ul>
{this.state.posts(post => (
<Post key={post.id} {...post} />
))}
</ul>
</div>
)
}
}
export default PostList
Reducer
export default (state = [], action) => {
switch (action.type) {
case "FETCH_POSTS":
return {
...state,
loading: true,
posts: []
}
case "FETCH_FAILD":
return {
...state,
loading: false,
posts: []
}
case "FETCH_SUCCESS":
return Object.assign({}, state, {
posts: action.posts
})
default:
return state;
}
}
Actions.js
export const fetchPosts = () => {
return {
type: 'FETCH_POSTS'
}
}
export const fetchSuccess = data => ({
type: "FETCH_SUCCESS",
posts: data
})
export const fetchFaild = () => {
return {
type: 'FETCH_FAILD'
}
}
GetPosts.js (Container)
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import PostList from '../components/PostList'
import { fetchPosts } from '../actions'
const mapStateToProps = state => ({ posts: state.posts });
const mapDispatchToProps = dispatch => bindActionCreators({fetchPosts}, dispatch);
const GetPosts = connect(
mapStateToProps,
mapDispatchToProps
)(PostList)
export default GetPosts
Saga.js
export function* fetchProducts() {
try {
console.log('saga')
const posts = yield call(api_fetchPost);
yield put({ type: "FETCH_SUCCESS", posts});
} catch (e) {
yield put({ type: "FETCH_FAILD", e});
return;
}
}
export function* watchFetchProducts() {
yield takeEvery("FETCH_POSTS", fetchProducts)
}
You are fetching posts from the state of your postlist component. Redux mapStateToProps map the redux state to connected component's props and not state
class PostList extends React.Component {
componentDidMount() {
console.log('did mount');
this.props.fetchPosts();
}
render() {
return (
<div>
<ul>
{this.props.posts && this.props.posts.map(post => {
return ( <Post key={post.id} {...post} /> );
})}
</ul>
</div>
)
}
}
export default PostList
Change this.state.posts to this.props.posts
I have a snippet of code that I want to use to call an api, but before show a loading screen. For some reason in my code below I cannot seem to get the action REQUEST_GAMES to hit my combined gameReducer. I have included all the code below. Any reason as to why the action type is not being picked up by the reducer? I am not sure what i am doing wrong. Have i not connected it to my component correctly? The redux logger is showing that action is being called.
AddGame.js (component)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../actions';
import { getIsFetching, getVisibleGames } from '../reducers'
class AddGame extends Component{
constructor(props){
super(props);
this.state = {term: ''}
this.onInputChange = this.onInputChange.bind(this)
this.onFormSubmit = this.onFormSubmit.bind(this)
}
onInputChange(event){
this.setState({term:event.target.value})
}
onFormSubmit(event){
const { requestGames, fetchGames } = this.props;
event.preventDefault();
// we need to go fetch weather data
requestGames();
fetchGames(this.state.term);
this.setState({term:'' })
}
renderContent(){
const { isFetching , games} = this.props
if (isFetching && !games.length){
return <p>Loading</p>
} else if (games){
return <div>{games.map(this.getGame)}</div>
}
}
styleCSS = {
padding:'20px'
};
getGame(data){
return(
<div>
<pre key={data.id}>{data.name}</pre>
<img src ={data.cover.url} alt = "" />
</div>
)
}
render(){
return(
<div style={this.styleCSS}>
<form onSubmit={this.onFormSubmit}>
<input
value={this.state.term}
onChange={this.onInputChange}/>
<button type="submit">
Search
</button>
{this.renderContent()}
</form>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
isFetching :getIsFetching(state),
games:getVisibleGames(state),
}
}
export default connect(mapStateToProps,actions)(AddGame);
index.js (reducer)
import { combineReducers } from 'redux';
import authReducers from './authReducer';
import { reducer as formReducer } from 'redux-form';
import gameReducer, * as fromGames from './gameReducer';
const allReducers= combineReducers({
auth: authReducers,
form: formReducer,
game: gameReducer
});
export default allReducers;
export const getIsFetching = (state) => fromGames.getIsFetching(state.game);
export const getVisibleGames = (state)=> fromGames.getGames(state.game)
gameReducer.js
import { REQUEST_GAME, FETCH_GAME } from '../actions/types';
import { combineReducers } from 'redux';
const gameReducer = () => {
const games = (state=[], action) => {
console.log(action);
switch(action.type){
case FETCH_GAME:
return action.payload || false;
default:
return state;
}
};
const isFetching = (state = false, action) => {
switch (action.type) {
case REQUEST_GAME:
return true;
case FETCH_GAME:
return false;
default:
return state;
}
};
return combineReducers({
games,
isFetching
});
};
export default gameReducer;
export const getIsFetching = state => state.isFetching
;
export const getGames = state => state.games;
actions.js
import axios from 'axios';
import { FETCH_USER, REGISTER_USER, FETCH_GAME, REQUEST_GAME } from './types';
export const fetchUser = ()=> async dispatch=>{
const res = await axios.get('/api/current_user');
dispatch({type:FETCH_USER, payload:res.data});
console.log('fetchuser:',res.data)
};
export const fetchGames = (search)=> async dispatch=>{
const proxy = 'https://still-eyrie-36200.herokuapp.com/'
const res = await axios.get(`${proxy}https://api-2445582011268.apicast.io/games/?search=${search}&fields=name,category,genres,game_modes,cover,first_release_date,summary`,{
headers: {
'user-key':'18430b84d6bfaab720b08eeda8f2810d',
'Accept':'application/json',
'Content-Type':'application/json',
}
})
dispatch({type:FETCH_GAME, payload:res.data});
console.log('gamedata:',res.data)
};
export const requestGames = () =>({
type: REQUEST_GAME
})
The REQUEST_GAME action is probably processed by the reducer. After requestGame(), fetchGames() is called immediately. fetchGames() changes the state back to false. And both these actions happen in the same function block. So, there is no chance for the prop changes to cause a component re-render.
I have a component that connects to a store and displays a child component like below:
render() {
return <div>
<div className="userBox">
<ProfilePhoto userid={this.props.id} />
</div>
<div className="nameTitleBox">
<div className="firstLastTitle">
<h1>{this.props.firstName} {this.props.lastName}</h1>
</div>
<IDBox userid={this.props.id} />
</div>
<div className="childcomponent">
<childComponent />
</div>
<div className="profileBox">
<EditInterests interestsList={this.props.interest} />
</div>
</div>
}
}
export default connect(
(state) => state.user,
UserState.actionCreators
)(User);
I want the child component to be a smart component that loads it's own data and controls everything itself. The code for it is pretty simple.
import * as React from 'react';
import { Link, RouteComponentProps } from 'react-router-dom';
import { ApplicationState } from '../../store';
import { connect } from 'react-redux';
import * as ChildState from '../../store/childStore';
export class ChildComponent extends React.Component {
componentWillMount() {
this.props;
}
render() {
return (<div>
<div className="textCenter"><h2 id="sss">{this.props.text}</h2></div>
<div className="textRight">
<input type="button" className="button" value="Yes" /> <b className="textColor">No</b>
</div>
</div>
</div>
</div>)
}
}
const mapDispatchToProps = (dispatch) => {
return {
action: dispatch(ChildState.actionCreators.requestChildren())
}
}
export default connect(
mapDispatchToProps,
ChildState.actionCreators
)(ChildComponent);
this.props in the child component is always an empty object. Nothing from the child state is in there, the initial state, the actions, dispatch...anything. I've tried a few different things. ChildState loads fine if I actually load it in the parent. Don't know why it's not loading in the child and connecting the props.
Adding the store below:
import { Action, Reducer } from 'redux';
import { fetch, addTask } from 'domain-task';
import { AppThunkAction } from './';
export const actionCreators = {
requestChildren: () => (dispatch, getState) => {
let url = 'random';
var myheaders = new Headers();
myheaders.append("X-Requested-With", "XMLHttpRequest");
let fetchTask = fetch(url, {
headers: myheaders,
credentials: "same-origin"
})
.then(response => response.json())
.then(data => {
dispatch({ type: 'POST_ACTION', children: data });
});
addTask(fetchTask);
}
}
export const initialState = { ... };
export const reducer = (state = initialState, incomingAction) => {
const action = incomingAction;
switch (action.type) {
case 'REQUEST_ACTION':
return {
...
};
case 'POST_ACTION':
return {
...
};
default:
}
return state || initialState;
};
I believe the problem is in mapDispatchtoProps have you tried using bindActionCreators
bindActionCreators make sure action (ChildState.actionCreators.requestChildren) flows through the middleware if there is any and then to the reducers
import { bindActionCreators} from 'redux';
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
ChildState.actionCreators.requestChildren}, dispatch); }
export default connect(
ChildState.actionCreators,
mapDispatchToProps
)(ChildComponent);
This was happening because I was exporting both the child component and the connect function. I removed the export on the child component and its working now as expected.