Redux-Saga is running twice for a single action - javascript

I have a saga to handle like requests. The user clicks a button to toggle the liked status of a photo.
The saga listens for an action of type SUBMIT_LIKE. My problem is that the submitLikeSaga is running twice for each SUBMIT_LIKE action.
e.g. In the api-error case one SUBMIT_LIKE action triggers two api calls and four RECEIVE_LIKE actions.
(using react-boilerplate if that helps.)
export function* submitLikeSaga(action) {
// optimistically update the UI
// action shape: {
// type: RECEIVE_LIKE,
// like: {id: 1, liked: true}
// }
yield put(receiveLike(action.like));
// POST like data to api
const response = yield call(
request,
`${API_ENDPOINT}/user_likes.json`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${action.token}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(action.like),
}
);
// if api call failed, reverse change made to UI
if (response.err) {
yield put(receiveLike({
id: action.like.id,
liked: !action.like.liked,
}));
}
}
export function* watchSubmitLike() {
yield* takeEvery(SUBMIT_LIKE, submitLikeSaga);
}
// All sagas to be loaded
export default [
watchFetchUsers,
watchSubmitLike,
];
EDIT: Add middleware and view code.
ProfileGrid.js
const ProfileGrid = ({
users,
submitLike,
token,
}) =>
<div className={styles.profileGrid}>
{users.map((user, i) => (
<div key={i} className={styles.gridTile}>
<GridTile
title={user.username}
actionIcon={<ActionIcon
onIconClick={() => { submitLike(user.id, !user.liked, token); }}
isActive={user.liked}
activeColor="yellow"
defaultColor="white"
/>}
>
<img style={{ width: '100%', height: 'auto' }} src={user.avatar} alt="profile" />
</GridTile>
</div>
))}
</div>;
ActionIcon.js
const ActionIcon = ({
onIconClick,
isActive,
activeColor,
defaultColor,
}) =>
<IconButton onClick={onIconClick} >
<StarBorder
color={isActive ? activeColor : defaultColor}
/>
</IconButton>;
store.js
/**
* Create the store with asynchronously loaded reducers
*/
import { createStore, applyMiddleware, compose } from 'redux';
import { fromJS } from 'immutable';
import { routerMiddleware } from 'react-router-redux';
import createSagaMiddleware from 'redux-saga';
import createReducer from './reducers';
const sagaMiddleware = createSagaMiddleware();
const devtools = window.devToolsExtension || (() => (noop) => noop);
export default function configureStore(initialState = {}, history) {
// Create the store with two middlewares
// 1. sagaMiddleware: Makes redux-sagas work
// 2. routerMiddleware: Syncs the location/URL path to the state
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
devtools(),
];
const store = createStore(
createReducer(),
fromJS(initialState),
compose(...enhancers)
);
// Extensions
store.runSaga = sagaMiddleware.run;
store.asyncReducers = {}; // Async reducer registry
// Make reducers hot reloadable, see http://mxs.is/googmo
/* istanbul ignore next */
if (module.hot) {
module.hot.accept('./reducers', () => {
System.import('./reducers').then((reducerModule) => {
const createReducers = reducerModule.default;
const nextReducers = createReducers(store.asyncReducers);
store.replaceReducer(nextReducers);
});
});
}
return store;
}
asyncInjectors.js
import { conformsTo, isEmpty, isFunction, isObject, isString } from 'lodash';
import invariant from 'invariant';
import warning from 'warning';
import createReducer from '../reducers';
/**
* Validate the shape of redux store
*/
export function checkStore(store) {
const shape = {
dispatch: isFunction,
subscribe: isFunction,
getState: isFunction,
replaceReducer: isFunction,
runSaga: isFunction,
asyncReducers: isObject,
};
invariant(
conformsTo(store, shape),
'(app/utils...) asyncInjectors: Expected a valid redux store'
);
}
/**
* Inject an asynchronously loaded reducer
*/
export function injectAsyncReducer(store, isValid) {
return function injectReducer(name, asyncReducer) {
if (!isValid) checkStore(store);
invariant(
isString(name) && !isEmpty(name) && isFunction(asyncReducer),
'(app/utils...) injectAsyncReducer: Expected `asyncReducer` to be a reducer function'
);
store.asyncReducers[name] = asyncReducer; // eslint-disable-line no-param-reassign
store.replaceReducer(createReducer(store.asyncReducers));
};
}
/**
* Inject an asynchronously loaded saga
*/
export function injectAsyncSagas(store, isValid) {
return function injectSagas(sagas) {
if (!isValid) checkStore(store);
invariant(
Array.isArray(sagas),
'(app/utils...) injectAsyncSagas: Expected `sagas` to be an array of generator functions'
);
warning(
!isEmpty(sagas),
'(app/utils...) injectAsyncSagas: Received an empty `sagas` array'
);
sagas.map(store.runSaga);
};
}
/**
* Helper for creating injectors
*/
export function getAsyncInjectors(store) {
checkStore(store);
return {
injectReducer: injectAsyncReducer(store, true),
injectSagas: injectAsyncSagas(store, true),
};
}

According to documentation : https://redux-saga.js.org/docs/api/index.html#takelatestpattern-saga-args this will take your latest call and and will fire only one action
import { fork, takeLatest } from 'redux-saga/effects';
export function* watchSubmitLike() {
yield fork(takeLatest, SUBMIT_LIKE, submitLikeSaga);
}

Your app will work fine if you make following
yield* takeEvery(SUBMIT_LIKE, submitLikeSaga); should be
yield takeEvery(SUBMIT_LIKE, submitLikeSaga);
You should use yield* only when you are sequencing sagas

I had this happen recently. In my case the saga was getting created twice. We we withSaga HOC (generally in a container)

Related

react saga - Filter data based on query's body

In my backend, I use the post request type to fetch the data, so I can send a query or any other information I want in the body, so that the data is fetched on its basis
On the front end, I use React with Saga, and I can fetch data from the server without any problem
The case in which I need help is as follows.
I have a table containing products in the database and I want to divide these products into three pages based on a field in the table
Of course, I was able to do the filtering process in Saga as follows, and there is no problem
How can I pass this query in the image variable according to my need?
Suppose I want to create three pages in React, and for each page I will send a different filter, how do I do that?
I will attach the full code that I tried to do
Waiting for your help
this action
import {
GET_SERVICES_SUPPORT_LIST,
API_RESPONSE_SUCCESS,
API_RESPONSE_ERROR,
} from "./actionType";
// common success
export const ServicesSupportApiResponseSuccess = (actionType, data) => ({
type: API_RESPONSE_SUCCESS,
payload: { actionType, data },
});
// common error
export const ServicesSupportApiResponseError = (actionType, error) => ({
type: API_RESPONSE_ERROR,
payload: { actionType, error },
});
export const getServicesSupportList = services => ({
type: GET_SERVICES_SUPPORT_LIST,
payload: services,
});
this action type
// Actions
export const API_RESPONSE_SUCCESS = "API_RESPONSE_SUCCESS";
export const API_RESPONSE_ERROR = "API_RESPONSE_ERROR";
export const GET_SERVICES_SUPPORT_LIST = "GET_SERVICES_SUPPORT_LIST";
this saga
import { call, put, takeEvery, all, fork } from "redux-saga/effects";
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import {
GET_SERVICES_SUPPORT_LIST,
} from "./actionType";
import {
ServicesSupportApiResponseSuccess,
ServicesSupportApiResponseError
} from "./action";
//Include Both Helper File with needed methods
import {
getServicesSupportList as getServicesSupportApi
}
from "../../helpers/backend_helper";
function* getServicesSupport({ payload: services }) {
try {
const response = yield call(getServicesSupportApi, services);
yield put(ServicesSupportApiResponseSuccess(GET_SERVICES_SUPPORT_LIST, response.data));
} catch (error) {
yield put(ServicesSupportApiResponseError(GET_SERVICES_SUPPORT_LIST, error));
}
}
export function* watchGetServicesSupportList() {
yield takeEvery(GET_SERVICES_SUPPORT_LIST, getServicesSupport);
}
function* servicesSupportSaga() {
yield all(
[
fork(watchGetServicesSupportList),
]
);
}
export default servicesSupportSaga;
this hook
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux'
import { getServicesSupportList} from "../../../store/actions";
const AllServicesSupportHook = () => {
const dispatch = useDispatch();
//to get state from redux
const { servicesSupportList, loder,error} = useSelector((state) => ({
servicesSupportList: state.ServicesSupport.servicesSupportList,
loder: state.ServicesSupport.loder,
error: state.ServicesSupport.error,
}));
//when first load
useEffect(() => {
if (loder) {
dispatch(getServicesSupportList());
}
}, [dispatch]);
let test ;
try {
if (servicesSupportList.paginator)
test = {query: {categoryTickets : 2} //This is definitely wrong
} catch (e) { }
const getPage = (services) => {
dispatch(getServicesSupportList(services));
}
return [servicesSupportList, loder,error,getPage]
};
export default AllServicesSupportHook;
get list
export const getServicesSupportList = (services) => api.create(url.GET_SERVICES_SUPPORT_LIST,services);
this reducer
import {
GET_SERVICES_SUPPORT_LIST,
API_RESPONSE_SUCCESS,
API_RESPONSE_ERROR,
} from "./actionType";
const INIT_STATE = {
servicesSupportList: [],
loder: true
};
const ServicesSupport = (state = INIT_STATE, action) => {
switch (action.type) {
case API_RESPONSE_SUCCESS:
switch (action.payload.actionType) {
case GET_SERVICES_SUPPORT_LIST:
return {
...state,
servicesSupportList: action.payload.data,
loder: false,
};
default:
return { ...state };
}
case API_RESPONSE_ERROR:
switch (action.payload.actionType) {
case GET_SERVICES_SUPPORT_LIST:
return {
...state,
error: action.payload.error,
loder: true,
};
default:
return { ...state };
}
case GET_SERVICES_SUPPORT_LIST: {
return {
...state,
loder: false,
};
}
default:
return { ...state };
}
};
export default ServicesSupport;
this components
import React from "react";
import { Row, Container ,Alert} from "reactstrap";
import ServicesSupportListItems from "./ServicesSupportListCard";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Loader from "../../Common/Loader";
const ServicesSupportList = ({ data, loder, error }) => {
return (
<Container fluid={true}>
<Row>
{loder === false ? (
data.data ? (
data.data.map((item, index) => {
return (
<ServicesSupportListItems
key={index}
id={item.id}
categoryTickets={item.categoryTickets}
title={item.titleProduct}
description={item.description}
point={item.point}
/>
);
})
) : (<Alert color="danger"> {error} </Alert>)
) : ( <Loader error={error} />
)}
<ToastContainer closeButton={false} />
</Row>
</Container>
);
};
export default ServicesSupportList;
I found the solution, thank you
function* getServicesSupport({ payload: services }) {
try {
var response;
if (services === "Support") {
response = yield call(getServicesSupportApi,{query: {categoryTickets : 2}});
}
if (services === "CardFree") {
response = yield call(getServicesSupportApi,{query: {categoryTickets : 6}});
}
yield put(ServicesSupportApiResponseSuccess(GET_SERVICES_SUPPORT_LIST, response.data));
} catch (error) {
yield put(ServicesSupportApiResponseError(GET_SERVICES_SUPPORT_LIST, error));
}
}

Next JS Redux not update server store

The Redux Update operations I made on the client-side in Next JS are not updated in the server store.
Hello. I have a problem. I'm developing an SSR application with Next JS. I have provided the next js link with the next-redux-wrapper. State update operations can be provided. State updates I made on the server-side can be viewed on the client-side. The redux updates I made on the client-side also appear on the client-side, but when I refresh the page, it returns to the previous position. Sample scenario:
Users have addresses. Their addresses can be taken from the DB and printed on the screen. DB updates when I add a new address or delete the old address. Along with it, it is updated in the store on the client-side. So far there is no problem. However, when I refresh the page, for example, if there are 4 addresses before updating and I deleted one, after the refresh, it is printed as 4 addresses again. It continues like this until I get data from the server again.
How can I move the client-side store updates to the server-side without having to make requests to the server over and over again?
store.js
// store.js
import { createStore, applyMiddleware } from 'redux';
import { createWrapper } from "next-redux-wrapper";
import thunkMiddleware from 'redux-thunk'
// ROOT REDUCERS
import rootReducer from "../reducers";
const bindMiddleware = (middleware) => {
if (process.env.NODE_ENV !== 'production') {
const { composeWithDevTools } = require('redux-devtools-extension')
return composeWithDevTools(applyMiddleware(...middleware))
}
return applyMiddleware(...middleware)
}
const store_ = (initialState) => {
return createStore(rootReducer, initialState, bindMiddleware([thunkMiddleware]));
}
const wrapper = createWrapper(store_/*, { debug: true }*/);
export {
wrapper
}
_app.js
// _app.js
const MyApp = ({props, Component, pageProps }) => {
const store = useStore();
if (!store.getState().R_PageSettings.initStore)
{
store.dispatch({
type: HYDRATE,
payload: {
...props.initialState
}
})
}
return (
<>
<Head>
<title>{ variables.meta.title }</title>
</Head>
<Component {...pageProps} />
</>
)
}
const wrappedApp = wrapper.withRedux(MyApp);
export default wrappedApp;
wrappedApp.getInitialProps = async ctx => {
const data = await wrapper.getServerSideProps(
async (req) => {
const { store, ctx } = req;
const reduxStates = store.getState();
let user = reduxStates.R_User.user;
if (!user)
{
const cookies = parseCookies(ctx);
if (cookies.usr && user !== undefined)
{
const getUser = await CustomersController.tokenLoginControl(cookies.usr);
if (getUser && getUser.status)
{
store.dispatch(setUserSSR(getUser.user))
user = getUser.user;
}
else
destroyCookie(ctx, 'usr');
}
}
return {
user
}
}
)(ctx)
return data;
}
action.js
// CONSTANTS
import {
C_User
} from "../constants";
export const setUserSSR = user => {
return {
type: C_User.SET_USER,
payload: {
user
}
}
}
export const setUser = user => dispatch => {
return dispatch({
type: C_User.SET_USER,
payload: {
user
}
})
}
addresspage.js
// addresspage.js
import { connect } from 'react-redux';
import { bindActionCreators } from "redux";
// COMPONENTS
import UserPageLayout from "../UserPagesLayout";
import {
CustomerAddressForm
} from "../../../components";
// CONTROLLERS
import {
CustomersController
} from "../../../controllers";
// ACTIONS
import {
setUser
} from "../../../actions";
const MyAddressPage = connect(({ R_User }) => {
return {
R_User
}
}, dispatch => {
return {
setUser: bindActionCreators(setUser, dispatch)
}
})((props) => {
const addAddressHandle = () => {
props.fullBarOpen(
<CustomerAddressForm confirmHandle={async (address, setLoading) => {
const execute = await CustomersController.addAddress(address);
if (execute.status)
{
await props.setUser(execute.user);
}
else
{
setLoading(false);
}
}}
/>
);
}
return (
<UserPageLayout>
</UserPageLayout>
);
})
export default MyAddressPage;

How can I refactor this ASYNC call in my react component to make it more readable?

I want my component to fetch an array of objects from the server. Each object is a message with author, body and date. I then want to render these messages in my react component.
My react component currently fetches data from the server before mounting. It will then store this message list in the redux state.|
I'm sure there's a better way of writing this code.
1. Can I place the fetch request in either the Action or Reducer file?
2. Can I write a function in the component to make the async call?
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from '../actions/actions_index.js';
class MessageList extends Component {
constructor(props) {
super(props)
}
componentWillMount() {
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => console.log('An error occured receiving messages', error))
.then((data) => {
this.props.fetchMessages(data.messages);
});
}
render() {
return (
<div className="message-list">
{this.props.messageList.map( (message, index) => { return <Message key={index} message={message}/> })}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ fetchMessages: fetchMessages },
dispatch
)
}
export default connect(mapStateToProps, mapDispatchToProps)(MessageList);
Can I place the fetch request in either the Action or Reducer file?
The fetch request should be placed in action creator. Where the retrieved data will be dispatched to reducer later to manipulate the data, and lastly update the store to show on UI. Here's simple flow for most of react-redux app.
UI -> Action creator (calling request, saga etc..) -> reducer -> store -> UI
Can I write a function in the component to make the async call?
Yes, this should be called action creator, and you can see actions.js below for more reference.
I think you can safely follow this sample pattern where most tutorials out there apply. I'm assuming all files listed here are in the same directory.
constant.js
const MESSAGE_FETCH__SUCCESS = 'MESSAGE/FETCH__SUCCESS'
const MESSAGE_FETCH__ERROR = 'MESSAGE/FETCH__ERROR'
export {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
}
actions.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const fetchMessageError = () => ({
type: MESSAGE_FETCH__ERROR
})
const fetchMessageSuccess = data => ({
type: MESSAGE_FETCH__SUCCESS,
payload: data
})
const fetchMessages = () => {
const data = fetch(...);
// if error
if (data.error)
fetchMessageError();
else fetchMessageSuccess(data.data);
}
export {
fetchMessages
}
reducers.js
import {
MESSAGE_FETCH__SUCCESS,
MESSAGE_FETCH__ERROR
} from './constant';
const INIT_STATE = {
messageList: []
}
export default function( state = INIT_STATE, action ) {
switch(action.type) {
case MESSAGE_FETCH__SUCCESS:
return {
...state,
messageList: action.payload
}
case MESSAGE_FETCH__ERROR:
// Do whatever you want here for an error case
return {
...state
}
default:
return state;
}
}
index.js
Please read the comment I noted
import React, { Component } from 'react';
import Message from '../components/message.jsx';
import { connect } from 'react-redux';
// Actions
import { fetchMessages } from './actions';
class MessageList extends Component {
/* If you don't do anything in the constructor, it's okay to remove calling `constructor(props)`
*/
//constructor(props) {
// super(props)
//}
// I usually put this async call in `componentDidMount` method
componentWillMount() {
this.props.fetchMessage();
}
render() {
return (
<div className="message-list">
{
/* Each message should have an unique id so they can be used
for `key` index. Do not use `index` as an value to `key`.
See this useful link for more reference: https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js
*/
this.props.messageList.map( message => <Message key={message.id} message={message}/> )
}
</div>
)
}
}
function mapStateToProps(state) {
return {
messageList: state.messageList
}
}
export default connect(mapStateToProps, {
fetchMessages
})(MessageList);
You could use redux-thunk in an action called getMessages.
So:
(The double arrow func, is to return an action, see redux-thunk)
const getMessages = ()=>(dispatch, getState)=>{
fetch('https://wagon-chat.herokuapp.com/general/messages')
.then(response => response.json(),
error => dispatch(['error', error]))
.then((data) => {
dispatch(data);
})
}
Then you've successfully reduced your component to:
componentWillMount(){
this.props.getMessages()
}
I think #Duc_Hong answered the question.
And in my opinion, I suggest using the side-effect middle-ware to make AJAX call more structured, so that we could handle more complicated scenarios (e.g. cancel the ajax request, multiple request in the same time) and make it more testable.
Here's the code snippet using Redux Saga
// Actions.js
const FOO_FETCH_START = 'FOO\FETCH_START'
function action(type, payload={}) {
return {type, payload};
}
export const startFetch = () => action{FOO_FETCH_START, payload);
// reducer.js
export const foo = (state = {status: 'loading'}, action) => {
switch (action.type) {
case FOO_FETCH_STARTED: {
return _.assign({}, state, {status: 'start fetching', foo: null});
}
case FOO_FETCH_SUCCESS: {
return _.assign({}, state, {status: 'success', foo: action.data});
}
......
}
};
Can I place the fetch request in either the Action or Reducer file?
// Saga.js, I put the ajax call (fetch, axios whatever you want) here.
export function* fetchFoo() {
const response = yield call(fetch, url);
yield put({type: FOO_FETCH_SUCCESS, reponse.data});
}
// This function will be used in `rootSaga()`, it's a listener for the action FOO_FETCH_START
export function* fooSagas() {
yield takeEvery(FOO_FETCH_START, fetchFoo);
}
Can I write a function in the component to make the async call?
// React component, I trigger the fetch by an action creation in componentDidMount
class Foo extends React.Component {
componentDidMount() {
this.props.startFetch();
}
render() {
<div>
{this.props.foo.data ? this.props.foo.data : 'Loading....'}
<div>
}
}
const mapStateToProps = (state) => ({foo: state.foo});
const mapDispatchToProps = { startFetch }
export default connect(mapStateToProps, mapDispatchToProps) (Foo);
//client.js, link up saga, redux, and React Component
const render = App => {
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
combinedReducers,
initialState,
composeEnhancers(applyMiddleware(sagaMiddleware))
);
store.runSaga(rootSaga);
return ReactDOM.hydrate(
<ReduxProvider store={store}>
<BrowserRouter><AppContainer><App/></AppContainer></BrowserRouter>
</ReduxProvider>,
document.getElementById('root')
);
}

Parameter 'initialState' cannot be referenced in its initializer

In my ReactJS / Typescript app, I'm getting the following error in my store.ts:
Parameter 'initialState' cannot be referenced in its initializer.
interface IinitialState {
fiatPrices: [];
wallets: [];
defaultCurrency: string;
}
const initialState = {
fiatPrices: [],
wallets: [],
defaultCurrency: ''
}
...
export function initializeStore (initialState:IinitialState = initialState) {
return createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
Anyone else run into this issue? Currently having to rely on // #ts-ignore
Entire store.ts file:
import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'
import thunkMiddleware from 'redux-thunk'
interface IinitialState {
fiatPrices: [];
wallets: [];
defaultCurrency: string;
}
const initialState = {
fiatPrices: [],
wallets: [],
defaultCurrency: ''
}
export const actionTypes = {
GET_PRICES: 'GET_PRICES'
}
// REDUCERS
export const reducer = (state = initialState, action: any) => {
switch (action.type) {
case actionTypes.GET_PRICES:
return state
default:
return state
}
}
// MOCK API
export async function getProgress(dispatch: any) {
try {
const priceList = await fetchPrices();
return dispatch({ type: actionTypes.GET_PRICES, payload: priceList })
}
catch (err) {
console.log('Error', err);
}
}
// Wait 1 sec before resolving promise
function fetchPrices() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ progress: 100 });
}, 1000);
});
}
// ACTIONS
export const addLoader = () => (dispatch: any) => {
getProgress(dispatch);
}
// #ts-ignore
export function initializeStore (initialState:IinitialState = initialState) {
return createStore(
reducer,
initialState,
composeWithDevTools(applyMiddleware(thunkMiddleware))
)
}
withReduxStore lib file
import React from 'react'
import { initializeStore, IinitialState } from '../store'
const isServer = typeof window === 'undefined'
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__'
function getOrCreateStore (initialState: IinitialState) {
// Always make a new store if server, otherwise state is shared between requests
if (isServer) {
return initializeStore(initialState)
}
// Create store if unavailable on the client and set it on the window object
// Waiting for (#ts-ignore-file) https://github.com/Microsoft/TypeScript/issues/19573 to be implemented
// #ts-ignore
if (!window[__NEXT_REDUX_STORE__]) {
// #ts-ignore
window[__NEXT_REDUX_STORE__] = initializeStore(initialState)
}
// #ts-ignore
return window[__NEXT_REDUX_STORE__]
}
// #ts-ignore
export default App => {
return class AppWithRedux extends React.Component {
// #ts-ignore
static async getInitialProps (appContext) {
// Get or Create the store with `undefined` as initialState
// This allows you to set a custom default initialState
const reduxStore = getOrCreateStore()
// Provide the store to getInitialProps of pages
appContext.ctx.reduxStore = reduxStore
let appProps = {}
if (typeof App.getInitialProps === 'function') {
appProps = await App.getInitialProps(appContext)
}
return {
...appProps,
initialReduxState: reduxStore.getState()
}
}
// #ts-ignore
constructor (props) {
super(props)
this.reduxStore = getOrCreateStore(props.initialReduxState)
}
render () {
return <App {...this.props} reduxStore={this.reduxStore} />
}
}
}
function initializeStore (initialState:IinitialState = initialState) { ... }
is not valid by any means, not just in TypeScript. It's incorrect to suppress the error with #ts-ignore.
initialState parameter shadows the variable of the same name from enclosing scope, so default parameter value refers the parameter itself. This will result in discarding default parameter value with ES5 target and in an error with ES6 target.
The parameter and default value should have different names:
function initializeStore (initialState:IinitialState = defaultInitialState) { ... }
Notice that the use of defaultInitialState isn't needed in a reducer, due to how initial state works. Initial state from createStore takes precedence if combineReducers is not in use.

React Native Ignite error : uncaught at root at takeLatest at saga

i am new in react native with ignite boilerplate. I am try to create simple function for login checking user data with API to Express. However, i am getting error: uncaught at root at takeLatest at login ReferenceError: call is not defined.
This is my code:
Api.js
// a library to wrap and simplify api calls
import apisauce from 'apisauce'
// our "constructor"
const create = (baseURL = 'http://localhost:4200/') => {
// ------
// STEP 1
// ------
//
// Create and configure an apisauce-based api object.
//
const api = apisauce.create({
// base URL is read from the "constructor"
baseURL,
// here are some default headers
headers: {
'Cache-Control': 'no-cache'
},
// 10 second timeout...
timeout: 10000
})
// ------
// STEP 2
// ------
//
// Define some functions that call the api. The goal is to provide
// a thin wrapper of the api layer providing nicer feeling functions
// rather than "get", "post" and friends.
//
// I generally don't like wrapping the output at this level because
// sometimes specific actions need to be take on `403` or `401`, etc.
//
// Since we can't hide from that, we embrace it by getting out of the
// way at this level.
//
const getRoot = () => api.get('')
const getRate = () => api.get('rate_limit')
const getUser = (username) => api.get('search/users', {q: username})
//Method for Backend-Server
//Users Methods
const getUsers = () => api.get('/users')
const login = (username, password) => api.post('users/login', {username: username, password: password})
// ------
// STEP 3
// ------
//
// Return back a collection of functions that we would consider our
// interface. Most of the time it'll be just the list of all the
// methods in step 2.
//
// Notice we're not returning back the `api` created in step 1? That's
// because it is scoped privately. This is one way to create truly
// private scoped goodies in JavaScript.
//
return {
// a list of the API functions from step 2
getRoot,
getRate,
getUser,
getUsers,
login
}
}
// let's return back our create method as the default.
export default {
create
}
LoginRedux.js:
import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'
/* ------------- Types and Action Creators ------------- */
const { Types, Creators } = createActions({
loginRequest: ['username', 'password'],
loginSuccess: ['username'],
loginFailure: ['error'],
logout: null
})
export const LoginTypes = Types
export default Creators
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
username: null,
error: null,
payload: null,
fetching: false
})
/* ------------- Reducers ------------- */
// we're attempting to login
export const request = (state) => state.merge({ fetching: true, payload: null })
// we've successfully logged in
export const success = (state, { username }) =>
state.merge({ fetching: false, error: null, username })
// we've had a problem logging in
export const failure = (state, { error }) =>
state.merge({ fetching: false, error })
// we've logged out
export const logout = (state) => INITIAL_STATE
/* ------------- Hookup Reducers To Types ------------- */
export const reducer = createReducer(INITIAL_STATE, {
[Types.LOGIN_REQUEST]: request,
[Types.LOGIN_SUCCESS]: success,
[Types.LOGIN_FAILURE]: failure,
[Types.LOGOUT]: logout
})
/* ------------- Selectors ------------- */
// Is the current user logged in?
export const isLoggedIn = (loginState) => loginState.username !== null
Redux index.js:
import { combineReducers } from 'redux'
import configureStore from './CreateStore'
import rootSaga from '../Sagas/'
export default () => {
/* ------------- Assemble The Reducers ------------- */
const rootReducer = combineReducers({
nav: require('./NavigationRedux').reducer,
github: require('./GithubRedux').reducer,
login: require('./LoginRedux').reducer,
search: require('./SearchRedux').reducer,
users: require('./UsersRedux').reducer
})
return configureStore(rootReducer, rootSaga)
}
LoginSagas.js:
import { put } from 'redux-saga/effects'
import LoginActions from '../Redux/LoginRedux'
// attempts to login
export function * login (api, { username, password }) {
const response = yield call(api.login, username, password)
if(response.ok) {
// dispatch successful logins
yield put(LoginActions.loginSuccess(username))
} else {
yield put(LoginActions.loginFailure('WRONG'))
}
// if (password === '') {
// // dispatch failure
// yield put(LoginActions.loginFailure('WRONG'))
// } else {
// // dispatch successful logins
// yield put(LoginActions.loginSuccess(username))
// }
}
index.js in Saga:
import { takeLatest, all } from 'redux-saga/effects'
import API from '../Services/Api'
import FixtureAPI from '../Services/FixtureApi'
import DebugConfig from '../Config/DebugConfig'
/* ------------- Types ------------- */
import { StartupTypes } from '../Redux/StartupRedux'
import { GithubTypes } from '../Redux/GithubRedux'
import { LoginTypes } from '../Redux/LoginRedux'
import { UsersTypes } from '../Redux/UsersRedux'
/* ------------- Sagas ------------- */
import { startup } from './StartupSagas'
import { login } from './LoginSagas'
import { getUserAvatar } from './GithubSagas'
import { getUsers } from './UsersSagas'
/* ------------- API ------------- */
// The API we use is only used from Sagas, so we create it here and pass along
// to the sagas which need it.
const api = DebugConfig.useFixtures ? FixtureAPI : API.create()
/* ------------- Connect Types To Sagas ------------- */
export default function * root () {
yield all([
// some sagas only receive an action
takeLatest(StartupTypes.STARTUP, startup),
takeLatest(LoginTypes.LOGIN_REQUEST, login, api),
// some sagas receive extra parameters in addition to an action
takeLatest(GithubTypes.USER_REQUEST, getUserAvatar, api),
takeLatest(UsersTypes.USERS_REQUEST, getUsers, api)
])
}
LoginScreen.js
import React, { PropTypes } from "react";
import { View, ScrollView, Text, TextInput, TouchableOpacity, Image, Keyboard, LayoutAnimation } from "react-native";
import { connect } from "react-redux";
import Styles from "./Styles/LoginScreenStyles";
import { Images, Metrics } from "../Themes";
import LoginActions from "../Redux/LoginRedux";
import { Button, Text as NBText, Contant, Form, Item, Input, Label } from "native-base";
import UsersActions from "../Redux/UsersRedux"
class LoginScreen extends React.Component {
static propTypes = {
dispatch: PropTypes.func,
fetching: PropTypes.bool,
attemptLogin: PropTypes.func,
getUsers: PropTypes.func
};
isAttempting = false;
keyboardDidShowListener = {};
keyboardDidHideListener = {};
constructor(props) {
super(props);
this.props.getUsers();
this.state = {
username: "reactnative#infinite.red",
password: "password",
visibleHeight: Metrics.screenHeight,
topLogo: { width: Metrics.screenWidth - 40 },
};
this.isAttempting = false;
}
componentWillReceiveProps(newProps) {
this.forceUpdate();
// Did the login attempt complete?
if (this.isAttempting && !newProps.fetching) {
this.props.navigation.goBack();
}
if(newProps.users!= null) {
console.log(newProps.users)
}
if(newProps.login != null) {
console.log("LOGIN TRY");
console.log(newProps.login);
}
if(newProps.fetching != null) {
console.log("LOGIN TRY 2");
console.log(newProps.fetching);
}
}
componentWillMount() {
// Using keyboardWillShow/Hide looks 1,000 times better, but doesn't work on Android
// TODO: Revisit this if Android begins to support - https://github.com/facebook/react-native/issues/3468
this.keyboardDidShowListener = Keyboard.addListener("keyboardDidShow", this.keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener("keyboardDidHide", this.keyboardDidHide);
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
keyboardDidShow = e => {
// Animation types easeInEaseOut/linear/spring
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
let newSize = Metrics.screenHeight - e.endCoordinates.height;
this.setState({
visibleHeight: newSize,
topLogo: { width: 100, height: 70 },
});
};
keyboardDidHide = e => {
// Animation types easeInEaseOut/linear/spring
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({
visibleHeight: Metrics.screenHeight,
topLogo: { width: Metrics.screenWidth - 40 },
});
};
handlePressLogin = () => {
// const { username, password } = this.state
// this.isAttempting = true
// attempt a login - a saga is listening to pick it up from here.
this.props.attemptLogin(this.state.username, this.state.password);
console.log("Try to login");
console.log(this.props.login);
this.props.navigation.navigate("LaunchScreen");
};
handleChangeUsername = text => {
this.setState({ username: text });
};
handleChangePassword = text => {
this.setState({ password: text });
};
render() {
const { username, password } = this.state;
const { fetching } = this.props;
const editable = !fetching;
const textInputStyle = editable ? Styles.textInput : Styles.textInputReadonly;
return (
<ScrollView
contentContainerStyle={{ justifyContent: "center" }}
style={[Styles.container, { height: this.state.visibleHeight }]}
keyboardShouldPersistTaps="always"
>
<Image source={Images.logo} style={[Styles.topLogo, this.state.topLogo]} />
<View style={Styles.form}>
<Form>
<Item stackedLabel>
<Label>Username</Label>
<Input
ref="username"
value={username}
editable={editable}
keyboardType="default"
returnKeyType="next"
autoCapitalize="none"
autoCorrect={false}
onChangeText={this.handleChangeUsername}
underlineColorAndroid="transparent"
onSubmitEditing={() => this.password._root.focus()}
/>
</Item>
<Item stackedLabel>
<Label>Password</Label>
<Input
ref={ref => (this.password = ref)}
value={password}
editable={editable}
keyboardType="default"
returnKeyType="go"
autoCapitalize="none"
autoCorrect={false}
secureTextEntry
onChangeText={this.handleChangePassword}
underlineColorAndroid="transparent"
onSubmitEditing={this.handlePressLogin}
/>
</Item>
</Form>
<View style={[Styles.loginRow]}>
<Button style={{ flex: 1, justifyContent: "center" }} full onPress={this.handlePressLogin}>
<NBText>Sign In</NBText>
</Button>
<Button
style={{ flex: 1, justifyContent: "center" }}
full
onPress={() => this.props.navigation.goBack()}
>
<NBText>Cancel</NBText>
</Button>
</View>
</View>
</ScrollView>
);
}
}
const mapStateToProps = state => {
return {
fetching: state.login.fetching,
login: state.login,
users: state.users
};
};
const mapDispatchToProps = dispatch => {
return {
attemptLogin: (username, password) => dispatch(LoginActions.loginRequest(username, password)),
getUsers: () => dispatch(UsersActions.usersRequest())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
Please help thanks.
You need to import call to use it
as import {call, put} from 'redux-saga/effects', in your LoginSagas.js

Categories