I've got this working but i'm after a more 'best practice way'.
its using the https://icanhazdadjoke api to display a random joke that gets updated every x seconds. is there a better way of doing this?
eventually i want to add stop, start, reset functionality and feel this way might not be the best.
Any middleware i can use?
Redux actions
// action types
import axios from 'axios';
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';
function fetchJoke() {
return {
type: FETCH_JOKE
};
}
function fetchJokeSuccess(data) {
return {
type: FETCH_JOKE_SUCCESS,
data
};
}
function fetchJokeFail(error) {
return {
type: FETCH_JOKE_FAILURE,
error
};
}
export function fetchJokeCall(){
return function(dispatch){
dispatch(fetchJoke());
return axios.get('https://icanhazdadjoke.com', { headers: { 'Accept': 'application/json' }})
.then(function(result){
dispatch(fetchJokeSuccess(result.data))
})
.catch(error => dispatch(fetchJokeFail(error)));
}
}
Redux reducer
import {combineReducers} from 'redux';
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE} from '../actions';
const defaultStateList = {
isFetching: false,
items:[],
error:{}
};
const joke = (state = defaultStateList, action) => {
switch (action.type){
case FETCH_JOKE:
return {...state, isFetching:true};
case FETCH_JOKE_SUCCESS:
return {...state, isFetching:false, items:action.data};
case FETCH_JOKE_FAILURE:
return {...state, isFetching:false, error:action.data};
default:
return state;
}
};
const rootReducer = combineReducers({
joke
});
export default rootReducer;
Joke component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { fetchJokeCall } from '../actions';
class Joke extends Component {
componentDidMount() {
this.timer = setInterval(()=> this.props.fetchJokeCall(), 1000);
}
componentWillUnmount() {
clearInterval(this.timer)
this.timer = null;
}
render() {
return (
<div>
{this.props.joke.joke}
</div>
);
}
}
Joke.propTypes = {
fetchJokeCall: PropTypes.func,
joke: PropTypes.array.isRequired
};
function mapStateToProps(state) {
return {
joke: state.joke.items,
isfetching: state.joke.isFetching
};
}
export default connect(mapStateToProps, { fetchJokeCall })(Joke);
Redux-Sagas is better and we are using it in our applications as well, this is how you can poll using Redux-Sagas
Just to give you an idea this is how you can do it, You also need to understand how Redux-Sagas work
Action
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';
export const START_POLLING = 'START_POLLING';
export const STOP_POLLING = 'STOP_POLLING';
function startPolling() {
return {
type: START_POLLING
};
}
function stopPolling() {
return {
type: STOP_POLLING
};
}
function fetchJoke() {
return {
type: FETCH_JOKE
};
}
function fetchJokeSuccess(data) {
return {
type: FETCH_JOKE_SUCCESS,
data
};
}
function fetchJokeFail(error) {
return {
type: FETCH_JOKE_FAILURE,
error
};
}
Reducer
import {combineReducers} from 'redux';
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE, START_POLLING, STOP_POLLING } from '../actions';
const defaultStateList = {
isFetching: false,
items:[],
error:{},
isPolling: false,
};
const joke = (state = defaultStateList, action) => {
switch (action.type){
case FETCH_JOKE:
return {...state, isFetching:true};
case FETCH_JOKE_SUCCESS:
return {...state, isFetching:false, items:action.data};
case FETCH_JOKE_FAILURE:
return {...state, isFetching:false, error:action.data};
case START_POLLING:
return {...state, isPolling: true};
case STOP_POLLING:
return {...state, isPolling: false};
default:
return state;
}
};
const rootReducer = combineReducers({
joke
});
export default rootReducer;
Sagas
import { call, put, takeEvery, takeLatest, take, race } from 'redux-saga/effects'
import {FETCH_JOKE, FETCH_JOKE_SUCCESS, FETCH_JOKE_FAILURE, START_POLLING, STOP_POLLING } from '../actions';
import axios from 'axios';
function delay(duration) {
const promise = new Promise(resolve => {
setTimeout(() => resolve(true), duration)
})
return promise
}
function* fetchJokes(action) {
while (true) {
try {
const { data } = yield call(() => axios({ url: ENDPOINT }))
yield put({ type: FETCH_JOKE_SUCCESS, data: data })
yield call(delay, 5000)
} catch (e) {
yield put({ type: FETCH_JOKE_FAILURE, message: e.message })
}
}
}
function* watchPollJokesSaga() {
while (true) {
const data = yield take(START_POLLING)
yield race([call(fetchJokes, data), take(STOP_POLLING)])
}
}
export default function* root() {
yield [watchPollJokesSaga()]
}
You can also use Redux-Observable, if you want to get more into this read this article
I've been working on pretty much the same problem, except that I wasn't concerned about starting and stopping the poll. For some reason the while loop kept freezing my app so I dispensed of it and instead set up my saga like this.
import { all, takeLatest, call, put } from 'redux-saga/effects';
import axios from 'axios';
import { API_CALL_REQUEST, API_CALL_SUCCESS, API_CALL_FAILURE, API_CALL_FETCHED } from
'../actions/giphy';
function apiFetch() {
let randomWord = require('random-words');
let API_ENDPOINT = `https://api.giphy.com/v1/gifs/search?
api_key=MYKEY&q=${randomWord()}&limit=12`;
return axios({
method: "get",
url: API_ENDPOINT
});
}
export function* fetchImages() {
try {
const res = yield call(apiFetch)
const images = yield res.data
yield put({type: API_CALL_SUCCESS, images})
} catch (e) {
yield put({type: API_CALL_FAILURE, e})
console.log('Error fetching giphy data')
}
}
export default function* giphySaga() {
yield all([
takeLatest(API_CALL_REQUEST, fetchImages),
]);
}
Then inside my component I added this.
componentDidMount() {
this.interval = setInterval(() => {
this.props.dispatch({type: 'API_CALL_REQUEST'});
}, 5000);
}
componentWillUnmount() {
clearInterval(this.interval)
}
It's working, but would like some feedback on how this could be possibly improved.
Here's a poor man's way. I don't think it's the best way but it doesn't require any extra library.
Actions
// action types
import axios from 'axios';
export const START_POLLING_JOKE = 'START_POLLING_JOKE';
export const STOP_POLLING_JOKE = 'STOP_POLLING_JOKE';
export const FETCH_JOKE = 'FETCH_JOKE';
export const FETCH_JOKE_SUCCESS = 'FETCH_JOKE_SUCCESS';
export const FETCH_JOKE_FAILURE = 'FETCH_JOKE_FAILURE';
const defaultPollingInterval = 60000
function startPollingJoke(interval = defaultPollingInterval) {
return function (dispatch) {
const fetch = () => dispatch(fetchJoke())
dispatch({
type: START_POLLING_JOKE,
interval,
fetch,
})
}
}
function stopPollingJoke() {
return {
type: STOP_POLLING_JOKE
}
}
function fetchJoke() {
return {
type: FETCH_JOKE
};
}
function fetchJokeSuccess(data) {
return {
type: FETCH_JOKE_SUCCESS,
data
};
}
function fetchJokeFail(error) {
return {
type: FETCH_JOKE_FAILURE,
error
};
}
export function pollJokeCall(interval = defaultPollingInterval) {
return function (dispatch) {
dispatch(fetchJoke())
dispatch(startPollingJoke(interval))
}
}
export function fetchJokeCall() {
return function(dispatch){
dispatch(fetchJoke());
return axios.get('https://icanhazdadjoke.com', { headers: { 'Accept': 'application/json' }})
.then(function(result){
dispatch(fetchJokeSuccess(result.data))
})
.catch(error => dispatch(fetchJokeFail(error)));
}
}
Reducers
import {combineReducers} from 'redux';
import {
START_POLLING_JOKE,
STOP_POLLING_JOKE,
FETCH_JOKE,
FETCH_JOKE_SUCCESS,
FETCH_JOKE_FAILURE,
} from '../actions';
const defaultStateList = {
isFetching: false,
items:[],
error:{},
pollingId: null,
polling: false,
};
const joke = (state = defaultStateList, action) => {
switch (action.type){
case START_POLLING_JOKE:
clearInterval(state.pollingId)
return {
...state,
polling: true,
pollingId: setInterval(action.fetch, action.interval),
}
}
case STOP_POLLING_JOKE:
clearInterval(state.pollingId)
return {...state, polling: false, pollingId: null}
case FETCH_JOKE:
return {...state, isFetching:true};
case FETCH_JOKE_SUCCESS:
return {...state, isFetching:false, items:action.data};
case FETCH_JOKE_FAILURE:
return {...state, isFetching:false, error:action.data};
default:
return state;
}
};
const rootReducer = combineReducers({
joke
});
export default rootReducer;
Component (might have a bug because I'm not used to class components)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { pollJokeCall, stopPollingJoke } from '../actions';
class Joke extends Component {
componentDidMount() {
this.props.pollJokeCall()
}
componentWillUnmount() {
this.props.stopPollingJoke()
}
render() {
return (
<div>
{this.props.joke.joke}
</div>
);
}
}
Joke.propTypes = {
pollJokeCall: PropTypes.func,
stopPollingJoke: PropTypes.func,
joke: PropTypes.array.isRequired,
};
function mapStateToProps(state) {
return {
joke: state.joke.items,
isfetching: state.joke.isFetching
};
}
export default connect(mapStateToProps, { pollJokeCall, stopPollingJoke })(Joke);
I have made a small (5kb gzipped) helper to create polling based on redux-thunk store. The idea is to have a logic to prevent registering the same polling twice, have callbacks between iterations and more.
https://www.npmjs.com/package/redux-polling-thunk
redux-saga is great and I've been using this with redux. It provides a great api to do things like delay, polling, throttling, race conditions, task cancellations. So using redux-saga, you can add a watcher whcih will keep on pooling
function* pollSagaWorker(action) {
while (true) {
try {
const { data } = yield call(() => axios({ url: ENDPOINT }));
yield put(getDataSuccessAction(data));
yield call(delay, 4000);
} catch (err) {
yield put(getDataFailureAction(err));
}
}
}
Related
Hello I have the following error when trying to consume my api
TypeError: api.get is not a function
api.js
import axios from 'axios';
const api = axios.create({
baseURL: 'http://localhost:8000', });
export default api;
action fetch:
const api = require('../../services/api');
export function productsError(bool) {
return {
type: 'PRODUCTS_HAS_ERRORED',
hasErrored: bool
};
}
export function productsIsLoading(bool) {
return {
type: 'PRODUCTS_IS_LOADING',
isLoading: bool
};
}
export function productsFetchSuccess(products) {
return {
type: 'PRODUCTS_SUCCESS',
products
};
}
export function errorAfterFiveSeconds() {
// We return a function instead of an action object
return (dispatch) => {
setTimeout(() => {
// This function is able to dispatch other action creators
dispatch(productsError(true));
}, 5000);
};
}
export function ProductsFetchData() {
return (dispatch) => {
dispatch(productsIsLoading(true));
api.get('/products')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(productsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((products) => dispatch(productsFetchSuccess(products)))
.catch(() => dispatch(productsError(true)));
};
}
reducer fetch
export function ProductsHasErrored(state = false, action) {
switch (action.type) {
case 'PRODUCTS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
export function ProductsIsLoading(state = false, action) {
switch (action.type) {
case 'PRODUCTS_IS_LOADING':
return action.isLoading;
default:
return state;
}
}
return action.products;
default:
return state;
}
} return action.products;
default:
return state;
}
}export function Products(state = [], action) {
return action.products;
default:
return state;
}
} return action.products;
default:
return state;
}
}
my store :
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
}
in my app:
import React, { Component } from 'react'
import {connect} from 'react-redux'
import { bindActionCreators } from 'redux';
import { ProductsFetchData } from '../../store/actions/productsFetch';
class index extends Component {
componentDidMount() {
this.props.fetchData('/products');
}
render() {
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
return (
<div>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
products: state.products,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch(ProductsFetchData())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(index);
basically I have error in this function:
export function ProductsFetchData() {
return (dispatch) => {
dispatch(productsIsLoading(true));
api.get('/products')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(productsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((products) => dispatch(productsFetchSuccess(products)))
.catch(() => dispatch(productsError(true)));
};
}
I don't know why or where I went wrong to get this error
in action fetch, you should be change:
const api = require('../../services/api');
to:
const api = require('../../services/api').default;
or
import api from '../../services/api')
You should just export the baseURL as as const, then in your actions:
import axios from 'axios'
//othercode here
export function ProductsFetchData() {
return (dispatch) => {
dispatch(productsIsLoading(true));
api.get(`${}/products`)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(productsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((products) => dispatch(productsFetchSuccess(products)))
.catch(() => dispatch(productsError(true)));
};
}
When you use export default, This file will create an object with key is default and export them.
const a = 2;
export default a;
import with require:
const a = require(...)
console.log(a)
// a here will be an object
>> Object {default: 2}
So when you want to use require from export default, you have to access to .default: console.log(a.default).
Or you can use import in ES6 like this:
import a from '...';
// console.log(a)
>> 2
I am fairly new to redux, and I am running into a problem.
I am trying to implement flash messages to my login page, but redux's dispatch is not changing the UI State.
I want a flash message to appear on the login page after user successfully register.
//login.js
class Login extends Component{
renderMessage() {
if (this.props.flashMessageType== "registrationComplete"){
return (
<Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
);
} else {
return (null);
}
}
render() {
return ({
this.renderMessage()
});
}
}
function mapStateToProps(state) {
return {
flashMessageType:state.flashMessage.flashType,
};
}
export default connect(mapStateToProps, actions)(Login);
Here is the reducer
const initialState = {
flashType: "",
};
export default function(state = {initialState}, action){
switch(action.type){
case USER_REGISTER:
return [
...state,
{
flashType:"registrationComplete"
}
];
default:
return initialState;
}
}
and here is the actions
export const submitForm = (values,history) => async dispatch => {
const res = await axios.post('/api/signup', values);
history.push('/');
dispatch({type: FETCH_USER, payload: res.data});
dispatch({type: USER_REGISTER});
};
I appreciate your help.
Thanks,
Vincent
As Amr Aly mentioned (and now soroush), you're essentially mutating the state when you do:
return[ ...state, { flashType:"registrationComplete" }]
What you really want is:
return { ...state, flashMessage: "registrationComplete" }
Also, some of your code is a bit redundant and/or missing some important instructions (like try/catch blocks).
What your code should look like:
FlashMessage.js
import React, { PureComponent } from 'react';
import Message from '../some/other/directory';
import actions from '../some/oter/directory':
class Login extends PureComponent {
render = () => (
this.props.flashMessage == "registrationComplete"
? <Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
: null
)
}
export default connect(state => ({ flashMessage: state.auth.flashMessage }), actions)(Login)
reducers.js
import { routerReducer as routing } from 'react-router-redux';
import { combineReducers } from 'redux';
import { FETCH_USER, USER_REGISTER } from '../actions/types';
const authReducer = (state={}, ({ type, payload }) => {
switch(type){
case FETCH_USER: return { ...state, loggedinUser: payload };
case USER_REGISTER: return { ...state, flashMessage: "registrationComplete" }
default: return state;
}
}
export default = combineReducers({
auth: authReducer,
routing
});
actions.js
import { FETCH_USER, USER_REGISTER } from './types';
export const submitForm = (values,history) => async dispatch => {
try {
const {data} = await axios.post('/api/signup',values);
dispatch({ type:FETCH_USER, payload: data });
dispatch({ type:USER_REGISTER });
history.push('/');
catch (err) {
console.error("Error: ", err.toString());
}
};
Your reducer should be:
const initialState = {
flashType: "",
};
export default function(state = initialState, action){
switch(action.type){
case USER_REGISTER:
return {
...state,
flashType: "registrationComplete",
};
default:
return state;
}
}
I'm having a problem here, I have been doing redux for about 2 weeks now.
I'm trying to create a loader so I am trying to get the isFetching state. Using thunk, i'm doing an ajax fetch, and dispatching the loading state.
The dispatch was called, because I can see in my console.
before component will mount, its suppose to call FETCH_PROFILE, and isFetching set to true, but when i console.log(this.props.profile.isFetching), it's returning false.
Same for FETCH_PROFILE_SUCCESS, it doesn't update this.props.profile. (Or because It's not rerendering...so I can't see it)
I've been working on this simple thing for hours and I have no idea why it doesn't update...... I am sure I made some mistake somewhere but no idea what.
export const FETCH_PROFILE = 'FETCH_PROFILE';
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
export const FETCH_PROFILE_FAIL = 'FETCH_PROFILE_FAIL';
export function getUserProfile() {
return (dispatch) => {
dispatch(getUserProfileStart());
const config2 = {
method: 'GET',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'include',
body: ``,
};
fetch('http://api.example.com/profile/',config2)
.then((resp) => resp.json())
.then(function(data) {
dispatch(getUserProfileSuccess(data));
return 0;
}).catch(function(error){
return dispatch({type: FETCH_PROFILE_FAIL});
})
}
}
function getUserProfileSuccess(data) {
return {
type: FETCH_PROFILE_SUCCESS,
isFetching: false,
payload: data
}
}
function getUserProfileStart() {
return {
type: FETCH_PROFILE,
isFetching: true
}
}
my reducer
import {
FETCH_PROFILE,
FETCH_PROFILE_SUCCESS,
FETCH_PROFILE_FAIL
} from '../actions/profile';
export default function userProfile(state={isFetching: false}, action) {
switch(action.type) {
case FETCH_PROFILE:
return {...state, isFetching: true}
case FETCH_PROFILE_SUCCESS:
return {...state, isFetching: false, data: action.payload}
case FETCH_PROFILE_FAIL:
return { ...state, isFetching: false };
default:
return state
}
}
My Component.
import React from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import {connect } from 'react-redux';
import * as profileActions from '../../actions/profile';
import {bindActionCreators} from 'redux';
class ProfilePage extends React.Component {
constructor(props) {
super(props);
this.getUserProfile = this.getUserProfile.bind(this);
}
componentWillMount() {
this.props.actions.getUserProfile();
}
render() {
console.log('Checking isFetching State', this.props.profile.isFetching);
return (
<div>
some text here.
</div>
);
}
}
function mapStateToProps(state) {
console.log('Mapping Stat', state.profile);
return {
profile: state.userProfile
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(profileActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(s)(ProfilePage));
my combine reducers...
import userProfile from './profile';
//some other imports...
export default combineReducers({
userProfile,
//other reducers that works...
});
Try this:
At index while creating store
export const store = createStore(
combineReducer,
applyMiddleware(thunk) // choose you middleware....
//initial state as per need
);
At reducer:
import {
FETCH_PROFILE,
FETCH_PROFILE_SUCCESS,
FETCH_PROFILE_FAIL
} from '../actions/profile';
export default function userProfile(state= {
initialized: true,
init:false,
success:false,
fail:false,
}, action) {
switch(action.type) {
case FETCH_PROFILE:{
const message = action.message;
return Object.assign({}, state, {
init:true,
success:false,
fail:false,
data:message,
})
}
}
case FETCH_PROFILE_SUCCESS:{
const data = action.data;
return Object.assign({}, state, {
init:false,
success:true,
fail:false,
data:data,
})
}
case FETCH_PROFILE_FAIL:{
const err = action.err;
return Object.assign({}, state, {
init:false,
success:false,
fail:true,
data:err,
})
}
default:
return state
}
}
At Component:
import React from 'react';
//use can use this if required.
//import withStyles from 'isomorphic-style-loader/lib/withStyles';
import {connect } from 'react-redux';
import { profileActions} from '../../actions/profile';
//import {bindActionCreators} from 'redux';
class ProfilePage extends React.Component {
constructor(props) {
super(props);
this.state{
success:false;
}
}
componentWillMount() {
this.props.getUserProfile();
}
componentWillReciveProps(nextprop){
if(nextprop.success===true){
this.setState({success==true});
}
}
render() {
return (
{(this.state.success)?<div>this.props.profile.data.yourdata</div>:<div>Loading....</div>}
);
}
}
function mapStateToProps(state) {
return {
profile: state.userProfile
};
}
export default connect(mapStateToProps,{profileActions
})(ProfilePage);
At action:
export const FETCH_PROFILE = 'FETCH_PROFILE';
export const FETCH_PROFILE_SUCCESS = 'FETCH_PROFILE_SUCCESS';
export const FETCH_PROFILE_FAIL = 'FETCH_PROFILE_FAIL';
export function getUserProfile() {
return (dispatch) => {
dispatch(getUserProfileStart(const message:"fetch start"));
const config2 = {
method: 'GET',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'include',
body: ``,
};
fetch('http://api.example.com/profile/',config2)
.then((resp) => resp.json())
.then(function(data) {
dispatch(getUserProfileSuccess(data));
return 0;
}).catch(function(error){
return dispatch(getUserProfileError(error));
})
}
}
function getUserProfileSuccess(data) {
return {
type: FETCH_PROFILE_SUCCESS,
data
}
}
function getUserProfileStart(message) {
return {
type: FETCH_PROFILE,
message
}
}
function getUserProfileError(err) {
return {
type: FETCH_PROFILE,
err
}
}
I'm trying to make an api call in action with axios and pass the results of it to the reducer.
Though action is triggered, reducer isn't. And I can't understand why.
Here's the component that should make api call before mounting
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
//actions
import { getPost } from '../actions/';
class PostShow extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
getPost(this.props.params.id);
}
render() {
console.log(this.props.activePost);
return (
<div>
<h1> hello from a post</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
activePost: state.posts.activePost
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
getPost
}, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(PostShow);
Here's my action
import axios from 'axios';
import { FETCH_POSTS, SEND_POST, FETCH_POST } from './types';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=qwerty';
export function fetchPosts() {
const req = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: req
}
}
export function sendPost(props) {
const req = axios.post(`${ROOT_URL}/posts${API_KEY}`, props);
return {
type: SEND_POST,
payload: req
}
}
export function getPost(id) {
console.log('action triggered');
const req = axios.get(`${ROOT_URL}/posts/${id}${API_KEY}`);
return {
type: FETCH_POST,
payload: req
}
}
And here's my reducer
import { FETCH_POSTS, FETCH_POST } from '../actions/types';
const INITIAL_STATE = {
allPosts: [],
activePost: null
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
allPosts: action.payload.data
};
case FETCH_POST:
console.log('reducer triggered');
return {
...state,
activePost: action.payload.data
};
default:
return state;
}
}
As a result I see 'action triggered' coming from console.log in action, and null coming from console.log in component, and no console.log from the reducer, so it's not triggered and I have no data to render in my component.
Though I make a request and get a response from server with the data, it doesn't go to the reducer. (moreover case FETCH_POSTS works fine and I can render a list of posts, but not a particular one).
"axios": "^0.17.0"
"redux-promise": "^0.5.3"
You need to use this.props.getPost in componentDidMount instead of getPost.
Connect sends bound action creator to component as a prop
I am new to react redux, and trying to make frineds search function with mock-api call(https://github.com/DerekCuevas/friend-list/blob/master/redux-saga-solution/api/index.js).
In this project, I am just getting stuck with props are not updated when redux-state update. When the fetch action dispatch, states is updated well. I can see successfully states update by redux dev tool, but there's just error "" at container even though I connect the container component with redux store by connect method. I appreciate if you guys have some idea to make updating props to container well.
here is my code.
https://github.com/gakuakachi/friends-list
containers/Main/index.js
const mapStateToProps = state => {
const { isFetching, friends, error } = state.friends;
return {
isFetching,
friends,
error
}
}
const mapDispatchToProps = dispatch => {
return {
fetchFriendsStart: friends => {
dispatch(Actions.fetchFriendsStart(friends))
}
}
}
export default connect(mapStateToProps, mapDispatchToProps
)(Main);
containers/Main/reducer.js
import { combineReducers } from 'redux'
import * as ActionType from './constants';
import { fromJS } from 'immutable';
/*
*
* Main reducer
*
*/
const initialState = fromJS({
isFetching: false,
query: '',
friends: [],
error: ''
});
export default function friendsReducer( state = initialState, action) {
switch (action.type) {
case ActionType.FRIENDS_FETCH_START:
return state.set('isFetching', false);
case ActionType.FRIENDS_FETCH_SUCCESSED:
return state.merge({
isFetching: false,
friends: action.friends
});
case ActionType.FRIENDS_FETCH_FAILED:
return state.merge({
isFetching: false,
error: action.error
});
default:
return state;
}
}
containers/Main/sagas.js
import { fork, call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import * as Actions from './actions';
import * as ActionTypes from './constants';
import search from './service/api';
function* helloSaga() {
console.log('Hello Sagas!');
}
function* fetchFriendsSaga(action) {
try {
// console.log("this is withing fetchFriendsSaga" + " "+ action.param)
const friends = yield call(search, action.param);
yield put({type: ActionTypes.FRIENDS_FETCH_SUCCESSED, friends: friends });
}catch(e) {
yield put({type: ActionTypes.FRIENDS_FETCH_FAILED, error: e.message});
}
}
function* fetchFriendsWatherSaga() {
yield takeEvery(ActionTypes.FRIENDS_FETCH_START, fetchFriendsSaga);
}
function* rootSaga() {
yield [
fork(helloSaga),
fork(fetchFriendsWatherSaga)
];
}
export default rootSaga;
containers/Main/actions.js
import fetch from 'isomorphic-fetch'
import { Api } from './service/api';
import * as ActionType from './constants';
export function fetchFriendsStart(param) {
return {
type: ActionType.FRIENDS_FETCH_START,
param
}
}
export const receiveFriends = friends => {
return {
type: ActionType.FRIENDS_FETCH_SUCCESSED,
friends
}
}
export const friendsFetchFailed = error => {
return {
type: ActionType.FRIENDS_FETCH_FAILED,
error
}
}