I am writing a single-paged application. Since this application updates pages on rootApp, some actions of rootApp are planned to be used by other pages, e.g. redirectPageTo. I directly use import and export to try to directly dispatch the action of RootApp, but the compiling error happens. The error is something undefined, but actually it's well defined in the same file. Is there any correct way to do this? Any ideas are welcome.
//this page is the page other than RootApp; here is the LoginPage reducer
import {redirectPageTo} from './RootAppAction.jsx';//import actions, with this line, errors like defaultState undefined happens
const defaultState={(some states declaration)};
export function updateLoginPageState(state={},action) {
...
dispatch(redirectPageTo("HOMEPAGE"));//use other module's action. Can I use it in this way?
}
I find out the root cause may relate to that I put the reducer and the action codes in the same file.
Related
I'm having a very weird issue with redux. In my current app, I decided to use a logout pattern suggested by Dan Abramov.
Basically, it looks like this:
const withLogout = (state, action) => {
if (action.type === UserActions.logout.toString()) {
state = undefined
}
return rootReducer(state, action)
}
The state is getting purged after the logout action is dispatched. I added logs to reducers and they all show that the state is getting reset to a default value. React Native Debugger shows the same thing.
However, the state inside the connect(react-redux) functions doesn't change. Even weirder, it looks like the function detects the fact that the state has changed because the logger function in mapState is being triggered. But the logger's output shows that the state hasn't changed there. Components are also not getting updated.
And as I mentioned in the title this issue happens only in dev mode. As soon as I build the app and install it everything starts working as intended. And it isn't caused by Debugger.
Any thoughts?
I found the reason. I had to import the store in a couple of places where I couldn't use react-redux to dispatch the action. This led to require cycles which sometimes led to creating multiple instances of the store.
I stumbled upon the following solution that makes sure you're using the same instance of the store across the app: instead of importing store in several places use the following pattern:
// somewhere in the code
let store
export const setStore = storeInstance => store = storeInstance
...
store.dispatch()
// store index.js
import { setStore } from 'someplace'
...
setStore(store)
I'm storing form data in redux store after normalizing it using normalizr. When I submit the form, I get the denormalized data using selectors inside my thunk and then send it to the server. The flow goes the following way:
rootReducer -> localReducer -> action/actionCreator -> rootReducer
In rootReducer file, the root reducer composes localReducer and contains the globalized selector to be used later in the thunk. The localReducer file imports actions from the actions file which contains the action creators too. The thunk action creator returns a thunk which does the api call using data retrieved by the selector in rootReducer file, hence the circular dependency.
Webpack is not handling well this circular dependency. I got a runtime Uncaught TypeError: Cannot read property 'JOB_FORM_RESET' of undefined error at the localReducer -> action/actionCreator level:
const jobsForm = (state = initialState, action) => {
switch (action.type) {
case ActionTypes.JOB_FORM_RESET:
Any thoughts on how to solve this?
Edit
ActionTypes being evaluated to undefined works as specced. ActionTypes is located in the action/actionCreator file whose execution didn't finish when it was first imported by localReducer because it started immediately importing the rootReducer. In order to avoid the infinite loop, an unfinished copy of action/actionCreator (where ActionTypes is evaluated to undefined) is given to localReducer.
The solution is to separate the actions and action creators by putting them in two different files. This will remove the cyclic dependency as shown in the following flow:
rootReducer -> localReducer -> action
actionCreator -> rootReducer
The weird thing for me is that grouping action and action creators has been advocated in redux guidelines since too long and separating them in two files feels less natural.
In addition, this cyclic issue doesn't appear in redux-saga model:
rootReducer -> localReducer -> action/actionCreator
saga -> api -> rootReducer
I'm used to this model but can't believe that redux-thunk model doesn't solve this issue. In other words, it doesn't seem fair to say that the cyclic issue is an inherent side effect of the redux-thunk model. Am I missing something here?
You can find an MCVE in this repo. The error is different but it's the same principle, it was caused by a cyclic dependencies caused by the following import in src/Users/actions.js file:
import { getSelectedUsers } from '../reducer';
The occurred error is No reducer provided for key "users". Just comment the above import and the error will disappear.
As I describe above, this works as specced, my concern is that the redux-thunk model doesn't handle this use case. In addition, putting actions and action creators both in the same file and then wait for a cyclic dependencies issue to occur to separate them doesn't seem a scalable solution.
The solution is quite simple: Extract actionTypes to separate file and import it in actions.js and reducer.js
actionTypes.js file:
export const SELECT_USER = 'SELECT_USER';
export const POST_USERS = 'POST_USERS';
You can import all actions at once like this
import * as actionTypes from './actionTypes.js'
Problem solved here:
https://github.com/svitekpavel/redux-thunk-globalized-selectors-cyclic-dependencies/commit/1c7f04fc5c1d4e4155891428138f8cb00412655e
Two more recommendations:
Extract selectors to separate file
Extract "effects" (postUsers) to effects.js
The second recommendation comes from experience, that these functions (side effects) that tutorials of redux-thunks keep in actions.js are actually side effects and not action creators.
If you were to use Redux-Saga, you would quickly realize that decoupling business logic (and side effects) from action creators is a good thing.
Also, they are two separate things :-)
I have simple Redux reducer, but for some actions I need to show notifications, in order to do so I need to trigger my custom notification function within the reducer so:
case REDUCER_ACTION_NAME:
notificationDisplay.success("Message", {
additionalStuff: extraOptions
});
What's even worse I'm using react-intl for translations and I need the "Message"o be translate-ready so I'm adding this to the mix:
case REDUCER_ACTION_NAME:
notificationDisplay.success(<FormattedMessage id="message" defaultMessage="Message" />, {
additionalStuff: extraOptions
});
It creates a translation in span and requires react so my reducer starts with these imports:
import React from 'react';
import notificationDisplay from 'my-notifications';
import { FormattedMessage } from 'react-intl';
import {
// all the actions
} from './actions.jsx';
// reducer
Is it okay? I feel something here is off - like importing React in reducers is an anti-pattern, because all reducer examples I could find are so clean and sleek and there are no external libs there whatsoever.
Am I right or am I wrong and my code is perfectly fine?
You should not do any kind of computations in reducer. It should change the state and nothing else. The way you are using it is a complete anti-pattern. Because it is doing some UI actions. And Redux is nothing to do with the UI. It should be used as the store and only the store.
But you can use Actions which is way better than doing it in reducer.
Best way to achieve your goal is to use your reducer to just push the messages into an array in the redux store. And create a Container that uses that messages array to show success or error messages. And create a timer that removes the message from the array after some time.
Just look at the https://github.com/diegoddox/react-redux-toastr repo they are doing it very well.
Thanks
Akhil P
TLDR: I want to be able to grab the latest Redux State in an external "Util" file. How can I do this?
Say I have a playlist.. and in many different areas of the app, you can "Start" the playlist. So in a "Util" file I have the "startPlaylist" function so I dont have to write the function in numerous places but in only one place.
The problem with this, is that if I make any changes to the playlist while the playlist is running, "playNextPageInPlaylist" function will not receive any updates to the playlist.
What can I do and change so that my function(s) in the Util file will receive the most updated Redux State?
I have startPlaylist function in 7 different areas, and the functions it involves (all in the Util file) are quite complex.. and it wouldn't make sense to copy and paste that in all 7 files.
Thanks for any help
React.Component File 1
import { startPlaylist } from '../util/EntitiesUtil';
start1() {
startPlaylist( store.playlists[0] );
}
React.Component File 2
import { startPlaylist } from '../util/EntitiesUtil';
start2() {
startPlaylist( store.playlists[0] );
}
EntitiesUtil.js
export function startPlaylist( playlistFromStore ) {
// do stuff
playNextPageInPlaylist( playlistFromStore ); // keeps grabbing next page on a timer
}
You got couple of options, the main options as i see it are:
pass the store to the function (bah please don't do that!).
You can write your own middleware that handles certain action types
and can dispatch other actions if needed (you also get a free
access to the ENTIRE store!).
I think the 2nd option is ideal, as you want your util to do stuff that reflect in the store or need stuff from the store. So basically your util wants to be a part of the redux flow!
Well it's not a component so you can't "connect" it but it can (and should be in my opinion) ad middleware that sits between your actions and reducers.
You can read about middlewares here.
I would have provided you an example of your use case but you didn't post any meaningful code.
Edit
A followup to your comment:
Its quite basic.
You have a signature of a function that never changes, just look at
the docs (it uses
currying,
this is another js topic you should learn)
You need to inject it to the store when you create it with
applymiddleware (same as you did with redux-thunk which is a
middleware by itself).
I realy recommend to look at the source code of redux-thunk the whole 11 lines of it.
You can learn a lot from it.
I believe the store has a getState() method available to you.
Import your created store and then call store.getState()
Check out this example from redux's main site:
http://redux.js.org/docs/api/Store.html#example
function select(state) {
return state.some.deep.property
}
let currentValue
function handleChange() {
let previousValue = currentValue
currentValue = select(store.getState())
if (previousValue !== currentValue) {
console.log(
'Some deep nested property changed from',
previousValue,
'to',
currentValue
)
}
}
We faced a similar issue in using corporate ui react library where state creation was delegated to core library. Thus, exporting store as public variable was not an option to us.
However, there is a horrible way of 'public static variable' that will be updated with your root reducer or 'slicing reducers'.
so, you should make some 'store-util.tsx' with 'let utilStore' variable and export some setter (for reducer) and getter (for any utility functions) functions.
Disclaimer: It is possible, that this question seems stupid because I might have overlooked anything obvious or self-evident in the docs. For now my reducer-setup looks like this:
import editBlogEntryForm from './blog/editBlogEntryForm';
import login from './login'; // <-- a directory with index.js
import blog from './blog'; // <-- a directory with index.js
export default combineReducers({
blog: blog,
login: login,
form: editBlogEntryForm
});
Where shall I put other forms in the future?
Learning more about seperating reducers, I moved the blog and login reducers into their own directories/files where I use reducer composition. But it seems I cannot move the form anywhere, it has to stay top-level, which does not make sense, if I later want to introduce a login-form or so. I hope I don't have to put everything into the same form-reducer, resulting in a quite big switch-statement?
I already tried moving the form: editBlogEntryForm "down" into the blog reducer, but the form stops working/updating then.
Here is the reduxForm call in my container-component for the form:
EditBlogEntryFormContainer = reduxForm({
form: 'EditBlogEntryForm',
validate
})(EditBlogEntryFormContainer);
Can someone please point me the right direction please?
From the redux-form-docs http://redux-form.com/6.6.3/docs/GettingStarted.md/ I got this:
Note that, by default, the key used to pass the redux-form reducer to
combineReducers should be named form. Although there is support for
custom key names, see getFormState config for more details.
Thanks to #Thijs Steel and his pointing me to the getFormState prop of reduxForm() (see docs http://redux-form.com/6.6.3/docs/api/ReduxForm.md/), I came to a solution that is working with verbose form name and state-location. Though I still cannot follow the docs saying about getFormState:
This functionality is rarely needed, and defaults to assuming that the
reducer is mounted under the form key.
I think we all want more than one form and form-reducer in our apps, so using getFormState rather seems the standard case to me. But still, I'm not sure, I might have missed anything obvious.
Solution is to change the connection of the form-component with the redux-form like this:
EditBlogEntryFormContainer = reduxForm({
form: 'EditBlogEntryForm',
getFormState: (state) => state.blog.editBlogEntryForm, // <-- your form reducer location
validate
})(EditBlogEntryFormContainer);
So any form can have its state from any location of the app's state making multiple forms possible.
Not using getFormState is defaulting to using the form-reducer from top-level of the state, which would result into this:
EditBlogEntryFormContainer = reduxForm({
form: 'EditBlogEntryForm',
getFormState: (state) => state.form, // <-- default of reduxForm
validate
})(EditBlogEntryFormContainer);
First of all, if you want to add another form in the Future you could call it form2: ... (Or some other, more apropriate name).
When you moved the form down, did you also use combinereducers to combine the lower level ones?. If so, bear in mind that to acces the state you would need to do state.blog.form and state.blog.main