I am using saga.js with Redux in my project and I am trying to call an API but that API is not being called. The generator function is called, but with yield.put() other method is not being called. I am fairly new to Redux Saga and I am stuck here. Any help would be really appreciated.
Saga.js
import { put, takeEvery, all ,fork, takeLatest} from "redux-saga/effects";
import axios from "axios";
function* runOurAction() {
let remoteData;
yield axios.get(url).then((resp) => {
remoteData = resp.data;
});
yield put({ type: "SET_DATA", payload: remoteData });
};
function* getAsyncDataWatcher() {
yield takeLatest("GET_TEAMS", runOurAction);
}
export default function* rootSaga() {
yield fork(getAsyncDataWatcher)
}
getAsyncDataWatcher() is being called but its not calling runOurAction
Reducer.js
const teams=(state=[],action)=>{
switch(action.type) {
case "SAVE_TEAMS":
return { ...state, payload: action.payload };
case "GET_TEAMS":
return { ...state, payload: action.payload };
case "SET_DATA":
return { ...state, payload: action.payload };
default:
return state;
}
}
export default teams;
Actions.js
const getTeams = (payload) => {
return {
type: "GET_TEAMS",
payload:payload
};
};
const saveTeams = (payload) => {
return {
type: "SAVE_TEAMS",
payload:payload
};
};
export { saveTeams, getTeams };
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import rootReducer from "./reducers";
import createSagaMiddleware from "redux-saga";
import { createStore, applyMiddleware ,compose} from "redux";
import { Provider } from "react-redux";
import rootSaga from "./saga";
const sagaMiddleware = createSagaMiddleware();
const enhancers = [window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(), applyMiddleware(sagaMiddleware)];
const store = createStore(
rootReducer,
compose(...enhancers)
);
sagaMiddleware.run(rootSaga);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
//
serviceWorker.unregister();
Team.js
import React,{useEffect} from 'react'
import {useSelector,useDispatch} from "react-redux";
import {getTeams} from "../actions";
const Team=()=> {
const data=useSelector(state=>state.teams);
const dispatch=useDispatch();
useEffect(() => {
console.log("Called");
dispatch(getTeams());
}, [dispatch])
console.log("hello",data);
return (
<div>
</div>
)
}
export default Team
The problem is probably caused by axios.get() as the return is a promise. Try the following:
yield axios.get(url).then((resp) => {
return resp.data;
}).then(response => {
remoteData = response;
});
I think you can't simply call axios function directly that way. You have to wrap it in call a saga-effects which only takes a function as its argument not a Promise resolve, so it would look like:
import { call } from 'redux-saga/effects';
// Wrap in a call which takes a function as argument
const remoteData = yield call(() => axios.get(url).then(response => repsonse.data));
// Or you can simply write in shorter way
const { data: remoteData } = yield call(axios.get, url);
Related
Sory for my English!
I am doing test work on React.js. The task is to make a regular blog. I ran into an unwanted problem. As a rule, componentDidMount makes entries, ready data and is called once.
I invoke the loadPosts action in the CDM to get the data.
The takeEvery effect sees the necessary saga, but does not cause it, but skips it.
When I press a button, everything works fine.
I'm new to React. All i tried is google
repository with project
branch - dev-fullapp
index.js
import store from "./redux/store";
const app = (
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
);
store.js
import { createStore, compose, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import apiSaga from "./sagas/index";
import rootReducer from "./reducers/index";
const initialiseSagaMiddleware = createSagaMiddleware();
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
storeEnhancers(applyMiddleware(initialiseSagaMiddleware))
);
initialiseSagaMiddleware.run(apiSaga);
export default store;
sagas.js
import { put, call, takeEvery } from "redux-saga/effects";
import { fetchGetPosts } from "../apis/index";
import { setErrorPosts, setPosts } from "../actions/actions-posts";
function* workerGetPosts() {
try {
const posts = yield call(fetchGetPosts);
yield put(setPosts(posts));
} catch (err) {
yield put(setErrorPosts(err));
}
}
export default function* watchSaga() {
yield takeEvery(POSTS.LOADING, workerGetPosts);
}
actions.js
import { POSTS } from "../constants";
export const loadPosts = () => {
console.log('action-load')
return {
type: POSTS.LOADING
}
};
export const setPosts = payload => ({
type: POSTS.LOAD_SUCCESS,
payload
});
export const setErrorPosts = error => ({
type: POSTS.ERROR,
error
});
rootReducer.js
import { combineReducers } from "redux";
import postsReducer from "./reducer-posts";
import loadReducer from "./reducer-load";
const rootReducer = combineReducers({
posts: postsReducer,
isLoad: loadReducer
});
export default rootReducer;
reducer-posts.js
import { POSTS } from "../constants";
const postState = {
posts: []
};
function postsReducer(state = postState, action) {
switch (action.type) {
case POSTS.LOAD_SUCCESS:
return {
...state,
posts: [...action.payload]
};
default:
return state;
}
}
export default postsReducer;
reducer-load.js
import { POSTS } from "../constants";
import { combineReducers } from "redux";
const loadReducerPosts = (state = false, action) => {
switch (action.type) {
case POSTS.LOADING: return false;
case POSTS.LOAD_SUCCESS: return true;
case POSTS.ERROR: return false;
default: return state;
}
};
const loadReducer = combineReducers({
isLoadPost: loadReducerPosts,
});
export default loadReducer;
news.jsx
class News extends Component {
componentDidMount() {
loadPosts();
}
render() {
CONST { loadPosts }this.props
return (
<main>
// code
<button onClick={loadPosts}>Test Button</button>
</main>
);
}
}
const mapStateToProps = (
{ posts: { loading, posts, success } }
) => ({
posts,
loading,
success
});
export default connect(
mapStateToProps,
{ loadPosts }
)(News);
loadPosts method is available as props to the React component in current case. Unlike in componentDidMount, on button click you are calling the function from props. You have to use this.props.loadPosts() on both places
I'm using React-Laravel for my project.
The problem is when I tried to use redux-thunk for the asynchronous dispatch function.
My dispatch function won't get executed.
Please do help me figure out this problem.
I have already tried to use promise or redux-devtools-extension library
https://codeburst.io/reactjs-app-with-laravel-restful-api-endpoint-part-2-aef12fe6db02
app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import Layout from './jsx/Layout/Layout';
import marketplaceReducer from './store/reducers/marketplace';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const appReducer = combineReducers({
marketplace: marketplaceReducer
});
const rootReducer = (state, action) => {
return appReducer(state, action);
}
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(logger, thunk)
));
const render = (
<Provider store={store}>
<BrowserRouter>
<Layout />
</BrowserRouter>
</Provider>
);
ReactDOM.render(render, document.getElementById('root'));
marketplace.js (action)
import * as actionTypes from './actionTypes';
import axios from '../../axios';
export const loadMarketplace = () => {
console.log("Load Marketplace");
return {
type: actionTypes.LOAD_MARKETPLACE
};
}
export const successMarketplace = (data) => {
console.log("Success Marketplace");
return {
type: actionTypes.SUCCESS_MARKETPLACE,
data: data
}
}
export const failedMarketplace = () => {
console.log("Failed Marketplace");
return {
type: actionTypes.FAILED_MARKETPLACE
}
}
export const showMarketplace = () => {
console.log("Show Marketplace Action")
return dispatch => {
//This is the problem
//Inside this function, I can't see any console.log, even loadMarketplace() didn't get called.
console.log("Show Marketplace in dispatch");
dispatch(loadMarketplace());
axios.get('/marketplaces')
.then(response => {
dispatch(successMarketplace(response));
})
.catch(error => {
dispatch(failedMarketplace());
});
};
}
marketplace.js (reducer)
import * as actionTypes from '../actions/actionTypes';
const initial_state = {
data: [],
loading: false
}
const loadMarketplace = (state, action) => {
console.log("Load Marketplace Reducer")
return {
...state,
loading: true
};
}
const successMarketplace = (state, action) => {
console.log("Success Marketplace Reducer", action.data)
return {
...state,
loading: false,
data: action.data
};
}
const failedMarketplace = (state, action) => {
return {
...state,
loading: false
};
}
const reducer = (state = initial_state, action) => {
//This is called when the first init, never got it through showMarketplace() function.
console.log("Marketplace Reducer", action);
switch (action.type) {
case actionTypes.LOAD_MARKETPLACE: return loadMarketplace(state, action);
case actionTypes.SUCCESS_MARKETPLACE: return successMarketplace(state, action);
case actionTypes.FAILED_MARKETPLACE: return failedMarketplace(state, action);
default: return state;
}
}
export default reducer;
Marketplace.js (jsx view)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../../store/actions';
class Marketplace extends Component {
componentDidMount() {
console.log('[ComponentDidMount] Marketplace')
this.props.showMarketplace();
}
render() {
return (
<React.Fragment>
Marketplace
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
showMarketplace: () => dispatch(actions.showMarketplace)
};
}
export default connect(null, mapDispatchToProps)(Marketplace);
This is the result of my console.log (when loading the first time for Marketplace.js)
Please do help, I've been struggling for 2 hours or more, only because of this problem. (This is my first time using React-Laravel).
Thank you.
I already found the problem. It is not redux-thunk problem.
It is actually a normal Redux problem we found anywhere.
Marketplace.js (jsx view)
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actions from '../../../store/actions';
class Marketplace extends Component {
componentDidMount() {
console.log('[ComponentDidMount] Marketplace')
this.props.showMarketplace();
}
render() {
return (
<React.Fragment>
Marketplace
</React.Fragment>
);
}
}
const mapDispatchToProps = dispatch => {
return {
showMarketplace: () => dispatch(actions.showMarketplace) //THIS IS THE PROBLEM, IT IS NOT EXECUTING PROPERLY. THIS ONE SHOULD BE
showMarketplace: () => dispatch(actions.showMarketplace()) //SHOULD BE LIKE THIS.
};
}
export default connect(null, mapDispatchToProps)(Marketplace);
Edited: I think it is something about thunk is not added right to redux.
First of all try to add only thunk.
const store = createStore(rootReducer, composeEnhancers(
applyMiddleware(thunk)
));
If it works, maybe try to change the order of them.
I am trying to use redux-saga to call my API and passing it a parameter. I am using redux-logger and console.log() to debug. But it seems that my saga doesn't go call the API for some reason.
Here is the console log of the states... as you can see the apiMsg doesnt change, however I have it change for each action.
I think I am going wrong somewhere in the actions, reducer or the component. Or else I am calling the API wrong in the Saga.
This is my code
api.js
import axios from 'axios';
export function getPostApi(postId){
return axios.get(
`https://jsonplaceholder.typicode.com/posts/${postId}`
).then(
response => {
return response;
}
).catch(
err => {throw err;}
);
}
my store
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga'
import {createLogger} from 'redux-logger';
import rootReducer from '../Reducers';
export default function configureStore(initialState) {
const sagaMiddleware = createSagaMiddleware();
const logger = createLogger();
return {
...createStore(rootReducer, initialState, applyMiddleware(sagaMiddleware, logger)),
runSaga: sagaMiddleware.run
}
}
the saga
import {call, put, takeEvery} from 'redux-saga/effects';
import * as service from '../Services/api';
import * as actions from '../Actions';
import * as types from '../actions/actionTypes';
export function* fetchPost(action){
try{
yield put(actions.apiRequest());
const [post] = yield [call(service.getPostApi, action.postId)];
yield put(actions.apiRequestSucceeded(post));
} catch(e){
yield put(actions.apiRequestFailed());
}
}
export function* watchApiRequest() {
yield takeEvery(types.FETCH_POST, fetchPost);
}
actions
import * as types from './actionTypes';
export const fetchPost = (postId) => ({
type: types.FETCH_POST,
postId,
});
export const apiRequest = () => {
return {
type: types.API_REQUEST
}
}
export const apiRequestSucceeded = (post) => {
return {
type: types.API_REQUEST_SUCCEEDED,
post
}
}
export const apiRequestFailed = () => {
return {
type: types.API_REQUEST_FAILED
}
}
reducer
import * as types from '../actions/actionTypes';
const initialState = {
apiMsg: '',
postId: 1,
fetching: false,
post: null
};
export default function getPostData(state = initialState, action) {
switch (action.type) {
case types.API_REQUEST:
return {
...state,
apiMsg: 'API call request!',
fetching: true
}
case types.API_REQUEST_SUCCEEDED:
return {
...state,
apiMsg: 'API called succeeded!',
fetching: false,
postId: action.post.id,
post: action.title
};
case types.API_REQUEST_FAILED:
return {
...state,
apiMsg: 'API called failed!',
fetching: false,
};
default:
return state;
}
}
the component page
import React, { Component } from 'react';
import { View, Text} from 'react-native';
import { connect } from 'react-redux';
import {fetchPost} from './Actions/apiTesterActions';
class TestPage extends Component {
constructor() {
super();
}
componentDidMount() {
this.props.fetchPost(1)
}
render() {
const { postId, fetching, post } = this.props;
console.log('Test page props', postId, fetching, post);
return (
<View>
<Text>Test Page Works! </Text>
</View>
)
}
}
const mapStateToProps = (state) => {
return {
postId: state.getPostData.postId,
fetching: state.getPostData.fetching,
post: state.getPostData.post
}
};
export default connect(mapStateToProps, {fetchPost})(TestPage);
I am still learning Redux, and now redux-saga, so I don't fully understand the structure/hierarchy, but I am learning and any advice is helpful. Thank you
EDIT:
I am using this redux saga api call example as a guide.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { Provider } from 'react-redux';
import configureStore from './ReduxTest/Store/configureStore';
import rootSaga from './ReduxTest/sagas';
const store = configureStore();
store.runSaga(rootSaga)
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
registerServiceWorker();
saga/index.js
import {fork, all} from 'redux-saga/effects';
import {watchApiRequest} from './apiTester';
export default function* rootSaga() {
yield all [
fork(watchApiRequest)
]
}
After some time with a lot of debugging, I have figured the problem.
When I first started creating the application, I got the following error...
[...effects] has been deprecated in favor of all([...effects]), please update your code
As the yield all [...] was deprecated, I changed my code without taking it into consideration, I was happy the error went, but didn't realize how much of an impact it would have on my application.
(wrong) saga/index.js
import {fork, all} from 'redux-saga/effects';
import {watchApiRequest} from './apiTester';
export default function* rootSaga() {
yield all [
fork(watchApiRequest)
]
}
(right) saga/index.js
import {fork, all} from 'redux-saga/effects';
import {watchApiRequest} from './apiTester';
export default function* rootSaga() {
yield all ([
fork(watchApiRequest)
])
}
As You can see it needed to be wrapped in brackets yield all ([...])
You will get the deprecated error but your code will still work. I am unsure how to fix the deprecated error.
If someone knows how to get rid of the deprecated error than that would great.
I am new to the saga world. Although I have worked with thunk on react-native territory, I am very confused at the moment. I am trying to get the skeleton of my project going which I expect to get very large soon. With that in mind, I am trying to separate the logic into multiple files.
I have gotten the reducer to fire except it is not the way I want. I am not sure how it is even happening. My saga does not fire but my state updates. I see the console log from my reducer but nothing from the saga watcher function. What should I change?
Index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { Provider } from 'react-redux'
import reducer from './reducers'
import rootSaga from './sagas'
import App from './App'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("etlRootDiv"),
);
App.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import FileExtensionSelector from './components/FileExtensionSelector'
import { setFileExtension } from './actions'
class App extends Component {
constructor(props) {
super(props)
}
handleTypeSelect() {
console.log('handle more click');
this.props.setFileExtension('zip');
console.log(this.props);
}
componentWillReceiveProps(nextProps){
console.log(nextProps);
}
render() {
return (
<div>
<FileExtensionSelector onFileTypeSelect={this.handleTypeSelect.bind(this)} />
<div>{this.props.fileType} ...asdasd</div>
</div>
)
}
}
const mapStateToProps = ({ metaState }) => {
const { fileType } = metaState;
return { fileType };
};
const mapDispatchToProps = (dispatch) => ({
setFileExtension(ext) {
dispatch(setFileExtension(ext))
}
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
reducers/index.js
import { combineReducers } from 'redux';
import metaState from './MetaStateReducer';
const rootReducer = combineReducers({
metaState,
})
export default rootReducer
reducers/metastatereducer.js
const INITIAL_STATE = {
fileType: null,
hasHeader: false,
};
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case 'SET_FILE_EXTENSION':
console.log('/// in set file reducer ///');
console.log(action);
// console.log({ ...state, ...INITIAL_STATE, fileType: action.payload });
return { ...state,...INITIAL_STATE, fileType: action.payload };
default:
return state;
}
}
actions/metaStateActions.js
function action(type, payload = {}) {
return { type, ...payload }
}
export const SET_FILE_EXTENSION = "SET_FILE_EXTENSION";
export const setFileExtension = (extension) => action( SET_FILE_EXTENSION, { payload: extension });
actions/index.js
export { setFileExtension, SET_FILE_EXTENSION } from './metaDataActions';
sagas/metastatesagas.js
import { take, put } from 'redux-saga/effects'
import { SET_FILE_EXTENSION } from '../actions';
function* watchFileExtension(ext) {
console.log(' --- in watch file ext ---');
const { extension } = yield take(SET_FILE_EXTENSION)
console.log(`set extension is ${extension}`);
// yield put({ type: 'SET_FILE_EXTENSION', payload: ext });
}
export const metaStateSagas = [
take("SET_FILE_EXTENSION", watchFileExtension),
]
sagas/index
import { all } from 'redux-saga/effects'
import { metaStateSagas } from './MetaStateSagas';
export default function* rootSaga() {
yield all([
...metaStateSagas,
])
}
redux-saga always passes an action along to the store before attempting to process itself. So, the reducers will always run before any saga behavior executes.
I think the error is that your metaStateSagas array needs to use takeEvery, not take, but I'm not entirely sure. Try that and see if it fixes things.
This issue likely stems from a misconfiguration of redux-thunk or a misunderstanding of how to write a thunk. I've tried a lot of different ways, but from what I can tell, this should work. However, I'm still getting a console message that says its firing a redux action of undefined.
Here is my store configuration
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import App from './components/App';
import rootReducer from './reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={ store }>
<App />
</Provider>,
document.getElementById('rootElement')
);
Here is my action:
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState.isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState.about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Here is me firing the action in my component:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../../actions/about';
import getAboutContent from '../../reducers';
class AboutMe extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getAboutContent();
}
render() {
return <div>{ this.props.content }</div>
}
}
const mapStateToProps = (state) => ({
content: {} || getAboutContent(state)
})
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ getAboutContent }, dispatch)
export default connect(
mapStateToProps, mapDispatchToProps
)(AboutMe);
I've tried quite a few configurations for mapDispatchToProps, i.e. connect(..., { fetchData: getAboutContent })..., and more. Any help is greatly appreciated.
Edit:
Here is the git repo, if that is helpful to anybody: https://github.com/sambigelow44/portfolio-page
Check your reducer name,you export fetchAboutContent, but import getAboutContent.
Code in action file is seems to be incorrect.
getState is a function.
const state = getState();
Change below code.
import axios from 'axios';
export const GET_ABOUT_CONTENT_REQUEST = 'GET_ABOUT_CONTENT_REQUEST';
export const GET_ABOUT_CONTENT_FAILED = 'GET_ABOUT_CONTENT_FAILED';
export const GET_ABOUT_CONTENT_OK = 'GET_ABOUT_CONTENT_OK';
export const fetchAboutContent = () => {
const url = `http://localhost:3000/about`;
return (dispatch, getState) => {
if (getState().isInitialized === true){
console.log("desktop init should not be called when already desktop is init")
return Promise.resolve();
}
if (getState().about.isLoading) {
console.log('is loading');
return Promise.resolve();
}
dispatch({ type: GET_ABOUT_CONTENT_REQUEST });
axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));
}
}
Also you need to return promise from axios call, just add return statement.
return axios.get(url)
.then(res => dispatch({
type: GET_ABOUT_CONTENT_OK,
res
}))
.error(err => dispatch({
type: GET_ABOUT_CONTENT_FAILED,
err
}));