UPDATE: I've narrowed down the issue quite a bit from this first post. please see the latest update. The problem appears to be to do with the size or complexity of the action payload rather than it being because the action is invoked following an async call.
I'm working on a react/redux application and am having a problem using the time travel feature in redux dev tools chrome extension.
When I replay the application in the slider monitor the first async call to a webapi action does not replay. All synchronous actions and async network calls except the first work just fine. Its just the first that doesn't render. I've tried using just redux-thunk for the async, but have also tried it with redux-saga (the current configuration). Im running the application in webpack-dev-server
The application itself is working function (all code is in typescript)
I've tried all kinds of configuration changes, but nothing seems to have any effect. Any ideas would be greatly appreciated.
Heres my configureStore file
function configureStore() {
const sagaMiddleware = createSagaMiddleware()
const store = createStore(rootreducer, compose(
applyMiddleware(invariant(), sagaMiddleware, thunk),
window.devToolsExtension ? window.devToolsExtension() : (f:any) => f
));
if (window.devToolsExtension) window.devToolsExtension.updateStore(store);
sagaMiddleware.run(logsSaga)
return store;
}
export default configureStore;
my saga
function* fetchLogs(logSearchParams: any) {
try {
const data = yield call(getLogTableData,
logSearchParams.params);
yield put({type: "ReceiveLogs",
data, logSearchParams:logSearchParams.params});
} catch (e) {
yield put({type: "LogsError", message: e.message});
}
}
export function* logsSaga() {
yield* takeEvery("RequestLogs", fetchLogs);
}
and the network call
return window.fetch('api/logs/gettable', {
method: 'post',
body: JSON.stringify(logSearchParams),
headers: headers
}).then(r => r.json());
Thanks for any help
EDIT: I'm using Redux-React and the connect decorator to connect Redux with the components. The action is called from an actionCreator
export let searchClicked = () => {
return (dispatch, getState) => {
let params = getSearchParms(getState());
return dispatch({type:'RequestLogs', params});
}
};
This is wired in to the components click handler using React-Redux mapDispatchToProps
Another two components receive the state via mapStateToProps, for example
function mapStateToProps(state) {
return state.logs;
}
When I debug this function isn't invoked when it should be (and is afterwards)
UPDATE:
I've tracked the problem down to a reducer for "ReceiveLogs", which is invoked by Redux-Saga. I have three reducers for this action. If I comment out this line
case "ReceiveLogs":
return {data:action.data.rows, selected:state.selected}
then other components which rely on reducers for this action work correctly and the dev tools replay works as expected. With this line, it fails. The problem appears to be "data:action.data.rows". rows is an array and if I change this to return an empty array, then replay works.
I think I'll give up for today.
UPDATE: It appears that the problem is possibly to do with the size of the array which is sent as part of the ReceiveLogs payload. if I restrict the size of the array by slicing e.g
return {data:action.data.rows.slice(0, 3), selected:state.selected}
then it works. If I include the 4th member of the array, it doesn't work. The 4th member of the array is significantly larger than the others since it has quite a large (and deep) and object included.
Is there some kind of size limit for action payloads and redux-dev-tools??? I'll carry on playing.
Check out Redux Devtools Excessive use of memory and CPU Troubleshooting:
That is happening due to serialization of some huge objects included in the state or action. The solution is to sanitize them.
Related
In React server components official GitHub example repo at exactly in this line here they are using response.readRoot().
I want to create a similar app for testing something with RSC's and it seems like the response does not contain the .readRoot() function any more (because they have updated that API in the react package on npm and I cannot find anything about it!). but it returns the tree in value property like below:
This means that whatever I render in my root server component, will not appear in the browser if I render that variable (JSON.parse(value) || not parsed) inside of my app context provider.
How can I render this?
Basically, if you get some response on the client side (in react server components) you have to render that response in the browser which has the new state from server but since I don't have access to readRoot() any more from response, what would be the alternative for it to use?
I used a trick o solve this issue, but one thing to keep in mind is that they are still unstable APIs that react uses and it's still recommended not to use React server component in the production level, uses it for learning and test it and get yourself familiar with it, so back to solution:
My experience was I had a lot of problems with caching layer they are using in their depo app. I just removed it. My suggestion is to not use it for now until those functions and APIs become stable. So I Removed it in my useServerResponse(...) function, which in here I renamed it to getServerResponse(...) because of the hook I created later in order to convert the promise into actual renderable response, so what I did was:
export async function getServerResponse(location) {
const key = JSON.stringify(location);
// const cache = unstable_getCacheForType(createResponseCache);
// let response = cache.get(key);
// if (response) return response;
let response = await createFromFetch(
fetch("/react?location=" + encodeURIComponent(key))
);
// cache.set(key, response);
return response;
}
and then creating a hook that would get the promise from the above function, and return an actual renderable result for me:
export function _useServerResponse(appState) {
const [tree, setTree] = useState(null);
useEffect(() => {
getServerResponse(appState).then((res) => {
setTree(res);
});
}, [appState]);
return { tree };
}
and finally in my AppContextProvider, I used that hook to get the react server component tree and use that rendered tree as child of my global context provider in client-side like below:
import { _useServerResponse } from ".../location/of/your/hook";
export default function AppContextProvider() {
const [appState, setAppState] = useState({
...someAppStateHere
});
const { tree } = _useServerResponse(appState);
return (
<AppContext.Provider value={{ appState, setAppState }}>
{tree}
</AppContext.Provider>
);
}
I know that this is like a workaround hacky solution, but it worked fine in my case, and seems like until we get stable APIs with proper official documentation about RSCs, it's a working solution for me at least!
I'm using redux-tookit for state.
My action:
const updateSomething = (data: string) => async (dispatch) => {
await user.set({ data })
dispatch(updatedData(data))
}
In my view I want to do something like:
const dispatch = useDispatch()
await dispatch(updateSomething('Hi!'))
Update 5 July 2021
TL;DR
If typeof store.dispatch still doesn't give you the right typing with ThunkDispatch as one of the overloaded options, you may consider manually typing it, like so: -
export type AppDispatch = ThunkDispatch<AppState, null | undefined, AnyAction> &
Dispatch<AnyAction>;
Note: This is the correct typing with the default middleware, if you have added more middlewares you should try to figure out the possibilities.
Background
While my proposed solution (below) works in codesandbox, it doesn't work in my project, which I ported from vanilla redux to redux toolkit. Maybe some of the packages installed break the types, just a speculation but when I include redux-debounced in my codesandbox sample (link below), the type for store.dispatch is falled back to Dispatch<AnyAction>, even without including redux-debounced in middleware.
This is certainly a mystery that has to be resolved!!
I had the similar issue as TS, so I made a simple project in codesandbox and surprisingly it works with a minor tweak!
In my view, what TS meant is that updateSomething('Hi!') is a valid thunk created using createAsyncThunk() in redux toolkit, where dispatching the thunk should return a Promise. That's a feature in redux toolkit. But unfortunately, somehow typescript is returning AsyncThunkAction and invoking the following line:
await dispatch(updateSomething('Hi!'));
actually yields a typing error. Here's what I got in my codesandbox project:
In my case, fetchDynamicName() is a valid thunk and supposedly the type of dispatchReturn should be a Promise.
With a minor tweak found in this post, it actually works!!
All we need is to export the dispatch type of the store, like so:
export type AppDispatch = typeof store.dispatch;
and assign the type to the dispatch function before using it:
const dispatch: AppDispath = useDispatch();
And voilà! See the screenshot below:
You can take a look at my codesandbox project at https://codesandbox.io/s/fast-cdn-08vpu?file=/src/App.tsx.
Edit: SOLVED! Please see below.
I want my Blog component to fire the fetchBlog action creator every time the browser requests its URL, be it via a link or a refresh. I'd like to do it with the React useEffect Hook and with the React-Redux useDispatch and useSelector Hooks. However, my action only fires when following the link to the page; I do not understand why, even after reading several explanations (like the official docs).
Here is the code:
// Everything duly imported, or else VSC would yell at me
export default function Blog() {
const dispatch = useDispatch();
// slug is set here with useSelector, this always works
useEffect(() => {
dispatch(fetchBlog(slug))
}, [slug, dispatch]);
const blog = useSelector((state) => state.blogs[0]);
// return renders the blog information from the blog constant
// since the action does not fire, blog is undefined because state.blogs is an empty array
}
I know that, on refresh, fetchBlog does not fire because of Redux DevTools and also because I put a debugger there. (And the back-end logs don't show the request coming in.) The action creator itself and the reducer must be working; if they weren't, the page would not load correctly when visited through a link.
Edit: I have determined useSelector and useDispatch are not the root cause of the problem, as changing the code to use connect with mapStateToProps and mapDispatchToProps gives the same result. The issue seems to be with useEffect.
I think the problem is you are returning the call to dispatch. Functions returned from useEffect are clean up functions, so I don't think this would run on mount, or update - only before unmount. Try this:
export default function Blog() {
// ...
// Don't return from useEffect. Just call dispatch within the body.
useEffect(() => {
dispatch(fetchBlog(slug);
}, [slug, dispatch]);
// ...
}
https://reactjs.org/docs/hooks-reference.html#cleaning-up-an-effect
I'd like to clarify what the issue was, which #Trace guided me to finding.
useEffect wasn't being called on refresh because it gets called after the component renders/returns. When refreshing, the state - including the blog data - is lost; instead of returning, a TypeError is thrown because data.title doesn't exist. So useEffect never gets the chance of being called and fetch the blog's content.
The solution to that goes like this:
export default function Blog() {
// ...
useEffect(/* ... */)
const blog = useSelector((state) => state.blogs[0]);
if (!blog) {
return <p>Loading...</p>
}
// return actual blog contents here
}
So now fetchBlog does get called, updating blog and rendering the content.
It isn't clear to me where the slug comes from.
In theory useEffect runs after every render. In case of multiple parameters it will run the callback when one of the array parameters passed in the second argument changes.
Either create a useEffect with empty array as second argument to run it 'once' (e.g. when you refresh) or check the slug value.
Edits after checking the repo:
It's not going to work because useEffect is run AFTER the render (which was included in my answer although someone downvoted it). Dispatching the call will only happen after, or not at all if the exception was thrown before (in this case a nullpointer).
You can get the slug from react-router with match, may be handy for you to know.
export default function Blog({ match }) {
const slug = match.params.slug;
etc
The git repo shows how dispatch as is added as array parameter to useEffect, which is not necessary.
I have a login popup which maps a 'isLoggingIn' boolean to the redux store. When a login request action is dispatched a saga intercepts the action and sends another action that the login is processing, the reducer will take that in and set the 'isLoggingIn' boolean to true.
My store:
export interface AppState {
playerToken:string,
loginOpen: boolean,
loginProcessing: boolean
}
The login saga:
function* loginUser(action: any) {
yield put({ type: (LOGIN + PROCESSING) });
try {
const response = yield call(apiCall, 'api/token', 'POST', { username: action.payload.username, password: action.payload.password });
if (response)
{
yield put({ type: (LOGIN + SUCCESS), payload: response.data });
}
catch ({ statusCode }) {
if (statusCode === 401) {
yield put({ type: (LOGIN + FAIL), payload: { error: "Invalid username or password" } })
}
console.log(statusCode);
}
}
Once the saga is done with the login if there's an error it dispatches an action which the reducer sets to a 'loginError' string in the store and sets the isLoggingIn to false, otherwise isLoggingIn is set to false and the user login id is set which prompts the popup to hide itself (i.e. isVisible={this.props.playerToken == undefined).
This seems insanely complicated but I'm not sure how to break this down using Redux principles. I feel strongly the isProcessingLogin should be part of the components state, but the component has no real idea what's going on after it sends the login attempt event and there's no way for it to ever know unless it's listening on for something in the props.
It gets much worse with the various crud operations which need to happen and the various 'isCreatingXModel' booleans which have to be set to true/false in the store and mapped correctly in components.
Is this how redux is supposed to work or am I over using it in places it doesn't belong?
If this is how redux is supposed to be used what are its benefits exactly? I've read online a lot about things which make sense like having a single point of truth, but they can all be done without the crazy redux bloat, I've read people say not to use redux until you need it but that means I'm going to be doing api calls in two conceptually separate areas of code when redux is integrated whenever I 'need it', finally one of the biggest advantages I see purported by advocates is its ability to rewind and move forward in time, which is great but it won't work in any live application which connects to a database in the backend it manipulates unless as part of rewinding there's an undo last api call action.
Keep in mind that these are all entirely my opinions.
1. You might not need sagas (or thunk or other 'async' redux plugin)
Remember that redux is state management only. The API calls can be written in vanilla javascript with or without redux. For example: here's a basic replication of your flow without sagas:
e.g.
import { setLoadingStatus } from './actions'
import { store } from './reducers' // this is what is returned by a createStore call
export function myApiCall(myUrl, fetchOptions) {
store.dispatch(setLoadingStatus('loading'))
return fetch(myUrl, fetchOptions)
.then((response) => {
store.dispatch(setLoadingStatus('succeeded', data))
// do stuff with response data (maybe dispatch a different action to use it?)
})
.catch((error) => {
store.dispatch(setLoadingStatus('failed', error))
// do stuff
})
}
Note the use of store.dispatch. There's an interesting notion in React-Redux that you can only dispatch actions with mapDispatchToProps, but fortunately, that's not true.
I replaced your multiple actions with one that takes a state and optional data. This'll reduce the number of actions and reducers you need to write, but your action history will be harder to read. (Instead of three distinct actions, you'll only have one.)
2. You might not need redux.
The example function above could look basically identical if you weren't using redux -- imagine replacing the store.dispatch calls with this.setState calls. In the future, when you added it, you'd still have to write all the reducer, action, action creator boilerplate, but it would be only slightly more painful than doing it from the start.
As I said above, I usually go with Redux when working with React the built-in state management is has a bad mental map with any sort of large app.
There are two opposing rules of thumb:
use redux for state that needs to be shared between components
use redux for all state and get a single source of truth
I tend to lean to the second one. I hate hunting down errant pieces of state in the leaves of a large React component tree. This is definitely a question with no "correct" answer.
So I'm working on implementing an application in React with Redux Saga and I'm kind of baffled at how little information there is out there for my particular use case, as it doesn't seem that strange. Quite possibly I am using the wrong terms or thinking about the problem in the wrong way, as I am rather new to React/Redux. In any event, I have been stymied by all my attempts to google this issue and would appreciate some insight from someone more experienced in the framework than I am.
My application state has a userSettings property on it which manages a few configuration options for the logged in user. At one point in the application, a user can flip a switch to disable the display of an "at a glance" dashboard widget, and I need to pass this information off to a backend API to update their settings info in the database, and then update the state according to whether this backend update was successful.
My code as it stands currently has a main saga for all user settings updates, which I intend to reach via a more specific saga for this setting in particular, thus:
Dashboard.js
function mapStateToProps(state) {
const { userSettings } = state;
return { userSettings };
}
...
class Dashboard extends Component {
...
...
hasDashboardAtAGlanceHiddenToggle() {
const { dispatch, userSettings } = this.props;
dispatch(setHasDashboardAtAGlanceHidden(!userSettings.hasDashboardAtAGlanceHidden));
}
}
export default connect(mapStateToProps)(Dashboard);
updateUserSettingsSaga.js
import { take, put, call } from 'redux-saga/effects';
import axios from 'axios';
import {
UPDATE_USER_SETTINGS,
SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN,
updateUserSettings,
updatedUserSettingsSuccess
} from '../../actions';
export function* setHasDashboardAtAGlanceHiddenSaga() {
const action = yield take(SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN);
const newValue = action.data;
//QUESTION HERE -- how to get full object to pass to updateUserSettings
yield put(updateUserSettings(stateObjectWithNewValuePopulated));
}
export default function* updateUserSettingsSaga(data) {
yield take(UPDATE_USER_SETTINGS);
try {
const response = yield call(axios.put, 'http://localhost:3001/settings', data);
yield put(updatedUserSettingsSuccess(response.data));
} catch (e) {
yield put(updatedUserSettingsFailure());
}
}
My question, as noted in the code, is that I'm not sure where/how the logic to merge the updated value into the state should occur. As near as I can figure, I have three options:
Build the updated state in the component before dispatching the initial action, ie:
hasDashboardAtAGlanceHiddenToggle() {
const { dispatch, userSettings } = this.props;
const newState = Object.assign({}, userSettings , {
hasDashboardAtAGlanceHidden: !userSettings.hasDashboardAtAGlanceHidden
});
dispatch(setHasDashboardAtAGlanceHidden(userSettings));
}
}
Use redux-saga's select effect and build the full state object in the more specific initial saga, ie:
export function* setHasDashboardAtAGlanceHiddenSaga() {
const action = yield take(SET_HAS_DASHBOARD_AT_A_GLANCE_HIDDEN);
const newValue = action.data;
const existingState = select(state => state.userSettings);
const updatedState = Object.assign({}, existingState, {
hasDashboardAtAGlanceHidden: newValue
});
yield put(updateUserSettings(updatedState));
}
Retrieve the server's copy of the user settings object before updating it, ie:
export default function* updateUserSettingsSaga() {
const action = yield take(UPDATE_USER_SETTINGS);
try {
const current = yield call(axios.get, 'http://localhost:3001/settings');
const newState = Object.assign({}, current.data, action.data);
const response = yield call(axios.put, 'http://localhost:3001/settings', newState);
yield put(updatedUserSettingsSuccess(response.data));
} catch (e) {
yield put(updatedUserSettingsFailure());
}
}
All of these will (I think) work as options, but I'm not at all clear on which would be the idiomatic/accepted/preferable approach within the context of Redux Saga, and there is a bewildering lack of examples (at least that I've been able to find) featuring POST/PUT instead of GET when interfacing with outside APIs. Any help or guidance would be appreciated -- even if it's just that I'm thinking about this in the wrong way. :D
The GET/PUT/POST aspect isn't relevant to the question. Overall, your question really comes down to the frequently asked question "How do I split logic between action creators and reducers?". Quoting that answer:
There's no single clear answer to exactly what pieces of logic should go in a reducer or an action creator. Some developers prefer to have “fat” action creators, with “thin” reducers that simply take the data in an action and blindly merge it into the corresponding state. Others try to emphasize keeping actions as small as possible, and minimize the usage of getState() in an action creator. (For purposes of this question, other async approaches such as sagas and observables fall in the "action creator" category.)
There are some potential benefits from putting more logic into your reducers. It's likely that the action types would be more semantic and more meaningful (such as "USER_UPDATED" instead of "SET_STATE"). In addition, having more logic in reducers means that more functionality will be affected by time travel debugging.
This comment sums up the dichotomy nicely:
Now, the problem is what to put in the action creator and what in the reducer, the choice between fat and thin action objects. If you put all the logic in the action creator, you end up with fat action objects that basically declare the updates to the state. Reducers become pure, dumb, add-this, remove that, update these functions. They will be easy to compose. But not much of your business logic will be there. If you put more logic in the reducer, you end up with nice, thin action objects, most of your data logic in one place, but your reducers are harder to compose since you might need info from other branches. You end up with large reducers or reducers that take additional arguments from higher up in the state.
I also wrote my own thoughts on "thick and thin reducers" a while back.
So, ultimately it's a matter of how you prefer to structure the logic.