redux-saga: function is called twice - javascript

I am toggling visibility of a modal in react based on result of some logic performed in saga middleware by dispatching action from saga.
I went through:
Action is being dispatched twice on github
takeEvery/takeLatest is executed twice even though action dispatched only once
Wasn't very helpful.
Store
export default function configureStore(preloadedState) {
const sagaMiddleware = createSagaMiddleware();
const middlewares = [..otherMiddleware, sagaMiddleware, ...someMoreMiddlewares];
const store = createStore({
// other configuration,
// middleWares
})
sagaMiddleware.run(rootRunner);
return store;
}
Reducer:
const initialState = {
activeSwitch: '1',
modalVisibility: false,
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case 'TOGGLE_MODAL':
return state.set('modalVisibility', !state.get('modalVisibility'));
case 'UPDATE_ACTIVE_SWITCH':
// update active switch
default:
return state;
}
}
Action:
export const switchOption = payload => ({
type: 'SWITCH_OPTION',
payload,
})
export const toggleModal = () => ({
type: 'TOGGLE_MODAL',
})
export const updateActiveSwitch = payload => ({
type: 'UPDATE_ACTIVE_SWITCH',
payload,
})
Component:
import switchOption from 'action';
function Component(props) {
return <div onClick={props.switchOpt(somePayloadParameter)} />;
}
const mapDispatchToProps = state => ({
switchOpt: (somePayloadParameter) => dispatch(switchOption(somePayloadParameter)),
})
export default connect(null, mapDispatchToProps)(Component);
RootSaga:
export default function* rootRunner() {
yield all([ fork(watcher) ])
}
Saga:
function* worker(payload) {
console.log('hey');
yield put({'TOGGLE_MODAL'})
// Perform some task and wait for modal ok button click
yield take('MODAL_OK');
if (taskSuccess) {
yield put({ type: 'UPDATE_ACTIVE_SWITCH', someValue});
yield put({ type: 'TOGGLE_MODAL'}};
}
export default function* watcher() {
while(true) {
yield actionObj = yield take('SWITCH_OPTION');
yield call(worker, actionObj.payload);
}
}
Modal is never visible as 'TOGGLE_MODAL' is being dispatched twice from saga, as a result of watcher calling worker twice.
If I put a debugger just after while(true) { in watcher, on page load, that breakpoint is hit twice.
Even if I remove every line from worker, it is still running twice.
Why is my saga code running twice?
EDIT
Component:
import switchOption from 'action';
function Component(props) {
return <div onClick={props.switchOpt(somePayloadParameter)} />;
}
const mapDispatchToProps = state => ({
// switchOption is action from action.js
switchOpt: (somePayloadParameter) => dispatch(switchOption(somePayloadParameter)),
})
export default connect(null, mapDispatchToProps)(Component);
Redux monitor middleware logs to the console in dev tools following three actions, after executing saga function when it is called onClick for the first time:
'SWITCH_OPTION'
'TOGGLE_MODAL' --> with modalVisibility set to true
'TOGGLE_MODAL' --> with modalVisibility set to false
Now the click on div becomes useless as MODAL never popped up and there is no OK button to click on.

Right now Component is calling props.switchOpt every time it renders. Instead, create a new function that can be passed by reference and is then called with onClick:
function Component(props) {
return <div onClick={() => { props.switchOpt(somePayloadParameter); }} />;
}

Related

Redux action fires twice

I have this redux todo app that updates the state of the remaining tasks based on the number of incomplete tasks.
The app is working without any errors or problems but when I add a task, toggle completion, and remove a task, the action type of remainingTasks/updateRemainingTasks fires twice:
Interestingly, that action only fires once when removing a task that has been completed:
These are the code for that slice and its corresponding component:
SLICE
import { createSlice } from "#reduxjs/toolkit";
const remainingTasksSlice = createSlice({
name: "remainingTasks",
initialState: 0,
reducers: {
updateRemainingTasks: (state, action) => {
return action.payload;
},
},
});
// Selectors
export const selectRemainingTasksSlice = (state) => state.remainingTasksReducer;
// Actions
export const { updateRemainingTasks } = remainingTasksSlice.actions;
// Reducers
export default remainingTasksSlice.reducer;
COMPONENT
import { useSelector, useDispatch } from "react-redux";
import {
selectRemainingTasksSlice,
updateRemainingTasks,
} from "./remainingTasksSlice";
import { selectTaskSlice } from "../task/taskSlice";
const RemainingTasks = () => {
const dispatch = useDispatch();
const remainingTasksSlice = useSelector(selectRemainingTasksSlice);
const taskSlice = useSelector(selectTaskSlice);
// Number of Incomplete Tasks
const incompleteTasks = taskSlice.filter((task) => !task.completed).length;
// Update the State of the Remaining Tasks
dispatch(updateRemainingTasks(incompleteTasks));
return (
<div>
<h1 className="header">
{remainingTasksSlice > 1
? `${remainingTasksSlice} Tasks Left`
: `${remainingTasksSlice} Task Left`}
</h1>
</div>
);
};
export default RemainingTasks;
I was wondering if this is a normal thing or my code isn't well optimized.
I think you have to call dispatch into a useEffect hook:
....
useEffect(()=>{
// Number of Incomplete Tasks
const incompleteTasks = taskSlice.filter((task) => !task.completed).length;
// Update the State of the Remaining Tasks
dispatch(updateRemainingTasks(incompleteTasks));
}, [taskSlice]);
....
otherwise you call dispatch every time you render the Component.

Draft.js - How to make smooth real-time editor with grammar checking functionality?

I tried to make a rich text editor with real-time grammar checking functionality.
I have my own dictionary and checking engine.
The problem here is the editor is not smooth as checking engine is run each time I make changes to draft.js editor even if I just move the cursor position.
Here is my RichTextEditor component.
import {updateEditorState, checkEditorState} from './logic/actions';
...
class RichTextEditor extends React.Component {
...
onChange = (editorState) => {
this.props.updateEditorState(editorState);
this.props.checkEditorState(editorState);
}
...
render() {
return (<Editor
editorState={this.props.editorState}
onChange={this.onChange}
/>);
}
}
const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
updateEditorState: bindActionCreators(updateEditorState, dispatch),
checkEditorState: bindActionCreators(checkEditorState, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(RichTextEditor);
logic/actions.js
import * as actionTypes from './rteActionTypes'
...
export const updateEditorState = (data) => {
return ({
type: actionTypes.UPDATE_EDITOR_STATE,
data: data
});
}
export const checkEditorState = (data) => {
return ({
type: actionTypes.CHECK_EDITOR_STATE,
data: data
});
}
...
logic/saga.js
import { takeLatest, all, call, put } from 'redux-saga/effects';
import * as actionTypes from './rteActionTypes';
import { checkEditorState } from './CheckEngine';
function* checkEditor(action) {
const {updated, editorState} = yield call(checkEditorState, action.data)
if (updated) {
yield put({ type: actionTypes.UPDATE_EDITOR_STATE, data: editorState })
}
}
export default function* saga() {
yield all([
takeLatest(actionTypes.CHECK_EDITOR_STATE, checkEditor)
]);
}
logic/reducer.js
import * as actionTypes from './rteActionTypes';
import {
EditorState,
CompositeDecorator,
convertFromRaw
} from 'draft-js';
const initialState = {
editorState: EditorState.createEmpty(decorator),
};
export default (state = initialState, action) => {
switch (action.type) {
case actionTypes.UPDATE_EDITOR_STATE:
return {
...state,
editorState: action.data
};
...
}
}
I know this implementation is not a good practise because the UI will be blocked until the redux action is dispatched successfully and that's the main reason for non-smooth editing.
I want to make a background thread which detects the changes of store variable 'editorState'.
Once the change is detected, it runs checkEngine and then dispatches a new redux action to update the editorState with grammar checked editorState.
But I don't know how to create a background thread or a worker for redux store in React.js
Can you help me with this problem?
Thanks.

Redux action not firing - no errors

I'm trying to call a simple action on click which fires a dispatch action. I can't seem to get a result or even indiciation that it's firing.
I'm trying to dispatch on click in a component. I've also tried putting a console.log in the action to see if it even gets fired but it doesn't. Redux dev tools also doesn't suggest it even gets fired on click.
onClick={() => {
setAQIType(name);
}}
Action:
import { SET_AQITYPE } from "./types";
export const setAQIType = (AQIType) => dispatch => {
dispatch({
type: SET_AQITYPE,
payload: { AQIType }
});
};
Reducer:
import { SET_AQITYPE } from '../actions/types';
const initialState = {
aqiType: 'DEFAULT',
loading: false,
};
export default function(state = initialState, action){
const { type, payload } = action;
switch(type){
case SET_AQITYPE:
return [...state, payload];
default:
return state;
}
}
Types:
export const SET_AQITYPE = 'SET_AQITYPE';
Three errors,
In reducer: Your state is an object and not a list.
In reducer: Assign payload to aqiType key
In dispatch: Payload is a string and not an object.
To fix:
export const setAQIType = (AQIType) => dispatch => {
dispatch({
type: SET_AQITYPE,
payload: AQIType // (3) pass as string
});
};
// In reducer
case SET_AQITYPE:
return { // (1) object
...state,
aqiType: payload // (2) specify aqiType key
};
This assumes that you've checked the basic example with connect() and mapDispatchToProps.
Most likely you missed to connect the component with the redux store, which means there is no dispatch function passed to your action.
https://react-redux.js.org/using-react-redux/connect-mapdispatch
Cheers
Try to return inside action function as below:
import { SET_AQITYPE } from "./types";
export const setAQIType = (AQIType) => dispatch => {
return dispatch({
type: SET_AQITYPE,
payload: { AQIType }
});
};

Create a Redux-saga listener

I have a UPDATE_DATE action on redux saga.
I want to update multiple resources every time that Date is modified. I would like to implement a listener to action UPDATE_DATE_SUCCESS that triggers a callback. Any idea? I would be great if I can call it from the React component.
Action:
UPDATE_DATE
UPDATE_DATE_SUCCESS
UPDATE_DATE_FAILURE
export const {
updateDate,
OnDateChange,
} = createActions({
[ActionTypes.UPDATE_DATE]: (date) => date,
});
Saga
export function* updateDate(newDate) {
console.log('from sagas to reducer', newDate); // eslint-disable-line no-console
try {
yield put({
type: ActionTypes.UPDATE_DATE_SUCCESS,
payload: newDate,
});
} catch (err) {
/* istanbul ignore next */
yield put({
type: ActionTypes.UPDATE_DATE_FAILURE,
payload: err,
});
}
}
export default function* root() {
yield all([
takeLatest(ActionTypes.UPDATE_DATE, updateDate),
takeEvery(ActionTypes.UPDATE_DATE_SUCCESS, test),
]);
}
Desirable implementation on React Component
componentDidMount() {
const { dispatch } = this.props;
dispatch(onDateChange((newDate) => {
dispatch(getApps(newDate));
dispatch(getPlatforms(newDate));
dispatch(getNetworks(newDate));
dispatch(getCountries(newDate));
dispatch(getFormats(newDate));
dispatch(getDevices(newDate));
dispatch(getNetworkKeys(newDate));
}));
}
Any help? thank you!
The idea behind Redux Sagas is to handle the side-effects of an action/event on the Saga rather than in the components.
I think the way of doing that would be something like this (Note the snippet code is not tested, use it as reference/example only):
// --> Saga <-- //
import { getApps, getPlatforms, getNetworks, getCountries, getFormats, getDevices, getNetworkKeys } from './actions.js';
export function* updateDateWatcher(action) {
const { payload: newDate } = action;
if (!newDate) return;
try {
// dispatch all required actions
yield all([
put(getApps(newDate),
put(getPlatforms(newDate),
put(getNetworks(newDate)),
put(getCountries(newDate)),
put(getFormats(newDate)),
put(getDevices(newDate)),
put(getNetworkKeys(newDate)),
]);
// then trigger a success action if you want to
yield put(updateDataSuccess());
} catch (err) {
// if something wrong happens in the try this will trigger
yield put(updateDateFailure(err));
}
}
export default function* root() {
yield all([
takeLatest(ActionTypes.UPDATE_DATE, updateDateWatcher),
]);
}
// --> Component <-- //
import { updateDate } from './actions.js';
class MyComponent extends Component {
componentDidMount() {
const { onDateChange } = this.props;
const date = new Date();
onDateChange(date); // -> triggers ActionTypes.UPDATE_DATE
}
}
const mapDispatchToProps = dispatch => ({
onDateChange: (newDate) => dispatch(updateDate(newDate)),
});
export default connect(null, mapDispatchToProps)(MyComponent);
I dont quite understand what you are trying to do but... if you just want a listener, why dont just subscribe a take action that listen for an specific action?
function * mySuperSagaListener() {
const listener = yield take('some_action_name')
// calculate the new state here
// here
// here
// when you are done... update the state
const updateState = yield put({type: 'some_action_name_update', payload: newState})
}
Then you components only will be subscribe of the piece of the state via react-redux HOC... and will update according... and if you want to dispatch actions from the components just:
dispatch({type: 'some_action_name'})
Best!

Re-rendering react component after clicking Like button (with Redux)

I have the following React component that shows all the users posts through the "renderPosts" method. Below it there's a like/unlike button on whether the currently logged in user has liked the post.
However, when I click on the like button, the component does not re-render in order for the "renderPosts" method to create an unlike button and the "like string" is modified as expected. Only when I go to another component and then come back to this component does the unlike button display and vice versa.
Is there anyway that I could fix this with Redux in my app? I tried this.forceUpdate after the onClick event but still does not work...
Also I tried creating a new Reducer called "likers", according to robinsax which basically get the array of users who like a particular post and imported it as props into the component but got
"this.props.likers.includes(currentUser)" is not a function
When the app first gets to the main page (PostIndex), probably because this.props.likers is still an empty object returned from reducer
Here is the code for my action creator:
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return{
type: LIKE_POST,
payload: post
}
}
export function unlikePost(username,postId){
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/unlikePost`,request);
return{
type: UNLIKE_POST,
payload: post
}
}
And this is my reducer:
import {LIKE_POST,UNLIKE_POST} from '../actions/index.js';
export default function(state = {},action){
switch(action.type){
case LIKE_POST:
const likers = action.payload.data.likedBy;
console.log(likers);
return likers;
case UNLIKE_POST:
const unlikers = action.payload.data.likedBy;
console.log(unlikers);
return unlikers;
default:
return state;
}
}
I would really appreciate any help since I'm a beginner
import { fetchPosts } from "../actions/";
import { likePost } from "../actions/";
import { unlikePost } from "../actions/";
class PostsIndex extends Component {
componentDidMount() {
this.props.fetchPosts();
}
renderPost() {
const currentUser = Object.values(this.props.users)[0].username;
return _.map(this.props.posts, post => {
return (
<li className="list-group-item">
<Link to={`/user/${post.username}`}>
Poster: {post.username}
</Link>
<br />
Created At: {post.createdAt}, near {post.location}
<br />
<Link to={`/posts/${post._id}`}>{post.title}</Link>
<br />
//error here, with this.props.likers being an
//array
{!this.props.likers.includes(currentUser) ? (
<Button
onClick={() => this.props.likePost(currentUser,post._id)}
bsStyle="success"
>
Like
</Button>
) : (
<Button
onClick={() => this.props.unlikePost(currentUser,post._id)}
bsStyle="warning"
>
Unlike
</Button>
)}{" "}
{post.likedBy.length === 1
? `${post.likedBy[0]} likes this`
: `${post.likedBy.length} people like this`}
</li>
);
});
}
function mapStateToProps(state) {
return {
posts: state.posts,
users: state.users,
likers: state.likers
};
}
}
Seems like the like/unlike post functionality isn't causing anything in your state or props to change, so the component doesn't re-render.
You should change the data structure you're storing so that the value of post.likedBy.includes(currentUser) is included in one of those, or forceUpdate() the component after the likePost and unlikePost calls.
Please do it the first way so I can sleep at night. Having a component's render() be affected by things not in its props or state defeats the purpose of using React.
As noted in other answers, you need to use redux-thunk or redux-saga to make async calls that update you reducer. I personally prefer redux-saga. Here's is a basic implementation of React, Redux, and Redux-Saga.
Redux-Saga uses JavaScript generator functions and yield to accomplish the goal of handling async calls.
Below you'll see a lot of familiar React-Redux code, the key parts of Redux-Saga are as follows:
watchRequest - A generator function that maps dispatch actions to generator functions
loadTodo - A generator function called from watchRequest to yield a value from an async call and dispatch an action for the reducer
getTodoAPI - A regular function that makes a fetch request
applyMiddleware - from Redux is used to connect Redux-Saga with createStore
const { applyMiddleware, createStore } = Redux;
const createSagaMiddleware = ReduxSaga.default;
const { put, call } = ReduxSaga.effects;
const { takeLatest } = ReduxSaga;
const { connect, Provider } = ReactRedux;
// API Call
const getTodoAPI = () => {
return fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
return response.json()
.then(response => response);
})
.catch(error => {
throw error;
})
};
// Reducer
const userReducer = (state = {}, action) => {
switch (action.type) {
case 'LOAD_TODO_SUCCESS':
return action.todo;
default:
return state;
}
};
// Sagas, which are generator functions
// Note: the asterix
function* loadTodo() {
try {
const todo = yield call(getTodoAPI);
yield put({type: 'LOAD_TODO_SUCCESS', todo});
} catch (error) {
throw error;
}
}
// Redux-Saga uses generator functions,
// which are basically watchers to wait for an action
function* watchRequest() {
yield* takeLatest('LOAD_TODO_REQUEST', loadTodo);
}
class App extends React.Component {
render() {
const { data } = this.props;
return (
<div>
<button onClick={() => this.props.getTodo()}>Load Data</button>
{data ?
<p>data: {JSON.stringify(data)}</p>
: null
}
</div>
)
}
}
// Setup React-Redux and Connect Redux-Saga
const sagaMiddleware = createSagaMiddleware();
const store = createStore(userReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchRequest);
// Your regular React-Redux stuff
const mapStateToProps = (state) => ({ data: state }); // Map the store's state to component's props
const mapDispatchToProps = (dispatch) => ({ getTodo: () => dispatch({type: 'LOAD_TODO_REQUEST'}) }) // wrap action creator with dispatch method
const RootComponent = connect(mapStateToProps, mapDispatchToProps)(App);
ReactDOM.render(
<Provider store={store}>
<RootComponent />
</Provider>,
document.getElementById('root')
);
<script src="https://npmcdn.com/babel-regenerator-runtime#6.3.13/runtime.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/0.16.2/redux-saga.min.js"></script>
<div id="root"></div>
You need to use redux-thunk middleware in order to use async actions.
First, add redux-thunk while creating store like
import thunk from 'redux-thunk';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
then change your method like this
export function likePost(username,postId) {
return function(dispatch) {
// body...
const request = {
username,
postId
}
axios.post(`${ROOT_URL}/likePost`,request)
.then(res => {
dispatch({
type: LIKE_POST,
payload: res
});
});
}
}
and now in your component after mapStateToProps, define mapDispatchToProps,
const mapDispatchToProps = dispatch => {
return {
likePost: (currentUser,postId) => dispatch(likePost(currentUser, postId)),
// same goes for "unlike" function
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
The problem is in your action creator.
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
// this is an async call
const post = axios.post(`${ROOT_URL}/likePost`,request);
// next line will execute before the above async call is returned
return{
type: LIKE_POST,
payload: post
}
}
Because of that your state is likely never updated and stays in the initial value.
You would need to use either redux-thunk or redux-saga to work with async actions.
As they say use redux-thunk or redux-saga. If your new to redux I prefer redux-thunk because it's easy to learn than redux-saga. You can rewrite your code like this
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return dispatch => {
post.then(res => {
dispatch(anotherAction) //it can be the action to update state
});
}
}

Categories