Redux dispatch in useEffect causes numerous redux errors - reactjs

Edit - Fixed The problem was absolute garbage redux code as it was my first time using it. I studied Redux and rewrote my code and it works fine. It wasn't working because there was way too much garbage code working at once.
I'm using nextJS and when visiting a shared URL such as /username/p/postID I want to display the initial post modal/popover.
async function presentInitialPost(postID: string) {
const postSnap = await db.collection("posts").doc(postID).get();
const postData = postSnap.data();
if(postData){
dispatch(showModalPostView(postData as Post));
}
}
useEffect(() => {
if(initialPostID){
presentInitialPost(initialPostID)
}
}, [initialPostID])
Errors (there ae numerous of each):
"You may not unsubscribe from a store listener while the reducer is executing."
"You may not call store.getState() while the reducer is executing."
I use the same dispatch throughout my app just fine - and if I call presentInitialPost on a button click instead - it works completely fine.
I've tried delays and debugging where the error is coming from but I haven't figured it out at all, any help is appreciated, thank you.
Redux code:
showModalPostView(state, action) {
state.showModalPostViewFunction(action.payload);
},
setShowModalPostViewFunction(state, action) {
state.showModalPostViewFunction = action.payload;
return state;
},
showModalPostViewFunction: (post: Post) => {},
showModalPostViewFunction comes from my overlay wrapper component
showModalPostView = (post: Post) => {
console.log(this.state)
this.setState({
showModalPostView: true,
modalPostViewPost: post,
});
};
When the modal post view is shown here, it contains multiple useSelectors which cause the errors - but only when i present the modal post view in useEffect - it works just fine throughout the app when dispatched through a click action.
In modal post view various selectors throw the errors:
const likedPosts = useSelector((state: ReduxRootState) => state.likedPosts);

It appears you are not using redux the way it was intended to be used. What you pass to dispatch() is supposed to be an action. A reducer receives that action and returns new state, and then then new state is sent to your subscriber (the React component).
You are instead calling a function showModalPostView which is calling some setState function in the parent component and is returning who knows what. That return value is being passed as an argument dispatch, which kicks off a reducer. However, that setState is likely causing your child component to unsubscribe from the store so it can re-render.
It like you aren't actually dispatching an action; you're just calling a function, so you shouldn't be using dispatch at all.
async function presentInitialPost(postID: string) {
const postSnap = await db.collection("posts").doc(postID).get();
const postData = postSnap.data();
if(postData){
showModalPostView(postData as Post);
}
}

Related

Why is dispatch returning a promise with no result? (redux middleware)

I came into a React project that uses vanilla Redux with middleware. The way it is setup is as follows:
composeEnhancers(applyMiddleware(...middleware.map(f => f(services))))
Now middlware is an array of, well, middleware containing functions. services is an object containing external services that are injected into the middlware functions (api and so on).
The interesting part is the middleware, here is a sample of it:
...
const throwErrorFlow = ({ api }) => ({ dispatch, getState }) => next => async (action) => {
next(action)
if (action.type === actions.THROW_ERROR) {
try {
dispatch(actions.setLoadingSlot({ state: false, context: action.payload.context }))
const context = getState().ui.context
const payload = { location: action.payload.location, error: action.payload.error?.stack, context }
console.log(payload);
await api.context.throwError(payload)
dispatch(actions.setErrorModalVisibility({ payload, visibility: true }))
} catch (error) {
console.log(error);
}
}
}
const middleware = [
middlwareFunction1,
middlwareFunction2,
...
throwErrorFlow
]
export default middleware
Then I created my own test action that returns a test string. I added a similar middlware function as the rest. When dispatching this test action from the UI and logging its result, all I get is: PromiseĀ {<fulfilled>: undefined}
So I tried zooming in a bit. My action is just the following:
export const customAction = payload => ({
type: CUSTOM_ACTION,
payload: payload,
})
And my bit in the middleware is the following:
const customAsyncActionFlow = () => storeAPI => () => action => {
if (action.type === actions.CUSTOM_ACTION) {
console.log(action);
return 'TEST!'
}
}
const middleware = [
middlwareFunction1,
middlwareFunction2,
...
throwErrorFlow,
customActionFlow
]
export default middleware
And I call it from the UI as:
console.log(dispatch(customAction('Hello World!')));
My action is logged correctly to the console, but then I get PromiseĀ {<fulfilled>: undefined} instead of 'TEST!'. So I removed all other middleware functions and only kept my customActionFlow, and everything worked as I expected. Where is this Promise with no result coming from? Yes all other middleware functions do not return anything, they just modify the state. Does this have to do with this fact? And how do I 'fix' this?
EDIT: okay so I seem to understand what is going on. For each action that requires interaction with the api, a middleware is written for this action which gets applied. In the end there are 20 middleware functions all culminating with the async action for each one. The action that I defined with the test middleware that returns a value gets "lost" in the mix I guess? I am still not sure as to why my return has no effect whatsoever.
Is there a way to make my dispatch action call the my test middleware exclusively while keeping all other middlewares applied?
Oh dear. While this isn't a direct answer to your question...
I've seen that style of "write all Redux logic as custom middleware" tried a few times... and it is a bad idea!
It makes things highly over-complicated, and adding all these extra middleware for individual chunks of functionality adds a lot of overhead because they all have to run checks for every dispatched action.
As a Redux maintainer I would strongly recommend finding better approaches for organizing and defining the app logic. See the Redux Style Guide for our general suggestions:
https://redux.js.org/style-guide/
Now, as for the actual question:
When you call store.dispatch(someAction), the default behavior is that it returns the action object.
When you write a middleware, that can override the return value of store.dispatch(). A common example of this is the redux-thunk middleware, which just does return thunkFunction(dispatch, getState). This is commonly used to let thunks return promises so that the UI knows when some async logic is complete:
https://redux.js.org/tutorials/essentials/part-5-async-logic#checking-thunk-results-in-components
https://redux.js.org/tutorials/fundamentals/part-7-standard-patterns#thunks-and-promises
In this case, the middleware is itself defined as an async function, and every async function in JS automatically returns a promise. So, just having one async middleware in the chain is going to end up returning a promise from store.dispatch(anything). (This would be another reason to not write a bunch of logic directly in a custom middleware like that.)

Why can't useEffect access my state variable in a return statement?

I don't understand why my useEffect() React function can't access my Component's state variable. I'm trying to create a log when a user abandons creating a listing in our app and navigates to another page. I'm using the useEffect() return method of replicating the componentWillUnmount() lifecycle method. Can you help?
Code Sample
let[progress, setProgress] = React.useState(0)
... user starts building their listing, causing progress to increment ...
console.log(`progress outside useEffect: ${progress}`)
useEffect(() => {
return () => logAbandonListing()
}, [])
const logAbandonListing = () => {
console.log(`progress inside: ${progress}`)
if (progress > 0) {
addToLog(userId)
}
}
Expected Behavior
The code would reach addToLog(), causing this behavior to be logged.
Observed Behavior
This is what happens when a user types something into their listing, causing progress to increment, and then leaves the page.
The useEffect() method works perfectly, and fires the logAbandonListing() function
The first console.log() (above useEffect) logs something greater than 0 for the progress state
The second console.log() logs 0 for the progress state, disabling the code to return true for the if statement and reach the addToLog() function.
Environment
Local dev environment of an app built with Next.js running in Firefox 76.0.1
nextjs v 8.1.0
react v 16.8.6
I'd really appreciate some help understanding what's going on here. Thanks.
I think it is a typical stale closure problem. And it is hard to understand at first.
With the empty dependency array the useEffect will be run only once. And it will access the state from that one run. So it will have a reference from the logAbandonListing function from this moment. This function will access the state from this moment also. You can resolve the problem more than one way.
One of them is to add the state variable to your dependency.
useEffect(() => {
return () => logAbandonListing()
}, [progress])
Another solution is that you set the state value to a ref. And the reference of the ref is not changing, so you will always see the freshest value.
let[progress, setProgress] = React.useState(0);
const progressRef = React.createRef();
progressRef.current = progress;
...
const logAbandonListing = () => {
console.log(`progress inside: ${progressRef.current}`)
if (progressRef.current > 0) {
addToLog(userId)
}
}
If userId is changing too, then you should add it to the dependency or a reference.
To do something in the state's current value in the useEffect's return function where the useEffects dependencies are am empty array [], you could use useReducer. This way you can avoid the stale closure issue and update the state from the useReducer's dispatch function.
Example would be:
import React, { useEffect, useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "set":
return action.payload;
case "unMount":
console.log("This note has been closed: " + state); // This note has been closed: 201
break;
default:
throw new Error();
}
}
function NoteEditor({ initialNoteId }) {
const [noteId, dispatch] = useReducer(reducer, initialNoteId);
useEffect(function logBeforeUnMount() {
return () => dispatch({ type: "unMount" });
}, []);
return <div>{noteId}</div>;
}
export default NoteEditor;
More info on this answer
When you return a function from useEffect, it behaves like componentWillUnmount so I think it only runs while cleaning up. You'd need to actually call logAbandonListing like:
useEffect(() => {
logAbandonListing();
}, []);
So it runs everytime a component re-renders. You can read more about useEffect on https://reactjs.org/docs/hooks-effect.html
It's written excellently.
I tried using this sandbox to explain my answer.
Basically you are returning a function from your useEffect Callback. But that returned function is never really invoked so it does no actually execute and thus log the abandon action. If you look at the Code in the sandbox I have added a wrapper Parens and () afterwards to actually cause the method to be invoked leading to console.log executing.

React Js - Combine Redux and Services layers

After some researches, I found some questions on stackoverflow about what I am trying to achieve, however, I don't feel that these questions and their answers gives me the "answers" or the "directions" i am looking for..
Note: I am pretty new to react even if I already made 2 projects and implemented redux into one of them. However, I ain't new at all in C# or in Go, even less in C. Based on my experience, I am just used to some architectures and I would like to reproduce one of them.
Here is a pretyy good schema from a similar question of mine:
Situation:
So let say I have pages that contains Components. I want these pages/compoments to display some stuff. One of my functionnality is to discover a map and for that, when the client moves, he gets new parts from my API. However, I don't wanna ask the server to give me the new parts and the ones I discovered already.
My idea about it would be to use a service MapService.js. This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones (concat).
However, I have to be logged for this, so I would like an ApiService.js that would store my authentication data and automatically put them in each of my requests.
Based on what I said, we would have something as:
Page -> Component -> Service -> API
From this, the API response would be gotten by my service, handled, then returned to the component. Handled means (data added to the previous then all returned)
I saw on internet one question that was referring "MVCS" (Model View Controller Service) pattern and I think I am looking for something as but I am not sure about how to implement it in ReactJs.
Redux seems to be something that you put all around and everywhere in your solution. What I would like is to use it as a "repository" let say, to be able to manage it from a service and not from the component itself. However, a service should be a single instance shared across the app and I don't know if something such as dependency injection could be the solution in ReactJS
Feel free to ask any edit if you need more details :)
Thanks for your help !
Here is a minimal example of Redux middleware usage. Usually, redux devs are using libraries (that give you a middleware) to have access to more appropriate APIs.
Redux middleware are chained, so each middleware can call the next middleware. The first middleware of the chain is called every time dispatch function (you can have it from react-redux connect) is called. In a middleware, if there is no next middleware it is the reducers that will be called. The next middleware can be call asynchronously after receiving an action. (Redux docs will still be better than my explainations).
In my example there is a catService that provide function that call rest API. Your services can be anything (a Class instance or a singleton for example). Usually in React/Redux stack, devs don't use object oriented development.
If a component dispatch getCat(123), the catMiddleware will be called (synchronously). Then requestGetCat will be called with the id 123. When the promise returned by requestGetCat will be resolved a setCat action will be send through the reducers to update the redux state. Once the redux state is done, the component listening for cats items object will be update too (triggering a rerender).
That can look very complexe, but in fact, it is very scalable and convenient.
// catService.js
// return a promise that return a cat object
const requestGetCat = id =>
fetch(`www.catcat.com/api/cat/${id}`)
.then(response => response.json())
// catTypes.js
export const GET_CAT = 'GET_CAT'
export const SET_CAT = 'SET_CAT'
// catActions.js
export const getCat = id => ({
type: GET_CAT,
id
})
export const setCat = (cat, id) => ({
type: SET_CAT,
id,
cat
})
// catReducer.js
const initialState = {
items: {}
}
const catReducer = (state = initialState, action) => {
if (action.type === SET_CAT) {
return {
items: {
...state.items,
[action.id]: action.cat
}
}
}
}
// catMiddleware.js
const handleGetCat = (next, action) => {
requestGetCat(action.id)
.then(cat => next(setCat(cat, action.id)))
// after retrieving the cat send an action to the reducers (or next middleware if it exist)
}
const actionHandlers = {
[GET_CAT]: handleGetCat
}
// receive every actions passing by redux (if not blocked)
// store: { dispatch, getState }
// next: next middleware or reducers (that set redux state)
// action: a redux action (dispatched) with at least type property
const catMiddleware = store => next => action => {
const handler = actionHandlers[action.type]
if (handler) {
handler(next, action)
} else {
// passing the action to the next middleware (or reducer - when there is no next middleware)
next(action)
}
}
// you have to apply your middleware
// and your reducer (see redux doc)
This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones
This is something I've wanted to do in the past, but never implemented a solution for.
The issue is that you essentially want to "cross the streams"..
In Redux there are two separate streams, ie dispatch an action to update the store, and read data from the store. Each of these are executed separately from a component. Combined, they can be used in a cycle by calling an action to load data into the store which triggers an update of the component which then reads from the store.
Basically you can't have non-component code that reads from the store, and if the data is missing, fires an action to load the data, then returns the data.
Thinking about it now, I'm wondering if the way to do this without adding logic to your view component is to wrap it in a component (HOC) that provides the logic.
The HOC will check the state for the location specified in the props. If it doesn't find it, it will dispatch an action to fetch it and render a loading display. When the state is updated with the new location it will update and render the wrapped component.
You could optionally always render the wrapped component and have it cope with the missing location until it is updated with the location set..
untested brain-dump below
loader HOC:
import React, { useEffect } from "react";
import actions from "./actions";
function withLocationLoader(Component) {
const Wrapper = function ({ location, locations, loadLocation, ...props }) {
useEffect(() => {
if (!locations[location]) {
loadLocation(location);
}
}, [locations]);
if (locations[location]) {
return <Component locations={locations} {...props} />;
}
return <div>Loading...</div>;
}
const mapStateToProps = (state, ownProps) => {
return { locations: state.locations };
};
const mapActionsToProps = {
loadLocation: actions.loadLocation,
};
return connect(
mapStateToProps,
mapActionsToProps
)(Wrapper);
}
export { withLoader };
component:
function MyBareComponent({ locations }) {
return <div>{JSON.stringify(locations)}</div>;
}
const MyComponent = withLocationLoader(MyBareComponent);
export { MyComponent };
actions: (utilising redux-thunk middleware)
function setLocation(location, data) {
return { type: "SET_LOCATION", payload: { location, data } };
}
export function loadLocation(location) {
return dispatch =>
Promise.resolve({ geoData: "" }) // mock api request
.then(data => dispatch(setLocation(location, data)));
}

React onClick delete dispatch won't send second dispatch request after response received

In a component I have a button that onClick dispatches a deleteQuestion action that sends a fetch backend delete request, and when the response is received is supposed to call another action to update the Redux store.
However, since it's an onClick event, the deleteQuestion thunk function does not work like a traditional dispatch request made from ComponentWillMount and instead returns an anonymous function with a dispatch parameter that never is called. Therefore, I'm required to call the dispatch twice simultaneously in the onClick method like so:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
deleteQuestion(questionId, history)(deleteQuestion); //calling method twice
}
While this approach is effective for trigging the delete request to the Rails backend, when I receive the response, the second dispatch function that I have embedded in the deleteQuestion action -- dispatch(removeQuestion(questionId)) -- won't trigger to update the Redux store. I've tried placing multiple debuggers in the store and checking console and terminal for errors, but nothing occurs.
I've read through the Redux docs and other resources online and from what I've been able to find they all say it should be possible to include a second dispatch call in a .then request. While it's possible to do this in get, post, and patch requests, I can't figure out why it won't work in a delete request.
The thunk call I make is:
export function deleteQuestion(questionId, routerHistory) {
return (dispatch) => {
fetch(`${API_URL}/questions/${questionId}`, {
method: 'DELETE',
}).then(res => {
dispatch(removeQuestion(questionId))
})
}
}
And the github is:
https://github.com/jwolfe890/react_project1/blob/master/stumped-app-client/src/actions/questions.js
I'd really appreciate any insight, as I've been trying to get passed this for two days now!
You are calling the action deleteQuestion directly instead of having your store dispatch the delete question action for you. You should instead call the deleteQuestion from your props that is already mapped to dispatch:
handleDelete = () => {
const { questionId } = this.props.match.params
const { history } = this.props
this.props.deleteQuestion(questionId, history);
}
If you pass in an object as mapDispatchToProps each element is dispatch call. In other words your mapDispatchToProps is equivalent to:
(dispatch) => ({
deleteQuestion: (...params) => dispatch(deleteQuestion(...params))
})

Add Loading Indicator for React-Redux app with Promise Middleware

I am new to react redux. I want to add a loading text when the user pressed the search button and dismiss the text when data comes back or the action completed.
In a normal async function, I can just toggle the isLoading flag before and after the call back.
However, in my app, I am dispatching an action that returns a 'type' and 'payload' that is a promise. The middleware redux-promise then 'automatically' converts that promise payload and send it to the reducer.
In other words, the middleware does the .then action for the promise behind the scene and gives my reducer the correct data.
In this case, I am not sure how I can add a loading text to my view because as soon as I call this.props.getIdByName(this.state.value), I do not know when the data comes back.
The logical place for me would be inside the reducer since that is when the data comes back. However, I belive that would be a bad way because reducers should not perform such task?
Inside my component, I have the following function for my submit
handleSubmit(e) {
e.preventDefault();
this.props.getIdByName(this.state.value);
}
Inside my actions/index.js file, I have the following action generator
export function getIdByName(name) {
const FIELD = '/characters'
let encodedName = encodeURIComponent(name.trim());
let searchUrl = ROOT_URL + FIELD + '?ts=' + TS + '&apikey=' + PUBLIC_KEY + '&hash=' + HASH + '&name=' + encodedName;
const request = axios.get(searchUrl)
return {
type: GET_ID_BY_NAME,
payload: request
}
}
Inside my reducers/reducers.jsx
export default function (state = INITIAL_STATE, action) {
switch (action.type) {
case GET_ID_BY_NAME:
console.log(action.payload.data.data.results[0].id); <-- here the data comes back correctly because reduer called the promise and gave back the data for me
return {...state, id: action.payload.data.data.results[0].id};
default:
return state;
}
}
And in my main index.js file, I have the store created with the following middleware
const createStoreWithMiddleware = applyMiddleware(
promise,
thunk
)(createStore);
When you dispatch an action with a Promise as its payload while using redux-promise, the redux-promise middleware will catch the action, wait until the Promise resolves or rejects, and then redispatch the action, now with the result of the Promise as its payload. This means that you still get to handle your action, and also that as soon as you handle it you know it's done. So you're right in that the reducer is the right place to handle this.
However, you are also right in that reducers should not have side-effects. They should only build new state. The answer, then, is to make it possible to show and hide the loading indicator by changing your app's state in Redux :)
We can add a flag called isLoading to our Redux store:
const reducers = {
isLoading(state = false, action) {
switch (action.type) {
case 'START_LOADING':
return true;
case 'GET_ID_BY_NAME':
return false;
default:
return state;
}
},
// ... add the reducers for the rest of your state here
}
export default combineReducers(reducers);
Whenever you are going to call getIdByName, dispatch an action with type 'START_LOADING' before calling it. When getIdByName gets redispatched by redux-promise, you'll know the loading is done and the isLoading flag will be set back to false.
Now we have a flag that indicates whether we are loading data in our Redux store. Using that flag, you can conditionally render your loading indicator. :)
I am sure Pedro's answer should get you started but I recently did the very exact loader/pre-loader in my app.
The simplest way to do it is.
Create a new key in the state object in one of your reducers and call it showLoader: false.
In the same container as before(the one with the button), create a mapStateToProps and get that state property showLoader.
Inside the container that holds the button you are trying to trigger the loader with, add an onClick event which calls an action, say displayLoader. In the same function also set the this.props.showLoader to true
Create a displayLoader action write something like this:
function displayLoader() {
return {
type: DISPLAY_LOADER,
payload: {}
}
}
In the reducers, catch this action and set the showLoader back to false when your desired payload is received.
Hope it helps!

Resources