Stack overflow when hot reloading a reducer with React Native and Redux - javascript

I'm using Redux with React Native and HMR. When I change a reducer, the accept bubbles up to my configureStore.js file which calls this:
if (module.hot) {
module.hot.accept(() => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}
But then HMR keeps on searching the references in a loop until the stack overflow.
Could it be that there's a circular reference somewhere? If so, any tips for tracking it down? I've tried stepping through the require.js code but the 'references' are kept just as numbers making it hard to track down the right file.

Here's my exact code if you want to examine it:
export default function configureStore() {
engine = createEngine('appstate');
engine = filter(engine, null, ['appStateLoaded', 'appReady', 'tourReady', 'tourPage', 'tooltipStep', 'connected']);
const cacheState = storage.createMiddleware(engine); //OLD PARAMETER: , [LOAD, 'SET_TOUR_READY', 'SET_TOUR_PAGE'] ... replaced by reducer key filtering above instead
enhancer = compose(
applyMiddleware(thunk, cacheState),
devTools({
name: Platform.OS,
hostname: '10.0.1.201',
port: 5678,
filters: {blacklist: ['REDUX_STORAGE_SAVE']}
})
);
//reset reducer state on LOGOUT
const appReducer = combineReducers(reducers);
const rootReducer = (state, action) => {
if (action.type === LOGOUT) {
state = undefined;
}
return appReducer(state, action);
}
reducer = storage.reducer(rootReducer);
store = createStore(reducer, enhancer);
//retreive previously cached state and inject into Redux store!
const load = storage.createLoader(engine);
load(store)
.then((newState) => console.log('Loaded state:', newState))
.catch(() => console.log('Failed to load previous state'));
if(module.hot) {
module.hot.accept(() => {
let nextRootReducer = storage.reducer(rootReducer);
store.replaceReducer(nextRootReducer);
load(store)
.then((newState) => console.log('Loaded state:', newState))
});
}
return store;
}
It should give you a solid example of how to use various middleware with module.hot.accept. ...As far as the "circular reference" thing goes--the only thing to do is check your reducers and make sure they don't import each other. I think I've seen this exact issue in fact--ES6 modules can properly address cycles initial building, but perhaps the HMR build process cannot. And that's all. So you end up thinking you have code that works, but it basically doesn't support HMR. I'd setup a quick test app and implement their hot.module.accept code and verify it works for you. Maybe you'll have to adjust one thing--but since you have so little code, it will be easy to find--and then make that same fix in your own code. If that doesn't surface the issue, then minimize what you're doing with redux in your app temporarily until you isolate what was causing the problem. ..Wish I had more suggestions for ya.

Related

useEffect running two times, but my dependencies array is empty, why is this happening? [duplicate]

I am new to reactJS and am writing code so that before the data is loaded from DB, it will show loading message, and then after it is loaded, render components with the loaded data. To do this, I am using both useState hook and useEffect hook. Here is the code:
The problem is, useEffect is triggered twice when I check with console.log. The code is thus querying the same data twice, which should be avoided.
Below is the code that I wrote:
import React from 'react';
import './App.css';
import {useState,useEffect} from 'react';
import Postspreview from '../components/Postspreview'
const indexarray=[]; //The array to which the fetched data will be pushed
function Home() {
const [isLoading,setLoad]=useState(true);
useEffect(()=>{
/*
Query logic to query from DB and push to indexarray
*/
setLoad(false); // To indicate that the loading is complete
})
},[]);
if (isLoading===true){
console.log("Loading");
return <div>This is loading...</div>
}
else {
console.log("Loaded!"); //This is actually logged twice.
return (
<div>
<div className="posts_preview_columns">
{indexarray.map(indexarray=>
<Postspreview
username={indexarray.username}
idThumbnail={indexarray.profile_thumbnail}
nickname={indexarray.nickname}
postThumbnail={indexarray.photolink}
/>
)}
</div>
</div>
);
}
}
export default Home;
Can someone help me out in understanding why it is called twice, and how to fix the code properly?
Thank you very much!
Put the console.log inside the useEffect
Probably you have other side effects that cause the component to rerender but the useEffect itself will only be called once. You can see this for sure with the following code.
useEffect(()=>{
/*
Query logic
*/
console.log('i fire once');
},[]);
If the log "i fire once" is triggered more than once it means your issue is
one of 3 things.
This component appears more than once in your page
This one should be obvious, your component is in the page a couple of times and each one will mount and run the useEffect
Something higher up the tree is unmounting and remounting
The component is being forced to unmount and remount on its initial render. This could be something like a "key" change happening higher up the tree. you need to go up each level with this useEffect until it renders only once. then you should be able to find the cause or the remount.
React.Strict mode is on
StrictMode renders components twice (on dev but not production) in order to detect any problems with your code and warn you about them (which can be quite useful).
This answer was pointed out by #johnhendirx and written by #rangfu, see link and give him some love if this was your problem. If you're having issues because of this it usually means you're not using useEffect for its intended purpose. There's some great information about this in the beta docs you can read that here
Remove <React.StrictMode> from index.js
This code will be
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
this
root.render(
<App />
);
React StrictMode renders components twice on dev server
You are most likely checking the issue on a dev environment with strict mode enabled.
To validate this is the case, search for <React.StrictMode> tag and remove it, or build for production. The double render issue should be gone.
From React official documentation
Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:
Functions passed to useState, useMemo, or useReducer
[...]
Strict Mode - Reactjs docs
Similar question here My React Component is rendering twice because of Strict Mode
Please check your index.js
<React.StrictMode>
<App />
</React.StrictMode>
Remove the <React.StrictMode> wrapper
you should now fire once
root.render(
<App />
);
react root > index.js > remove <React.StrictMode> wrapper
It is the feature of ReactJS while we use React.StrictMode. StrictMode activates additional checks and warnings for its descendants nodes. Because app should not crash in case of any bad practice in code. We can say StrictMode is a safety check to verify the component twice to detect an error.
You will get this <React.StricyMode> at root of the component.
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
if you want to restrict components to render twice, You can remove <React.StrictMode> and check it. But It is necessary to use StrictMode to detect a run time error in case of bad code practice.
if you are using Next js, change reactStrictMode from "true" to false :
add this to your next.config.js
reactStrictMode: false,
I have found a very good explanation behind twice component mounting in React 18. UseEffect called twice in React
Note: In production, it works fine. Under strict mode in the development environment, twice mounting is intentionally added to handle the errors and required cleanups.
I'm using this as my alternative useFocusEffect. I used nested react navigation stacks like tabs and drawers and refactoring using useEffect doesn't work on me as expected.
import React, { useEffect, useState } from 'react'
import { useFocusEffect } from '#react-navigation/native'
const app = () = {
const [isloaded, setLoaded] = useState(false)
useFocusEffect(() => {
if (!isloaded) {
console.log('This should called once')
setLoaded(true)
}
return () => {}
}, [])
}
Also, there's an instance that you navigating twice on the screen.
Not sure why you won't put the result in state, here is an example that calls the effect once so you must have done something in code not posted that makes it render again:
const App = () => {
const [isLoading, setLoad] = React.useState(true)
const [data, setData] = React.useState([])
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
setLoad(false)//causes re render
setData(data)//causes re render
})
},[])
//first log in console, effect happens after render
console.log('rendering:', data.length, isLoading)
return <pre>{JSON.stringify(data, undefined, 2)}</pre>
}
//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
To prevent the extra render you can combine data and loading in one state:
const useIsMounted = () => {
const isMounted = React.useRef(false);
React.useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
};
const App = () => {
const [result, setResult] = React.useState({
loading: true,
data: []
})
const isMounted = useIsMounted();
React.useEffect(() => {
console.log('in effect')
fetch('https://jsonplaceholder.typicode.com/todos')
.then(result => result.json())
.then(data => {
//before setting state in async function you should
// alsways check if the component is still mounted or
// react will spit out warnings
isMounted.current && setResult({ loading: false, data })
})
},[isMounted])
console.log(
'rendering:',
result.data.length,
result.loading
)
return (
<pre>{JSON.stringify(result.data, undefined, 2)}</pre>
)
}
//render app
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The new React docs (currently in beta) have a section describing precisely this behavior:
How to handle the Effect firing twice in development
From the docs:
Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn’t be able to distinguish between the Effect running once (as in production) and a setup → cleanup → setup sequence (as you’d see in development).
So this warning should make you double check your useEffect, usually means you need to implement a cleanup function.
This may not be the ideal solution. But I used a workaround.
var ranonce = false;
useEffect(() => {
if (!ranonce) {
//Run you code
ranonce = true
}
}, [])
Even though useEffect runs twice code that matters only run once.
As others have already pointed out, this happens most likely due to a Strict Mode feature introduced in React 18.0.
I wrote a blog post that explains why this is happening and what you can do to work around it.
But if you just want to see the code, here you go:
let initialized = false
useEffect(() => {
if (!initialized) {
initialized = true
// My actual effect logic...
...
}
}, [])
Or as a re-usable hook:
import type { DependencyList, EffectCallback } from "react"
import { useEffect } from "react"
export function useEffectUnsafe(effect: EffectCallback, deps: DependencyList) {
let initialized = false
useEffect(() => {
if (!initialized) {
initialized = true
effect()
}
}, deps)
}
Please keep in mind that you should only resort to this solution if you absolutely have to!
I've had this issue where something like:
const [onChainNFTs, setOnChainNFTs] = useState([]);
would trigger this useEffect twice:
useEffect(() => {
console.log('do something as initial state of onChainNFTs changed'); // triggered 2 times
}, [onChainNFTs]);
I confirmed that the component MOUNTED ONLY ONCE and setOnChainNFTs was NOT called more than once - so this was not the issue.
I fixed it by converting the initial state of onChainNFTs to null and doing a null check.
e.g.
const [onChainNFTs, setOnChainNFTs] = useState(null);
useEffect(() => {
if (onChainNFTs !== null) {
console.log('do something as initial state of onChainNFTs changed'); // triggered 1 time!
}
}, [onChainNFTs]);
Here is the custom hook for your purpose. It might help in your case.
import {
useRef,
EffectCallback,
DependencyList,
useEffect
} from 'react';
/**
*
* #param effect
* #param dependencies
* #description Hook to prevent running the useEffect on the first render
*
*/
export default function useNoInitialEffect(
effect: EffectCallback,
dependancies?: DependencyList
) {
//Preserving the true by default as initial render cycle
const initialRender = useRef(true);
useEffect(() => {
let effectReturns: void | (() => void) = () => {};
/**
* Updating the ref to false on the first render, causing
* subsequent render to execute the effect
*
*/
if (initialRender.current) {
initialRender.current = false;
} else {
effectReturns = effect();
}
/**
* Preserving and allowing the Destructor returned by the effect
* to execute on component unmount and perform cleanup if
* required.
*
*/
if (effectReturns && typeof effectReturns === 'function') {
return effectReturns;
}
return undefined;
}, dependancies);
}
There is nothing to worry about. When you are running React in development mode. It will sometimes run twice. Test it in prod environment and your useEffect will only run once. Stop Worrying!!
It is strict mode in my case. Remove strict mode component at index.tsx or index.jsx
If someone comes here using NextJS 13, in order to remove the Strict mode you need to add the following on the next.config.js file:
const nextConfig = {
reactStrictMode: false
}
module.exports = nextConfig
When I created the project it used "Strict mode" by default that's why I must set it explicitly.
Ok this is maybe a bit late to comment on this - but I found a rather useful solution which is 100% react.
In my case I have a token which I'm using to make a POST request which logs out my current user.
I'm using a reducer with state like this:
export const INITIAL_STATE = {
token: null
}
export const logoutReducer = (state, action) => {
switch (action.type) {
case ACTION_SET_TOKEN :
state = {
...state,
[action.name] : action.value
};
return state;
default:
throw new Error(`Invalid action: ${action}`);
}
}
export const ACTION_SET_TOKEN = 0x1;
Then in my component I'm checking the state like this:
import {useEffect, useReducer} from 'react';
import {INITIAL_STATE, ACTION_SET_TOKEN, logoutReducer} from "../reducers/logoutReducer";
const Logout = () => {
const router = useRouter();
const [state, dispatch] = useReducer(logoutReducer, INITIAL_STATE);
useEffect(() => {
if (!state.token) {
let token = 'x' // .... get your token here, i'm using some event to get my token
dispatch(
{
type : ACTION_SET_TOKEN,
name : 'token',
value : token
}
);
} else {
// make your POST request here
}
}
The design is actually nice - you have the opportunity to discard your token from storage after the POST request, make sure the POST succeeds before anything. For async stuff you can use the form :
POST().then(async() => {}).catch(async() => {}).finally(async() => {})
all running inside useEffect - works 100% and within I think what the REACT developers had in mind - this pointed out that I actually had more cleanup to do (like removing my tokens from storage etc) before everything was working but now I can navigate to and from my logout page without anything weird happening.
My two cents...
I used CodeSandbox and removing prevented the issue.
CodeSandbox_sample

React - Running side effects inside pure functions

Introduction
My current use case requires to store the most fresh state updates in a cache. As state updates are async, and there can be a lot of components updating the same one in parallel, it might be a good option to store them inside the body of the useState or useReducer pure functions.
But... side effects come, and the frustration start. I have tried to await dispatches, creating custom hooks "useReducerWithCallback", and other stuff, but I don't see the correct solution to my problem.
Problem
I have a module usersCache.js which provides me with the necessary methods to make modifications to my cache:
const cache = {};
export const insert = (id, data) => ...
export const get = (id) => ...
// and more stuff
I am trying to update this cache when I make state updates. For example:
const currentUser = useContext(CurrentUserContext);
...
// Note: setData is just the state setter useState hook
currentUser.setData((prevData) => {
const newTotalFollowing = prevData.totalFollowing + 1;
usersCache.update(currentUser.data.id, { newTotalFollowing }); <---- SIDE EFFECT
return { ...prevData, totalFollowing: newTotalFollowing };
});
And same stuff in my otherUsers reducer
import { usersCache } from "../../services/firebase/api/users"
export default (otherUsers, action) => {
switch (action.type) {
case "follow-user": {
const { userId, isFollowing } = action;
const prevUserData = otherUsers.get(userId);
const newTotalFollowers = prevUserData.totalFollowers + (isFollowing ? 1 : -1);
usersCache.update(userId, { totalFollowers: newTotalFollowers }); // merge update
return new Map([
...otherUsers,
[
userId,
{
...prevUserData,
totalFollowers: newTotalFollowers
]
]
);
}
...
}
}
As in pure functions we shouldn't perform side effects... Is there any other approach to handle this?
Note: I am not using Redux
You can check this full working example using the repository pattern and react hooks to simplify async actions with state dispatches. I know you are not using redux but you can adapt this example using the useReducer hook to connect it to your React Context store.

What's the point of Actions and Reducers in Redux?

I have been using Redux for a while, but I still can't see the point of actions and reducers.
As described in the docs, a reducer can be summarised as (previousState, action) => newState. The same principle applies for React's useReducer.
So this reducer function basically handles all actions, which seems like a violation of the Single Responsibility Principle. I'm sure that there's a good reason to do it this way, but I don't see it.
It would make more sense to me to just have a function per action. So instead of having an ADD_TODO action you would have a addTodo(previousState, todoText) => newState function. This would reduce (no pun intended) a lot of boilerplate code and might even give a slight performance improvement as you no longer need to switch through the action types.
So my question is: What's the advantage of having a reducer as opposed to a single-action function?
So if your question is why do we use reducers at all?
The real boilerplate of Flux is conceptual: the need to emit an
update, the need to register the Store with a Dispatcher, the need for
the Store to be an object (and the complications that arise when you
want a universal app).
That is a fundamental design choice redux has made as it is inspired from flux.
If you do not like the switch cases, and there by the reducer size. You can have something like this below:
export const todos = createReducer([], {
[ActionTypes.ADD_TODO]: (state, action) => {
const text = action.text.trim()
return [...state, text]
}
})
Above is a function that lets us express reducers as an object mapping from action types to handlers.
createReducer can be defined as :
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
You can read more on this here
Actually it is a silly design and makes no sense.
It is just a kind of over-engineering. Because in most case, you could have just used a simpler function to update a state instead of doing it a silly way by making it two functions (reducer and action).
By adopting redux in your project, you may write tons of reducer and action codes which looks extremely ugly and difficult to do code reviews
Try to imagine, if useState function is design by redux way!
const [todos, setTodos] = useState([])
this one line code now would looks like this(tons of ugly codes):
export interface TODO {
name: string
}
export interface ITodosAction {
type:
| "SET_TODOS";
payload: {
todos?: TODO[];
};
}
export default function todosReducer(
state: TODO[] = [],
action: ITodosAction
): TODO[] {
export function setTodos(todos: TODO[]): ITodosAction {
return {
type: "SET_TODOS",
payload: { todos }
};
}
const todos = useSelector(state=>state.todos)
dispatch(setTodos(["A", "B"]));
if useState became like this, we would have to suffer using react (thanks to react's team for not adopting an over-engineering and silly design like redux).
Remember: never try to figure out why this design is good when it has already brought you trouble. We use redux not because redux is good (instead it is a worst design i've ever seen) but because we don't have many choices
I think you're not appreciating how flexible reducers are, you can create as many of them as you like:
const initialState = {
key: 'someValue',
key2: 'someValue',
key3: {
key31: 'someValue',
key32: 'someValue',
key33: []
}
}
// all the below reducers can live in separate files and only handle a single action, if they want, no switches involved at all
const key = (state = initialState.key, action) => { /* the logic to be performed at 'key' */ }
const key2 = (state = initialState.key, action) => { /* the logic to be performed at 'key2' */ }
const key31 = (state = initialState.key3.key31, action) => { /* the logic to be performed at 'key3.key31' */ }
const key32 = (state = initialState.key3.key32, action) => { /* the logic to be performed at 'key3.key32' */ }
const key33 = (state = initialState.key3.key33, action) => { /* the logic to be performed at 'key3.key33' */ }
const key3 = combineReducers({key31,key32,key33})
// this is the top level reducer for your store
const myTopLevelReducer = combineReducers({key,key2,key3});

Alternative to Redux reducer

I'm building a library that other apps will use. My library is an extension to redux.
To keep this question as general as possible, I currently have middlewares, action creators and one reducer.
The reducer is the problem because my reducer depends on the state structure which I, as a library developer, can't and shouldn't know. The user may use a combineReducers function or other function and give my reducer's state any name he wants.
My question is - What options Redux.js library provide to library developers in a case like this; hiding reducers/other alternatives to reducers?
Wrap your library in a configuration function, which requires the user to supply a selector, that points to place in the state that your reducer occupies.
In addition, if users access your state (not the case for you right now), you can supply selectors to use the state, without knowing it's structure.
A general non-working example:
const createSelectors = (mainSelector) => ({
selectorA: (state) => mainSelector(state).dataA,
selectorB: (state) => mainSelector(state).dataB,
});
const createMiddlewares = (actionTypes, selectors) => {
const middlewareA = ({ getState, dispatch }) =>
next => action => {
const myStateA = selectors.selectorA(getState());
};
return {
middlewareA
};
};
const factory = (mainSelector = ({ myState }) => myState) => {
const actionTypes = {};
const actions = {};
const reducer = () => {};
const selectors = createSelectors(mainSelector);
const middleware = createMiddlewares(actionTypes, selectors);
return {
actionTypes,
actions,
reducer,
middleware,
selectors
};
};
This package minimize needs for writing reducers, because it is using auto merge feature with actions. There is some solution with arrays too.
https://www.npmjs.com/package/micro-reducers
I think instead of struggling with the complex Redux library. you should give it a try with https://rootzjs.org/ an alternative to redux. Its literally like a cakewalk.

Why aren't these two versions of a higher order function giving the same results?

Below I am modifying the dispatch function of my Redux store so that it console.logs the state of my application (I am aware that this is not the smart way to do this but my question is related to JS syntax not Redux).
There are two versions of the higher order function addLoggingToDispatch(dispatch), and the only difference is that the second version is using arrow functions on all function definitions.
Question: Why aren't these two implementations of addLoggingToDispatch exactly the same? From my understanding of JS and arrow functions they are the same, but I'm not getting the same result in my browser, so I would like to understand why.
Expected behavior: Specifically, what the code below does is that when the "DOMContentLoaded" event happens, I replace the original Redux store.dispatch function with my own addLoggingToDispatch, which just calls the original dispatch(action) function in between logging the state of my application to the console.
Actions are dispatched in my application on keydown or keyup events. So if the user simply presses a key and releases the key, I should see four messages in the console (the state of the application before and after each of the events).
With the first version of addLoggingToDispatch the logging works as expected. With the second version, when the user presses and releases a key, the logging starts to occur without end, eventually using up all my memory.
import React from 'react';
import ReactDOM from 'react-dom';
import { applyMiddleware } from 'redux';
import configureStore from './store/store';
import Root from './components/root';
//VERSION 1: working version
const addLoggingToDispatch = (store) => {
const dispatch = store.dispatch;
return (action) => {
console.log("Old State: ", store.getState());
console.log("Action: ", action);
dispatch(action)
console.log("New State", store.getState());
}
}
// VERSION 2: does not work
const addLoggingToDispatch = store => action => {
const dispatch = store.dispatch;
console.log("Old State: ", store.getState());
console.log("Action: ", action);
dispatch(action)
console.log("New State", store.getState());
}
document.addEventListener('DOMContentLoaded', () => {
const store = configureStore();
store.dispatch = addLoggingToDispatch(store);
debugger;
const rootEl = document.getElementById('root');
ReactDOM.render(<Root store={store} />, rootEl);
});
The difference has nothing to do with arrow functions, but only with the creation of the dispatch variable. As #nnnnnn noted in the comments, by not storing the property before overwriting it you are creating an infinite recursion.
However, even the working version looks like a really bad approach. Overwriting store.dispatch and storing the original value should happen in the same place, or even be factored out into one function, instead of being scattered over your code base. This helps to understand what happens here and prevents mistakes such as yours.
Better wrap dispatch in a single line:
const addLogging = (state, fn) => function(x) {
console.log("Before: ", state());
console.log("Argument: ", x);
fn.call(this, x);
console.log("After: ", state());
};
document.addEventListener('DOMContentLoaded', () => {
const store = configureStore();
store.dispatch = addLogging(() => store.getState(), store.dispatch);
const rootEl = document.getElementById('root');
ReactDOM.render(<Root store={store} />, rootEl);
});

Categories