React/Redux app actions not being dispatched properly - javascript

I'm trying to set up my first React app using Redux, but I'm currently having some issues with dispatching actions. I have the following files for my actions so far:
constants/AuthActionTypes.js:
export const AUTH_PENDING = 'AUTH_PENDING';
export const AUTH_SUCCESS = 'AUTH_SUCCESS';
export const AUTH_FAIL = 'AUTH_FAIL';
actions/AuthActions.js:
import * as AuthActionTypes from '../constants/AuthActionTypes';
function authPending() {
return { type: AuthActionTypes.AUTH_PENDING };
}
function authSuccess(response) {
return { type: AuthActionTypes.AUTH_SUCCESS };
}
export function login(username, password) {
return dispatch => {
dispatch(authPending());
// nothing really happens yet
dispatch(authSuccess());
};
}
I've also got a login component containing an HTML form, and a login function that gets called when the form is submitted.
components/Login.js
...
login (e) {
e.preventDefault();
var username = ReactDOM.findDOMNode(this.refs.username).value;
var password = ReactDOM.findDOMNode(this.refs.password).value;
this.props.dispatch(AuthActions.login(username, password));
}
...
The function is triggered, but... something isn't right. I use redux-devtools, and it only dispatches a single action - surely, it should dispatch the actions from both authPending and authSuccess? In addition, the devtools pane doesn't display any action type, and I get the following warning in the console:
Warning: Failed propType: Invalid prop action of type function
supplied to LogMonitorEntry, expected object. Check the render
method of LogMonitor.
What am I doing wrong here? What have I missed? I've primarily looked at the examples at https://github.com/rackt/redux/tree/master/examples/real-world and https://github.com/yildizberkay/redux-example, and I can't see what I'm doing differently that makes my own app break.

... and a couple of minutes later, I've stumbled across the answer: I didn't have the redux-thunk middleware applied to my store. Applied that, and everything works as expected.

You can do w/o thunk.
change
this.props.dispatch(AuthActions.login(username, password));
to
this.props.dispatch(AuthActions.authPending());
this.props.dispatch(AuthActions.authSuccess());
Ofcourse, you have to import those two methods/action-creators.

Related

Redirecting in react-router-dom v6 in a non-JSX context

I'm trying to redirect a user from the route they are currently looking at to another route programatically. In my case, I am not in a JSX environment, and cannot use any kind of React hooks. How would I go about this?
I tried to use the code block below to redirect (tried using JSX), only to realize that it wouldn't work as it isn't in the context of the root router.
ReactDOM.render(<div>
<Navigate to="/" />
</div>, document.getElementById("redirect"));
I also want to try and redirect without using window.location.href = as that would cause the whole page to refresh, something I don't want to happen.
EDIT: As requested, I am trying to redirect to a page from an event that is emitted by Tauri and is handled by some TypeScript code on the front end. Using window.location.href isn't an issue in any case.
Here is an example of what I'm trying to do:
/**
* Sets up event listeners.
*/
export async function setupListeners() {
console.log("Setting up link event listeners...");
await listen("deeplink", onLinked);
}
/**
* Invoked when a deep link call is received.
* #param event The event.
*/
async function onLinked(event: Event<string>) {
const { payload } = event;
if (payload == "test:")
// redirect("/testPage");
}
See redirect:
import { redirect } from "react-router-dom";
const loader = async () => {
const user = await getUser();
if (!user) {
return redirect("/login");
}
};
(from the docs)

Using React and Redux Hooks, why is my action not firing?

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.

How can I show a redux-form warning as a result of async-validation?

Redux-forms supports validation errors and warnings.
An error displays a message and prevents the form from being submitted, where as a warning just shows a message.
Redux-forms also support async validation.
I made the mistake of assuming that async validation errors and warnings would be supported, but this isn't the case.
Unfortunately warnings are not officially possible with async validation.
It would currently take considerable effort to move away from using redux-forms, so I'm trying to find a workaround that will suffice.
One solution would be to manually add warnings to a form. If that's possible then the async validation could be performed mostly as normal, but with setting the warnings at the end, rather than providing the expected error object.
But I've looked at the documentation and there doesn't seem to be a way to manually add warnings, or errors for that matter.
You pass validation functions either to a specific field, or the form as a whole, and these validator functions are called as needed by redux-forms.
So it appears that errors and warnings can't be set directly but maybe we can manually trigger re-validation of a specific field?
No, apparently this is also not currently possible.
So to summarize:
Async validation warnings aren't possible.
Manually setting warnings for a form isn't possible.
Manually triggering synchronous validation of a field isn't possible.
Any suggestions, insight, or corrections are very welcome.
I'll be very happy if I'm wrong on any of those summary points!
If I can't find a solution then I'll look for an alternative library, and I'll start the arduous task of moving away from redux-forms.
This has certainly been a good reminder about the folly of assumptions on my part.
I went with Dennie's recommendation and use reducer.plugin() in my root reducer to listen for redux form's async validation completion action ##redux-form/STOP_ASYNC_VALIDATION and (rightly or wrongly) change it from an error to a warning by injecting the action.payload into syncWarnings. Redux-form then passes this as a meta.warning prop to the field.
Reducer code:
import { reducer as formReducer } from 'redux-form';
...
const errorToWarning = (state, action) => {
/* eslint-disable no-unused-vars, no-case-declarations */
switch (action.type) {
case "##redux-form/STOP_ASYNC_VALIDATION":
const { asyncErrors, ...noErrors } = state;
const syncWarnings = action.payload || undefined;
return { ...noErrors, syncWarnings };
default:
return state;
}
};
const rootReducer = combineReducers({
form: formReducer.plugin({
FirstForm: errorToWarning,
AnotherForm: errorToWarning
}),
// other reducers
});
export default rootReducer;
Component:
const MyTextField = ({ meta: { touched, error, warning }, ...props }) => {
let cssClass = "";
let errorText = "";
if (touched) {
cssClass = warning ? "warning" : cssClass;
cssClass = error ? "error" : cssClass;
errorText = warning || errorText;
errorText = error || errorText;
}
return (
<TextField
className={cssClass}
hintText={props.hintText || ""}
{...props}
errorText={errorText}
warning={warning}
/>
);
};
I am using Material UI (that's where TextField comes from, not shown in imports).
See redux-form docs for more info on reducer.plugin().
The async validation callback for a form passes in dispatch as a second argument. That would allow you to trigger an action to add a custom warning to the global Redux state object. If you don't throw any errors then the form will still be considered valid.
For example, if you wanted to warn the user about some given input but wanted to allow them to submit their data anyway, you could call dispatch with some custom action that adds a warning to a global warnings array.
import { addWarning } from '../actions/warning';
import { db } from '../db';
export const asyncValidate = async (values, dispatch) => {
const someNonUniqueValueAlreadyExists = await db.findOne('someProperty', values.someValue);
if (someNonUniqueValueAlreadyExists) {
// just dispatch the action to add a warning to global state
// don't throw any error here
dispatch(addWarning('Is the user sure they want someValue here for someProperty?'));
}
};

Redux Dev Tools not working for large action payload

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.

Trigger Notification/Callback after reflux action execution

I recently used reflux in my project and here is a question puzzled me a lot.
As the reflux pattern, I call actions in my React components, and fetch remote data in my reflux store which are listening to the actions. And my components listen to the changing of data in store. For example get a list of items. So far it's fine.
But sometimes, I want a notification which can told me that the action has executed successfully.
For example, I have a UserStore, a UserActions and a LoginComponent which listen to UserStore. when users have input username and password and click a submit button, the LoginComponent call UserActions.login(), and I send login request in UserStore. When login succeed, UserStore gets user infos from the response.
In this time, I want to give an prompt in LoginComponent such as 'Login Success'. I have two ways to do it but I don't think either is good enough.
Give a flag in the data of UserStore then trigger changing. LoginComponent gets this flag when UserStore trigger a data change event, and then prompt. Because the UserStore would trigger data change not only in login success but also in some other situations like fetching user infos from cookies or sessionStorage, LoginComponent has to add an if-else for this login success flag, if it is login success then prompt.
I don't think it is a good pattern because that the flag is just for the prompt and NOT a real data like user's infos. And if I also want prompt after user change password, I will need another flag field.
Pass a promise(or a callback function) to the UserAction call, and resolve this promise after login succeed, then the LoginComponent can prompt in promise.then. It's seem better than the first one, but isn't it a little anti-pattern because the promise which been passed through actions to stores may broke the Unidirectional in reflux?
What I want to ask is: What's the common/appropriate way to solve this problem?
I'm not from an English area and not good at English expression. This is my first question in stackoverflow.com. I'm not very sure if I have described my question clearly. So if you have some advice to me about the question, please let me know and I will improve it to help others who care about this question. Thanks a lot!
You can include a parameter in the trigger.
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount = () => { this.unsubscribe = BasicStore.listen(this.storeDidChange); }
componentWillUnmount = () => { this.unsubscribe(); }
storeDidChange = (id) => {
switch (id) {
case 'data1': this.setState({Data1: BasicStore.getData1()}); break;
case 'data2': this.setState({Data2: BasicStore.getData2()}); break;
case 'data3': this.setState({Data3: BasicStore.getData3()}); break;
default: this.setState(getState());
}
}
}
import Reflux from 'reflux';
import Actions from '../actions/sa.Actions';
import AddonStore from './Addon.Store';
import MixinStoreObject from './Mixin.Store';
function _GotData(data) { this.data1 = data; BasicStore.trigger('data1'); }
let BasicStoreObject = {
init() { this.listenTo(AddonStore, this.onAddonTrigger); },
data1: {},
listenables: Actions,
mixins: [MixinStoreObject],
onGotData1: _GotData,
onAddonTrigger() { BasicStore.trigger('data2'); },
getData1() { return this.data1; },
getData2() { return AddonStore.data2; },
getData3() { return this.data3; }
}
const BasicStore = Reflux.createStore(BasicStoreObject);
export default BasicStore;

Categories