I'm trying to learn react-redux-saga; so i'm building a simple app which calls a random user profile api and just displays it. Basically when the user hits the button "next image" it should make a REST call and retrieve the next image. I was able to make the API call and display the information but it keeps constantly calling the API infinitely and the data keeps changing despite not clicking anything. Here is my code:
App.js (Parent component)
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="App">
<p className="App-intro">
{ this.props.user !== undefined ? <ImageGenerator user={this.props.user}></ImageGenerator> : <span></span>}
</p>
</div>
);
}
}
const mapStateToProps = state => {
return { user: state.value };
};
const mapDispatchToProps = dispatch => {
return {
getNewImage: () =>
dispatch({
type: NEXT_IMAGE
})
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
This component is the one that makes the API call and passes the information onto the ImageGenerator component via the user prop. I noticed when I comment out the ImageGenerator line the API calls stop. I also put a log in mapDispatchToProps to verify that it's only dispatching the NEXT_IMAGE action once.
Here are my actions:
export const NEXT_IMAGE = function() { return { type: "NEXT_IMAGE" } };
export const fetchFailed = function(error) { return { type: "FETCH_FAILED", value: error } };
export const setImage = function(data) { return {type: "SET_IMAGE", value: data} };
Here is my saga:
export function* fetchImage() {
try {
const response = yield call(fetch, 'https://randomuser.me/api/');
const responseBody = yield response.json();
console.log("QWERT", responseBody.results);
yield put(setImage(responseBody.results[0]));
} catch (e) {
yield put(fetchFailed(e));
}
return;
}
export function* watchNextImage() {
yield takeEvery(NEXT_IMAGE, fetchImage);
}
export default function* rootSaga() {
yield all([
fetchImage(),
watchNextImage()
])
}
I have a suspicion it has to do with my watchNextImage function in my saga. That's the thing that puts a watch on the NEXT_IMAGE action and then calls fetchImage if it occurs. However, I don't understand why it would keep calling fetchImage if I only dispatched the NEXT_IMAGE action once...
Heres my reducer and my ImageGenerator - Probably nothing to interesting here:
reducer.js:
const rootReducer = (state = {}, action) => {
switch(action.type) {
case NEXT_IMAGE: {
return Object.assign({}, state, action)
}
case "FETCH_FAILED": {
return state;
}
case "SET_IMAGE": {
return Object.assign({}, state, action)
}
default: {
return state;
}
}
};
export default rootReducer;
ImageGenerator.js:
class ImageGenerator extends Component {
constructor(props) {
super(props);
}
render() {
let user = this.props.user;
console.log("USer", user);
return (
<div>
Name: {user.name.first} {user.name.last} <br />
Phone: {user.phone} <br />
Date of Birth: {user.dob.date} <br/>
Age: {user.dob.age} <br/>
Email: {user.email} <br />
Gender: {user.gender} <br/>
City: {user.location.city } <br />
State: {user.location.State } <br />
Street: {user.location.street } <br />
<img src={user.picture.medium} alt="No Image Found"/>
<button onClick={NEXT_IMAGE}>New Image</button>
<button>Add to Favorites</button>
</div>
)
}
}
export default ImageGenerator
Change rootSaga to only yield watchNextImage (the saga). You do not need to yield fetchImage (the side effect).
export default function* rootSaga() {
yield all([
spawn(watchNextImage)
])
}
After that, you just need to wire up your action correctly in your component.
Related
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()]);
}
I'm trying to do an API call through an action. I am using an onchange event to do a call with also adding an ID.
I made the MapDispatchToProps where I bind my action through bindActionCreators. When I call the action, I see that he is doing the API call and gets the correct value. Only when it returns to the onchange event it is undefined.
I tried following several examples and pluralsights tutorials, but none of these work.
ACTION:
export function loadStanding(id) {
var url = "http://api.football-data.org/v2/competitions/" + id + "/standings";
return function (dispatch) {
return fetch(url,
{
mode: "cors"
})
.then(
response => response.json(),
error => console.log('An error occurred.', error)
)
.then((json) => {
console.log("=== LOADSTANDING ACTION ===");
console.log(json);
dispatch(loadStandingsSucces(json));
});
};
}
PAGE:
class HomePage extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { standings: [], selectedId: 0 };
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
event.preventDefault();
this.props.actions.loadStanding(event.target.value).then(function(output) {
console.log("=== HANDLECHANGE ===");
console.log(output);
});
}
render() {
const { competitions = [] } = this.props.competitions;
const compIds = [2000,2001,2002,2003,2013,2014,2015,2016,2017,2018,2019,2021];
return (
<div className="flex-container">
<div className="row">
<div className="flex-item">
<h2>Kies een competitie:</h2>
</div>
<div className="flex-item">
<DropdownComponent onChange={this.handleChange} value="id" itemKey="id" text="name" competitions={competitions.filter(function(comp) { return compIds.includes(comp.id); })} />
</div>
<div className="flex-item">
{/* <TableComponent /> */}
</div>
</div>
</div>
);
}
}
HomePage.propTypes = {
competitions: PropTypes.any.isRequired,
actions: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
competitions: state.competitions,
standings: state.standings
};
}
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(standingActions, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomePage);
First, I sugest you to check that call is working properly with Postman or similar tool.
In second place, I think that you have a little misconception about how to manage data with React and Redux.
The data you are fetching must be stored inside the redux store, when you call that action creator, the data received in the response should be dispatched to a reducer.
That reducer will store that information and then will cause your component will render again, and the fetched data will be available on the component props.
More information here: Redux data flow
I have a form in react where I'm asking for the last 8 of the VIN of a car. Once I get that info, I want to use it to get all the locations of the car. How do I do this? I want to call the action and then display the results.
Added reducer and actions...
Here is what I have so far...
class TaglocaByVIN extends Component {
constructor(props){
super(props);
this.state={
searchvin: ''
}
this.handleFormSubmit=this.handleFormSubmit.bind(this);
this.changeText=this.changeText.bind(this);
}
handleFormSubmit(e){
e.preventDefault();
let searchvin=this.state.searchvin;
//I want to maybe call the action and then display results
}
changeText(e){
this.setState({
searchvin: e.target.value
})
}
render(){
return (
<div>
<form onSubmit={this.handleFormSubmit}>
<label>Please provide the last 8 characters of VIN: </label>
<input type="text" name="searchvin" value={this.state.searchvin}
onChange={this.changeText}/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default TaglocaByVIN;
Here are my actions:
export function taglocationsHaveError(bool) {
return {
type: 'TAGLOCATIONS_HAVE_ERROR',
hasError: bool
};
}
export function taglocationsAreLoading(bool) {
return {
type: 'TAGLOCATIONS_ARE_LOADING',
isLoading: bool
};
}
export function taglocationsFetchDataSuccess(items) {
return {
type: 'TAGLOCATIONS_FETCH_DATA_SUCCESS',
items
};
}
export function tagformsubmit(data){
return(dispatch) =>{
axios.get(`http://***`+data)
.then((response) => {
dispatch(taglocationsFetchDataSuccess);
})
};
}
reducer:
export function tagformsubmit(state=[], action){
switch (action.type){
case 'GET_TAG_FORM_TYPE':
return action.taglocations;
default:
return state;
}
}
This is an easy fix but it will take a few steps:
Set up an action
Set up your reducer
Fetch and Render data in component
Creating the Action
The first thing, you need to set up an action for getting data based on a VIN. It looks like you have that with your tagformsubmit function. I would make a few adjustments here.
You should include a catch so you know if something went wrong, change your function param to include dispatch, add a type and a payload to your dispatch, and fix the string literal in your api address. Seems like a lot but its a quick fix.
Update your current code from this:
export function tagformsubmit(data){
return(dispatch) =>{
axios.get(`http://***`+data)
.then((response) => {
dispatch(taglocationsFetchDataSuccess);
})
};
}
to this here:
//Get Tag Form Submit
export const getTagFormSubmit = vin => dispatch => {
dispatch(loadingFunctionPossibly()); //optional
axios
.get(`/api/path/for/route/${vin}`) //notice the ${} here, that is how you use variable here
.then(res =>
dispatch({
type: GET_TAG_FORM_TYPE,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_TAG_FORM_TYPE,
payload: null
})
);
};
Creating the Reducer
Not sure if you have already created your reducer. If you have you can ignore this. Creating your reducer is also pretty simple. First you want to define your initial state then export your function.
Example
const initialState = {
tags: [],
tag: {},
loading: false
};
export default (state=initialState, action) => {
if(action.type === GET_TAG_FORM_TYPE){
return {
...state,
tags: action.payload,
loading: false //optional
}
}
if(action.type === GET_TAG_TYPE){
return {
...state,
tag: action.payload,
}
}
}
Now that you have your action and reducer let's set up your component.
Component
I'm going to assume you know all of the necessary imports. At the bottom of your component, you want to define your proptypes.
TaglocaByVIN.propTypes = {
getTagFormSubmit: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired
};
mapStateToProps:
const mapStateToProps = state => ({
tag: state.tag
});
connect to component:
export default connect(mapStateToProps, { getTagFormSubmit })(TaglocaByVIN);
Update your submit to both pass data to your function and get the data that is returned.
handleFormSubmit = (e) => {
e.preventDefault();
const { searchvin } = this.state;
this.props.getTagFormSubmit(searchvin);
const { tags } = this.props;
tags.map(tag => {
//do something with that tag
}
Putting that all together your component should look like this (not including imports):
class TaglocaByVIN extends Component {
state = {
searchvin: ""
};
handleFormSubmit = e => {
e.preventDefault();
const { searchvin } = this.state;
this.props.getTagFormSubmit(searchvin);
const { tags } = this.props.tag;
if(tags === null){
//do nothing
} else{
tags.map(tag => {
//do something with that tag
});
};
}
changeText = e => {
this.setState({
searchvin: e.target.value
});
};
render() {
return (
<div>
<form onSubmit={this.handleFormSubmit}>
<label>Please provide the last 8 characters of VIN: </label>
<input
type="text"
name="searchvin"
value={this.state.searchvin}
onChange={this.changeText}
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
TaglocaByVIN.propTypes = {
getTagFormSubmit: PropTypes.func.isRequired,
tag: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
tag: state.tag
});
export default connect(
mapStateToProps,
{ getTagFormSubmit }
)(TaglocaByVIN);
That should be it. Hope this helps.
I have a question about passing value (item.id) from one component to another component's saga, where I could add additional field in POST body and make a request.
I have two components: 1st Form component, where is two input fields. 2st component is Item, which are GET'ed from API. So there is a itemId value, which I need to give when making POST request with form.
My soliution right now is to pass itemId to localstorage and then take it in saga, but it causes some bugs when user opens two browser windows. What would be better solution for this task?
My Item component:
export class FindClientItem extends React.PureComponent {
constructor() {
super();
this.state = {
modalIsOpen: false,
};
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
}
openModal() {
this.setState({ modalIsOpen: true });
}
closeModal() {
this.setState({ modalIsOpen: false });
localStorage.removeItem('itemId');
}
render() {
const { item } = this.props;
if(this.state.modalIsOpen){
localStorage.setItem('itemId',item.itemId);
}
// Put together the content of the repository
const content = (
<Wrapper>
<h3>{item.title}</h3>
Details: {item.description}...<button onClick={this.openModal}>
More
</button>
<Modal
isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel="Modal"
>
<h3>{item.title}</h3>
Details: {item.description} <br />
<button onClick={this.openBidModal}>Submit</button>{' '}
</Modal>
</Wrapper>
);
// Render the content into a list item
return <ListItem key={`items-${item.itemId}`} item={content} />;
}
}
And then my other 1st Form component's saga:
export function* submitForm() {
try {
const formType = 'item';
const body = yield select(makeSelectModifiedData());
body.itemId = localStorage.getItem('itemId');
let requestURL;
switch (formType) {
case 'item':
requestURL = 'http://localhost:1234/item';
break;
default:
}
const response = yield call(request, requestURL, { method: 'POST', body });
} catch (error) {
Alert.error('Error message...', {
html: false,
});
}
}
Not sure if this is the "Best" way to do this, however, works well for me. Have you tried creating a shared js file (imported into both components) which GETS / SETS a variable? for example.
shared.js
let data = null;
setData(d){
data = d;
}
getData(){
return data;
}
addChangeListner(eventName, callback){
this.on(eventname, callback);
}
dispatcherCallback(action){
switch(action.actionType){
case 'SET_DATA':
this.getData();
}
}
Whenever you require your component to update, you can add an change listener to then return the new data once set so the components aren't out of sync. Just remember to remove the listener afterwords!
Component
componentDidMount(){
shared.addChangeListner('SET_DATA', this.onUpdate)
}
// use x to pass to your saga...
onUpdate(){
var x = shared.getData();
}
Hope this helps!
index.js
import {handleSave, loadData } from './action';
import Modal from './Modal',
export class GetFormData extends React.PureComponent {
componentDidMount() {
this.props.loadData();
}
saveData = (data) => {
this.props.handleSave(data)
}
render() {
return (
<div>
<Modal
isOpen={this.state.modalIsOpen}
onRequestClose={this.closeModal}
style={customStyles}
contentLabel="Modal"
data={this.props.getdata}
handlePost={this.saveData}
/>
</div>
)
}
}
const mapStateToProps = state => ({
getdata: state.formData,
});
const mapDispatchToProps = dispatch => ({
loadData: bindActionCreators(loadData, dispatch),
handleSave: bindActionCreators(handleSave, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(GetFormData);
actions.js
import {
LOAD_DATA,
LOAD_DATA_SUCCESS,
LOAD_DATA_FAILED
HANDLE_SAVE,
HANDLE_SAVE_SUCCESS,
HANDLE_SAVE_FAILED
} from './constants';
export function loadData() {
return {
type: LOAD_DATA,
};
}
export function loadDataSuccess(formData) {
return {
type: LOAD_DATA_SUCCESS,
formData
};
}
export function loadDataFailed(error) {
return {
type: LOAD_DATA_FAILED,
error
};
}
export function handleSave(data) {
return {
type: HANDLE_SAVE,
data
};
}
export function handleSaveSuccess() {
return {
type: HANDLE_SAVE_SUCCESS
};
}
export function handleSaveFailed(error) {
return {
type: HANDLE_SAVE_FAILED,
error
};
}
reducers.js
import { fromJS } from 'immutable';
import {
LOAD_DATA, LOAD_DATA_SUCCESS, LOAD_DATA_FAILED,
HANDLE_SAVE,
HANDLE_SAVE_SUCCESS,
HANDLE_SAVE_FAILED
} from './constants';
const initialState = fromJS({
formData: undefined,
});
function formDataReducer(state = initialState, action) {
switch (action.type) {
case LOAD_DATA:
return state;
case LOAD_DATA_SUCCESS:
return state.set('formData', action.formData);
case LOAD_DATA_FAILED:
return state.set('errormsg', fromJS(action.errormsg));
case HANDLE_SAVE:
return state.set('data', action.data);
case HANDLE_SAVE_SUCCESS:
return state.set('message', action.message);
case HANDLE_SAVE_FAILED:
return state.set('errormsg', fromJS(action.errormsg));
default:
return state;
}
}
saga.js
import { takeEvery, call, put } from 'redux-saga/effects';
import {
LOAD_DATA,
LOAD_DATA_SUCCESS,
LOAD_DATA_FAILED,
HANDLE_SAVE,
HANDLE_SAVE_SUCCESS,
HANDLE_SAVE_FAILED
} from './constants';
export function* getFormDataWorker() {
try {
const formData = yield call(api);
if (formData) {
yield put({ type: LOAD_DATA_SUCCESS, formData });
}
} catch (errormsg) {
yield put({ type: LOAD_DATA_FAILED, errormsg });
}
}
// watcher
export function* formDataWatcher() {
yield takeEvery(LOAD_DATA, getFormDataWorker);
}
export function* saveDataWorker(action) {
try {
const message = yield call(savedata, action.data);
if (message) {
yield put({ type: HANDLE_SAVE_SUCCESS, message });
}
} catch (errormsg) {
yield put({ type: HANDLE_SAVE_FAILED, errormsg });
}
}
// watcher
export function* saveDataWatcher() {
yield takeEvery(HANDLE_SAVE, saveDataWorker);
}
// All sagas to be loaded
export default [
saveDataWatcher,
formDataWatcher,
];
Modal.js
const Modal = ({data, handlePost}) => (
{ data ? data.map(item => (
<input type="text" value={item.id} />
)
}
<Button type="submit" onClick={handlePost}/ >
)
Hope this helps!
I would suggest the following:
Remove the usage of localstorage
On componentDidUpdate dispatch an action that sets the itemId in the Redux store.
componentDidUpdate() {
this.props.setItemId({itemId: this.props.item.itemId})
}
On form submit, dispatch the same action as you are currently using to trigger the saga.
Change your makeSelectModifiedData selector to return the itemId you are storing in Redux now.
Im not 100% sure if it is working correct, but it does noet give the result of the video course that I followed.
The renderPosts is just suppose to render the list, but instead it get a blank array the first time round. and when mapStateToProps is called the second time, the array is filled with the expected values.
it is as if the first time mapStateToProps is invoked, it did not pass through the action creator first or something.
COMPONENT
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
// console.log("renderPosts - this.props.posts",this.props.posts);
if(this.props.posts){
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.renderPosts()}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
ACTION
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const CREATE_POST = 'CREATE_POST';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty123';
export function fetchPosts(){
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
export function createPost(props) {
const request = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return{
type: CREATE_POST,
payload: request
}
}
REDUCER
import { FETCH_POSTS } from '../actions/index';
const INITIAL_STATE = { postsList:[], post:null };
export default function(state = INITIAL_STATE, action){
console.log("action.type",action.type);
switch (action.type) {
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
default:
return state;
}
}
mapStateToProps is called twice. on the initial call the array is empty. on the second call I have my ten posts inside the array.
Problem is that it seems to want to render the first array and ignores the second
I have put an consol.log in the
renderPosts
and
mapStateToProps
and it renders as follows.
Console
any Ideas?
I think the error is coming from the way you handle the Promise. The first time you see the mapStateToProps in the console you can see you have no data so this is PENDING, the second is when it's FULFILLED. You need to find a way to handle this.
Example but not the best, I think you can just change you if statement.
import React, {Component} from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions/index';
import { Link } from 'react-router';
class PostsIndex extends Component {
componentWillMount() {
console.log("componentWillMount");
this.props.fetchPosts();
}
renderPosts() {
return this.props.posts.map((post) => {
return (
<li className="list-group-itme" key="{post.id}">
<span className="pull-xs-right">{post.catagories}</span>
<strong>{post.title}</strong>
</li>
);
});
}
render() {
return (
<div>
<div className="text-xs-right">
<Link to="/posts/new" className="btn btn-primary">
Add New Post
</Link>
</div>
<h3>Posts</h3>
<ul className="list-group">
{this.props.posts !== [] this.renderPosts() : <h1>Loading...</h1>}
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log("mapStateToProps",state.posts);
return {posts: state.posts.all}
}
export default connect(mapStateToProps, {fetchPosts})(PostsIndex);
The second one should be by changing the way you do the promise. A good library is redux-promise-middleware
This is a example of my app what I did.
Actions
export const reqAllGames = games => {
const promise = new Promise((resolve, reject) => {
request
.get(`${config.ROOT_URL}/${config.API_KEY}`)
.end((err, res) => {
if (err) {
reject(err);
} else {
resolve(res.body.top);
}
});
});
return {
type: types.RECEIVE_ALL_GAMES,
payload: promise
};
};
Reducer
import * as types from "../constants/";
const gameReducer = (games = { isFetched: false }, action) => {
switch (action.type) {
case `${types.RECEIVE_ALL_GAMES}_PENDING`:
return {};
case `${types.RECEIVE_ALL_GAMES}_FULFILLED`:
return {
games: action.payload,
err: null,
isFetched: true
};
case `${types.RECEIVE_ALL_GAMES}_REJECTED`:
return {
games: null,
err: action.payload,
isFetched: true
};
default:
return games;
}
};
export default gameReducer;
Component
const Games = ({ games, err, isFetched }) => {
if (!isFetched) {
return <LoadingCircular />;
}
else if (err === null) {
return (
<div>
<GamesList games={games} />
</div>
);
} else {
return <h1>Games not find!</h1>;
}
};
const mapStateToProps = (state) => state.games;
export default connect(mapStateToProps)(Games);
If you using react-router you can use the onEnter api and do the actions right here. With that you know your component gonna get the post. A good tutorial is this one from RallyCoding https://www.youtube.com/watch?v=JicUNpwLzLY
Hope that can help you
https://www.udemy.com/react-redux/learn/v4/questions/1693796
In your reducer you're assigning the list of posts to the key postsList.
case FETCH_POSTS:
return {...state, postsList: action.payload.data};
We can confirm that they are properly being assumed to postsList by looking at the mapStateToProps console log you have in your screenshot.
Your mapStateToProps, however, is looking at the property state.posts.all
return {posts: state.posts.all}
The list of posts are not assigned to the all property, they are assigned to the postsList property. This is why you don't see the updated list of posts in your component. You'll need to update either the property the reducer is placing the list of posts on or update your mapStateToProps to pull the list of posts from the correct property.
-Stephen Grider