React redux - how to handle events? - reactjs

I am learning the concepts of Redux.
From what I understand, reducers are not supposed to mutate state, they return a new state with mutations applied. They are also not supposed to (or maybe even can't?) dispatch other actions.
Given those limitations, that pretty much leaves you with the action creators, middleware, or your React components (if using Redux with React) to call your business logic. So, what I'm trying to do is use Firebase, the wall I've hit, and the questions I have are the following:
Where should you be creating an instance (i.e. initialising Firebase)?
Similarly, where should be uninitialising Firebase? Usually I would expect these to be in a service of some kind that you'd call, but I'm not really sure where you would make the call to that service.
Where should you listen for changes? Firebase provides .on on references to allow you to react to changes (i.e. child_added, child_removed) - where should these .on handlers go? I was thinking that surely when one of these events comes in, an action needs to be dispatched with the child in question to update the state, but I can't do this in the reducers, I don't want to do this on a component, and the action creator just seems like an odd place for it to (and when I think about it, how would that even work?)!
Solution
Finally I ended up following the #ampersand suggestion. I made a module with all the listeners of Firebase application. I import in the Firebase module a dispatcher.js file, that is composed by dispatcher function like these:
import store from '../store';
import * as actions from './index';
export function addUserMessage(text) {
store.dispatch(actions.addUserMessage(text));
}
export function addResponseMessage(messageObj) {
store.dispatch(actions.addResponseMessage(messageObj));
}
What I was not understanding was how to dispatch action from an external module. Now I have the concepts clear.

I'm not familiar with Firebase, but I think middleware is most likely the answer here.
You can write a simple middleware that has access to your Firebase instance and will dispatch these message to the Redux store.
// firebase-middleware.js
export default function createFireBaseMiddleware (firebaseInstance) {
return store => dispatch => {
fireBaseinstance.on(message => {
dispatch({
type: 'FIREBASE_MESSAGE',
payload: message,
})
})
return action => {
if (action.type === 'TALK_BACK_TO_FIREBASE' {
firebaseInstance.postMessage(action)
}
// Let the action continue down the path of middleware to the store
return dispatch(action)
}
}
}
// createStore.js
export default createStore(
reducer,
applyMiddleware(createFireBaseMiddleware(myfirebaseInstance)
)

To tack onto #chautelly's response, generally services in react/redux are just modules—create your firebase instance inside a standalone module, export the appropriate object from it, and import where needed.
database.js
import firebase from 'firebase';
firebase.initializeApp({
apiKey: 'api key',
authDomain: 'db_name.firebaseio.com',
databaseURL: 'https://db_name.firebaseio.com/'
});
const database = firebase.database();
export default database;

Related

Redux batch doesn't work properly on React Native app

Need to call multiple dispatches. Trying to use batch from react-redux, but it triggers only the first dispatch inside the function. Tried to exchange dispatches, the same, triggers only the first one, checked in dev tools. Thunk is connected
In app:
dispatch(
setData({
user,
userLang,
userToken,
dateMargin,
}), )
Action:
export const setData = ({user, userLang, userToken, dateMargin}) => {
return dispatch => {
batch(() => {
dispatch(setToken(userToken))
dispatch(setLang(userLang))
dispatch(setUser(user))
dispatch(setDateMargin(dateMargin))
})
}
}
Yes, batch is a re-export from react-dom. React-native does not have a similar API, so that is not possible there.
Generally, it is recommended to Model Actions as Events, Not Setters, Allow Many Reducers to Respond to the Same Action and to Avoid Dispatching Many Actions Sequentially (see the links for more explanation), so what you are doing here is pretty much an anti-pattern:
You have the logic in your component, although the big strentgh of Redux is that it moves the state logic out of your component, into your Store.
Going by the recommendations from the official Redux Style guide, you'd rather
dispatch(userLoggedIn({
userToken,
userLang,
user,
dateMargin
})
and have potentially multiple reducers acting on that event-type action.

React toolkit and redux-first-router

I am digging into React with Redux for a rewrite of our product.
A lot of fog around Redux was cleared by using Redux-Toolkit https://redux-toolkit.js.org/.
Then I found that React-Router made state management messy and found a solution in redux-first-router https://github.com/faceyspacey/redux-first-router.
Now I want to combine these excellent libraries. But I think I'm doing something wrong in the configuration.
Here is the code. Starting with a sandbox example at https://codesandbox.io/s/m76zjj924j, I changed the configureStore.js file into (for simplicity I have omitted code for the user reducer)
import { connectRoutes } from 'redux-first-router';
import { configureStore, getDefaultMiddleware } from '#reduxjs/toolkit'
import { routePaths } from '../routes';
const { reducer: location } = connectRoutes(routePaths);
const {
middleware: routerMiddleware,
enhancer: routerEnhancer,
initialDispatch
} = connectRoutes(routePaths, { initialDispatch: false });
export default function configureRouteStore() {
const store = configureStore({
reducer: {
location: location
},
middleware: [...getDefaultMiddleware(), routerMiddleware],
enhancers: (defaultEnhancers) => [routerEnhancer, ...defaultEnhancers]
})
initialDispatch();
return store;
}
But now each time a change in route = Redux store is updated, I get an exception in the browser:
index.js:1 A non-serializable value was detected in the state, in the path: `location.routesMap.PROFILE.thunk`. Value: dispatch => {
dispatch(USER_DATA_LOADED({
avatar: null
}));
const avatar = `https://api.adorable.io/avatars/${Math.random()}`;
setTimeout(() => {
// fake async call
dispatch(USER_…
Take a look at the reducer(s) handling this action type: HOME.
I can see that this stems from the routes definitions if the route has a 'thunk' property defined as this: PROFILE: { path: "/profile/:username", thunk: fetchUserData },
If I change the thunk property to a serializable value (or remove it) the error is gone.
Somehow now the thunk is added to the payload of the action to update paths. What...?
What to do? OK, I can get it work with the traditional Redux setup but as I am a big fan the redux toolkit it would be sweet for me and maybe a few more people out there to make it work with the toolbox.
I'm a Redux maintainer and creator of Redux Toolkit.
Based on that error message and reading the Redux-First-Router source code, it looks like the library is indeed attempting to store thunk functions in the Redux store. This is a problem, because we specifically instruct users to never put non-serializable values like functions in state or actions.
By default, Redux Toolkit adds a "serializable state invariant middleware" that warns you if non-serializable values are detected in state or actions, to help you avoid accidentally making this mistake.
It is possible to pass some options to getDefaultMiddleware() to customize the behavior of these middlewares. There is currently an ignoredActions option, but I don't think we have an option to ignore specific sections of the state tree. The included redux-immutable-state-invariant middleware does have an ignore option for portions of the state, so perhaps we could add that approach.
I've added https://github.com/reduxjs/redux-toolkit/issues/319 to see if we can add an option like that.
In the meantime, you could potentially turn off the middleware by calling getDefaultMiddleware({serializableCheck: false}).
update
I've just published Redux Toolkit v1.2.3, which adds an ignoredPaths option to the serializability check middleware to allow ignoring specific keypaths within the state.
Again, please note that this is purely an escape hatch to work around misbehaving libraries, and should not be used as a regular approach.

I have a value in my state i want to pass to another page

I have a api call which takes in a varable from the state in my actions which goes to an axios get and it passes it there. I know this because if I console the variable in the axios get its there which goes to a route and to the controller but when I console loge the request in the controller its empty. I am trying to do a find() to a specific email. If I hard code it then it works perfectly which means my variable probably isn't passing there and I don't know why. I have a post that works perfectly
my action
export const getUser = (currentUser) => {
return(dispatch, getState) => {
API.getUserInfo({
emailaddress:currentUser.emailaddress,
password: currentUser.password
})
.then(res => {
dispatch({type:"USER_PROFILE",userPro:res.data})
})
.catch(err => console.log(err));
}
}
reducer
const initState ={
userProfile:[]
}
const userReducer = (state = initState,action)=>{
switch(action.type){
case "CREATE_USER" :
console.log("created User", action.newProfile)
return state;
case "USER_PROFILE":
console.log("User", action.userPro)
return {
userProfile: [...state.userProfile,action.userPro]
}
default:
return state;
}
}
root reducer
const rootReducer = combineReducers({
authR: authReducer,
userR:userReducer
})
mapstatetoprops
const mapStateToProps = (state)=>{
console.log(state)
return{
userInfo:state.userR.userProfile
}
}
export default connect( mapStateToProps ) (Layout);
Right, so you're now moving away from component state and into application state. This might end up being a long answer but in short I would suggest you read-up on the Redux and React-Redux documentation. https://redux.js.org/ https://react-redux.js.org/
Redux is all about persisting data in your application. Which is what you need if you want to take the data from one page and make it available for use in another page. It is essentially comprised of three parts:
Actions: Functions that are called to carry data from your components or APIs into a common place (reducers). The data is recognized as a "payload."
Reducers: Functions that are triggered by your actions. They use the "payload" to return a new app state (data).
Store: Just like the name suggests, it is an object that contains all your reducers; a collection of all your application states.
Now react-redux is simply middleware that let's your components communicate with your store.
There is some pretty standard mark-up to get this all to work. I'll show you examples with what I assume your code looks like.
So first let's define a reducer (a data maintainer for lack of better words) and lets store it in a file called authReducer.js
const authReducer = (state = {}, action) => {
switch(action.type){
CASE "SET_USER_CREDS":
return {
user: action.payload
}
default:
return state
}
}
export default authReducer
So digging into this code. We defined a function with two parameters, a state which we gave an initial value of {} and an action, which refers to the actions that get sent to this reducer. If there was an action with a type of "SET_USER_CREDS" then the reducer returns an object that will contain information on a user. As we can see, the only way it can get data is by consuming it from an action.
Now we need an action, a means to communicate with the reducer we just made. Let's create a file called authActions.js
export const recordUser = (userData) => {
return {
type: "SET_USER_CREDS":
payload: userData
}
}
Looks simple enough, we created a function that essentially is trying to meet the requirements of making our reducer to work. These action creators are actually used by our components, this is method in which we can get data from a component and keep it somewhere.
But wait, where do we keep this data? We talked about reducers, but where do they live? Well it's time to build our store.
store.js
import {createStore, combineReducers} from "redux"
import authReducer from "./authReducer"
const store = createStore(combineReducers({
auth: authReducer
}))
export default store
Alright we got a store now. Quick facts about the syntax. We used a few methods fromt the redux library. We used createStore() and we passed in combineReducers(), where the latter accepts an object. In the object we define a key-value pair for each reducer we want to put in our store. The key is typically the name/type of data the reducer is managing.
Cool, we've set up a store, a reducer and an action-creator. But as is, there is no way for React to communicate with your store. Well this is where react-redux comes in. In whereever you defined your react-router-dom routes, we'll need to make some modifications.
Let's just say this is your router file, you'll need to add the following.
import {Provider} from "react-redux"
import store from "./store"
<Provider store={store}>
//Routes
</Provider>
<Provider> is a component that accepts a redux-store as an argument. We can wrap our <Routes/> inside of it, thus providing the store to all our components.
Congrats, we're about 2 steps away from getting all this to work.
So now in your Header component, or wherever you're entering the user data you need to do a few things.
1) Bring in some dependencies.
import {connect} from "react-redux"
import {recordUser} from "./authActions"
2) Outside of your component. Define a function called mapDispatchToProps()
const mapDispatchToProps = (dispatch) => {
return {
recordUser: (userData) => {
dispatch(recordUser(userData))
}
}
}
In short, this is a function that will let us call your action-creators inside your component. The recordUser key is now an available prop inside your component.
3) Inside your component you need to define an event-handler to use our new prop. It will need to be triggered when the user is navigating to the other page.
handleRecordUser = () => {
const userData = {
email: this.state.email,
password: this.state.password
}
this.props.recordUser(userData)
}
So its doing as we promised, taking data from our component state and passing it off to an action creator. Remember, you need to call this event-handler to execute this action at the same time as the re-route to the new page. If you're using a <Link> to reroute, just do something like:
4) Modify the export of this component to use connect()
export default connect(null, mapDispatchToProps)(Header)
connect() gives our component access to methods like dispatch() which is a requirement to use your action-creators.
Last, but not least, consume your store.
In the component you routed to you need to do the following:
1) Bring in some dependencies.
import {connect} from "react-redux"
2) Outside your component, define a function called mapStateToProps()
const mapStateToProps = (state) => {
return {
auth: state.auth
}
}
mapStateToProps() let's you tap into the store state and enables you to choose which reducer data you want to bring into your component. state.auth should look familiar, since in our store.js file we defined a key-value pair of {auth: authReducer}. We're simply calling that key.
So by defining a key of auth, I'm now saying that I will have a prop in my component called this.props.auth and it's value will be the reducer ouput.
3) Lastly, connect your component
export default connect(mapStateToProps)(YourComponent)
Now you can utilize the data from your previous Login component by consuming the saved data in your redux store by making use of this.props.auth.
you can use redux over here or you can use the localstorage, cookie, sessions any one of these browser storage to set the values and when your component gets rendered you can retrieve this data from browser and make your API call. Though this is not the best approach but if you don't know how to use redux then you can apply this.

How to chain actions in redux

From research, I'm seeing that thunk is the tool you use to be able to chain actions together and/or deal with callbacks, asyc actions, and side-effects.
I'm having trouble understanding the thunk middleware guide. They reference 'store.dispatch' all the time (as do most tutorials on redux) yet I never actually call dispatch and never have access to 'store' so I'm unsure how to implement anything they propose. (I assume this is because I use mapDispatchToProps in react.... which isn't an option in my action creators files)
Below is my action creator (some code removed to clarity):
import { CREATE_NEW_SAMPLING_EVENT } from '../Constants/ActionTypes';
import { emptySamplingEvent } from '../Constants/SamplingEvent';
import _ from 'lodash';
import uuidv4 from 'uuid';
export function createNewSamplingEvent(eventName) {
let newEvent = _.cloneDeep(emptySamplingEvent);
newEvent.eventID = uuidv4();
newEvent.eventName = eventName;
newEvent.dateModified = new Date();
//TODO: call an optional callback
//TODO: dispatch eventLinkTable event
return { type: CREATE_NEW_SAMPLING_EVENT, event: newEvent }
}
What I would like to do is listed in the 'todo's.
I have another action, called EVENT_LINK_TABLE in a different action creator file and different reducer, that would take the uuid from this action creator as an argument. I'd like to dispatch this EVENT_LINK_TABLE action (with the uuid) as soon as I'm done making this a new event.
Further, I'd like to call a standard callback (which will actually be dispatching another action - LOAD_SAMPLNG_EVENT).. but I'm unsure how to call the callback AND return the action. I'm also hearing that doing that from the action creator is bad practice, as well as I don't want it to happen if there is a failure in the creation process.
To add additional info, this is where I'm dispatching the action in my react code:\
handleBrandNewButtonClick = () => {
this.props.createNewSamplingEvent(this.state.newSamplingEventName);
}
This component is 'connect'ed thusly:
const mapStateToProps = function (state) {
return {
samplingEvents: state.SamplingEvents, //to get list of sampling events to check for uniqueness for that user
}
}
const mapDispatchToProps = {
createNewSamplingEvent,
}
export default withRouter(
withStyles(styles, { withTheme: true })
(connect(mapStateToProps, mapDispatchToProps)
(NewEventForm)
)
);
Looks like you don't have proper knowledge in redux. I'll tell you how redux store works. Then you will be able to understand redux.
In redux, we have five things,
Action creator, Action, Dispatcher, Reducers, Store
Imagine you thought to open an insurance company. Ok, here we have our first client. He came to the company and ask for, 'Hey I need to open a new account'.
Now the front desk person will say, 'Ok fill this form and give it to me' Once he gave the form to the front desk, then the person can leave.
In redux, this person is known as Action Creator
The form itself know as the object that returns from action creator
The front-desk person know as Dispatcher
Once the dispatcher got the form, he will make some photocopies of it and send to all departments in your company. policy department, Accounting department, Claims department, etc
These departments are known as reducers
Now each department will check what kind of form is this. Or it's about opening a new account. Ok, Accounting department will get the sum of money and add it to the company vault. Also, the policy department will make a new account for the client. But, the Claims department will not care about this form because it is not about a claim. This is why we set a 'type' property to the object.
Actions creators should only return plain objects. For example, if there is an async call in the action creator it does not return a plain object so we need to have some middleware to avoid this problem. Here we have redux-thunk. This middleware will help us to manually do the dispatch process.
So we get the dispatcher as the parameter to the action creator. Then, once we get the results from the async process, now we can dispatch the results manually inside that action creator. This redux thunk act as a middleware between the dispatcher and the reducers.
You can run business logic inside a function and dispatch the action itself. The action creator, setNewsCreator creates a POJO. That POJO is then dispatched as an action that will get picked up by the reducer
// action creator
const setNewsCreator = ({ news }) => ({ type: 'SET_NEWS', news })
const fetchNews = () => async dispatch => {
const response = await getNews()
dispatch(setNewsCreator({ news: response }))
}
and its usage in a component
componentDidMount() {
this.props.fetchNews() // assuming you've added this function to your mapDispatchToProps
}
Apologies for my first comment. In hindsight, I was not explaining actions correctly.

In React with Redux, when should I save data to back end

In React with Redux, when there are some user operations, e.g., in facebook, user adds some comments, I will call dispatch() to send the add action to redux store, but when should I call back end API to save these data to database? do I need to do it together with dispatch()?
thanks
One solution would be to transfer your API logic into a thunk using a middleware package such redux-thunk (or similar).
Using thunks allows you to treat special kinds of actions as functions which means you can extend a plain action with specific action-related logic. The example you give of needing to serialize your state is an excellent use-case for redux-thunk.
You should note that, unlike reducers, thunks explicitly support fetching state and dispatching subsequent actions via the getState and dispatch functions.
Below is an ES6 example of how such a multi-purpose thunk might look.
To demo the getState() method the new item will only be saved via the api only if the redux state shouldSave value is truthy.
I would also use the async/await syntax to make sure the the api call succeeds before dispatching the local redux action.
Thunk Example - adding a new item
import api from './api'
export const addNew = async (item) => {
return (dispatch, getState) => {
try{
const state = getState()
if(state.shouldSave){
await api.save(item)
}
dispatch({
type: ITEM_ADD_NEW,
data: item
})
}catch(err){
const error = new Error("There was a problem adding the new item")
error.inner=err
throw(error)
}
}
}

Resources