Redux: Shared actions - redux

I have a few actions in my app that need to be shared by a few different react components. For example, I have a fetchAPIData action creator which accepts some params and fires off actions to to make a fetch (using a custom fetch middleware i've written):
export function fetchAPIData(params) {
const actions =
[
types.API_CALL,
types.RECEIVE_API_DATA,
types.API_ERROR
];
const params = {
method: 'params.method',
params
};
return fetch(actions, params);
};
This action and others like this needs to be called by various different parts of the app so i've created a common-actions directory where these actions live. This feels like the wrong approach so I am wondering if there is a more accepted way of doing this?

I would recommend you to use connect function react-redux
You can then import your actions to the top level component, and bind them with the state you want. then pass the props to any children you want. quick example:
import React, { Component } from 'react';
// Import actions below
import * as myActions from '/redux/actions/myActions';
// Make sure redux, react-redux are installed
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
class App extends Component {
render() {
const { redux, actions } = this.props; // the redux state.
const { actionName } = actions;
<div>
<button onClick={() => actionName()}> {/* Dispatching redux action... */}
Dispatch action
</button>
<ChildComponent {...this.props} />, {/* Now you can pass the props to any children */}
</div>;
}
}
// take any state from redux you want and return it within redux object
const mapStateToProps = state => ({
redux: {
myState: state.myState,
},
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators({
...myActions,
// import actions below...
}, dispatch),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App); // The component name you want to bind the state and the actions to.
I left notes so you can understand what's going on. This is ES6.

Related

React-redux dispatch action onclick using hooks and redux toolkit

Fairly new to redux, react-redux, and redux toolkit, but not new to React, though I am shaky on hooks. I am attempting to dispatch an action from the click of a button, which will update the store with the clicked button's value. I have searched for how to do this high and low, but now I am suspecting I am thinking about the problem in React, without understanding typical redux patterns, because what I expect to be possible is just not done in the examples I have found. What should I be doing instead? The onclick does seem to capture the selection, but it is not being passed to the action. My goal is to show a dynamic list of buttons from data collected from an axios get call to a list of routes. Once a button is clicked, there should be a separate call to an api for data specific to that clicked button's route. Here is an example of what I currently have set up:
reducersRoutes.js
import { createSlice } from "#reduxjs/toolkit";
import { routesApiCallBegan } from "./createActionRoutes";
const slice = createSlice({
name: "routes",
initialState: {
selected: ''
},
{... some more reducers...}
routeSelected: (routes, action) => {
routes.selected = action.payload;
}
},
});
export default slice.reducer;
const { routeSelected } = slice.actions;
const url = '';
export const loadroutes = () => (dispatch) => {
return dispatch(
routesApiCallBegan({
url,
{...}
selected: routeSelected.type,
})
);
};
createActionRoutes.js
import { createAction } from "#reduxjs/toolkit";
{...some other actions...}
export const routeSelected = createAction("routeSelection");
components/routes.js:
import { useDispatch, useSelector } from "react-redux";
import { loadroutes } from "../store/reducersRoutes";
import { useEffect } from "react";
import { routeSelected } from "../store/createActionRoutes";
import Generic from "./generic";
const Routes = () => {
const dispatch = useDispatch();
const routes = useSelector((state) => state.list);
const selected = useSelector((state) => state.selected);
useEffect(() => {
dispatch(loadroutes());
}, [dispatch]);
const sendRouteSelection = (selection) => {
dispatch(routeSelected(selection))
}
return (
<div>
<h1>Available Information:</h1>
<ul>
{routes.map((route, index) => (
<button key={route[index]} className="routeNav" onClick={() => sendRouteSelection(route[0])}>{route[1]}</button>
))}
</ul>
{selected !== '' ? <Generic /> : <span>Data should go here...</span>}
</div>
);
};
export default Routes;
Would be happy to provide additional code if required, thanks!
ETA: To clarify the problem - when the button is clicked, the action is not dispatched and the value does not appear to be passed to the action, even. I would like the selection value on the button to become the routeSelected state value, and then make an api call using the routeSelected value. For the purpose of this question, just getting the action dispatched would be plenty help!
After writing that last comment, I may actually see a couple potential issues:
First, you're currently defining two different action types named routeSelected:
One is in the routes slice, generated by the key routeSelected
The other is in createActionRoutes.js, generated by the call to createAction("routeSelection").
You're importing the second one into the component and dispatching it. However, that is a different action type string name than the one from the slice - it's just 'routeSelection', whereas the one in the slice file is 'routes/routeSelected'. Because of that, the reducer logic in the slice file will never run in response to that action.
I don't think you want to have that separate createAction() call at all. Do export const { routeSelected } = slice.actions in the slice file, and dispatch that action in the component.
I'm also a little concerned about the loadroutes thunk that you have there. I see that you might have omitted some code from the middle, so I don't know all of what it's doing, but it doesn't look like it's actually dispatching actions when the fetched data is retrieved.
I'd recommend looking into using RTK's createAsyncThunk API to generate and dispatch actions as part of data fetching - see Redux Essentials, Part 5: Async Logic and Data Fetching for examples of that.

How to fix "dispatch is not a function" error in react native project

In a react-native, redux, firebase project, I have a drawer component that subscribes to an onSnapshot listener when the component mounts, and on will unmount, it calls the snapshot reference. this component looks like this:
import { onAccountChange } from '../actions/Agenda';
import {dispatch} from 'redux';
class DrawerContentComponent extends React.Component {
constructor (props) {
super(props);
}
componentDidMount(){
this.unsubscribeAccount = firebase.firestore().collection('users').doc(this.props.authUser.uid).onSnapshot((doc) => {
dispatch({type: types.LOAD_ACCOUNT, payload: doc.data()})
});
}
componentWillUnmount() {
this.unsubscribeAccount();
}
< ...rest of component... >
EDIT:
const mapStateToProps = ({ account, auth, inbox, agenda }) => {
const { role, profileImg, legalName, username, rating, phoneNumber } = account;
const { conversations } = inbox;
const { authUser } = auth;
const { events } = agenda;
return {
role,
profileImg,
legalName,
username,
rating,
phoneNumber,
authUser,
conversations,
events
};
};
const mapDispatchToProps = { logoutUser, onProfileChange, onAccountChange, getConversations, getAgenda };
export default connect(mapStateToProps, mapDispatchToProps)(DrawerContentComponent);
}
Edit: onAccountChange():
export const onAccountChange = (uid) => {
return (dispatch) => {
firebase.firestore().collection('users').doc(uid).onSnapshot((doc) => {
dispatch({ type: types.LOAD_ACCOUNT, payload: doc.data() });
});
};
};
The above functions as necessary, because I couldn't manage to unsubscribe from the action, which previously was placed in an external directory for actions.
Problem: I want to be able to implement this by somehow using the function thats already created in the actions file ( getAgenda()) without having to rewrite the code in the component, because im currently doing that just to have the ability to unsubscribe from the listener on unmount, only way I thought of to make it work.
ideally, id like to do something like this:
componentDidMount() {
this.unsubscribeAgenda = this.props.getAgenda();
}
componentWillUnmount() {
this.unsubscribeAgenda();
}
But the above results in:
TypeError: 'dispatch is not a function' if I take out the dispatch import, the error is ReferenceError: Cant find variable: dispatch, I obviously need to dispatch changes for a onSnapshot listener
What are some strategies to handle this?
You can't import dispatch directly from redux.
You need to either use react-redux's connect() function to wrap your action creators with dispatch or get dispatch directly from it.
If you are using a functional component, you could use useDispatch to get access to it.
If you don't want to use one of the normal react-redux options, you can export dispatch from your store, and then import it from where you created your store.
export const dispatch = store.dispatch
If most of your logic for the firestore is in an redux thunk action (or similar with asynchronous capabilities), use connect to get the action wrapped in dispatch and run it as you have in your ideal at the end. Whatever you return from a thunk action is returned from the call as well, so you should be able to set it up to return the unsubscribe function.
connect({},{onAccountChange})(DrawerContentComponent)
Then you can dispatch onAccountChange action creator using:
this.props.onAccountChange()
Edit:
Modify your onAccountChange function to this so that your thunk returns your unsubscibe function.
export const onAccountChange = (uid) => {
return (dispatch) => {
return firebase
.firestore()
.collection('users')
.doc(uid)
.onSnapshot((doc) => {
dispatch({ type: types.LOAD_ACCOUNT, payload: doc.data() });
});
};
};
Then you just need to add onAccountChange to the mapDispatch to props and use this in your componentDidMount method:
this.unsubscribeAccount = this.props.onAccountChange();
For making components to be attached to store for both dispatch actions or mapping props, it is used with connect(mapStateToProps, mapDispatchToProps)(Component). in your case, there is no props passed to component so I'll just send null for mapStateToProps
(assuming you used Provider at some parent component REDUX. I cant understand how to connect a component defined as a class extending React.Component in order to read the store)
import { connect } from 'react-redux';
class DrawerContentComponent extends React.Component {
...rest code...
componentDidMount() {
this.unsubscribeAgenda = this.props.getAgenda();
}
componentWillUnmount() {
this.unsubscribeAgenda();
}
}
export default connect(null, { getAgenda })(DrawerContentComponent)

Redux/thunk Error: Actions must be plain objects. Use custom middleware for async actions

i'm new to Redux and have followed this video https://www.youtube.com/watch?v=93p3LxR9xfM to implement Redux into my MERN template, however it keeps crashing due to "Error: Actions must be plain objects. Use custom middleware for async actions."
I believe the problem might be somewhere in the dispatch function but can't seem to find it.
fetch file:
`export function fetchPosts () {
return function(dispatch) {
fetch('http://localhost:5000/products/5d082bb89501e113334e5c8e')
.then(res => res.json())
.then(posts => dispatch({
type: FETCH_POSTS,
payload: posts
})
);
}
} `
the component:
class MyComponent extends React.Component {
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div></div>
);
}
};
export default connect(null, { fetchPosts })(MyComponent);
You fetchPosts action returns a function, whereas it was expected to return a plain object.
Returning a function is required for async operations in redux. But you need to wire up a middleware called redux-thunk.
https://github.com/reduxjs/redux-thunk
So that, this error will go away.
How to wire up redux-thunk:
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
const store = createStore(
reducers, // your reducers
compose(
applyMiddleware(thunk)
)
)

React-Redux unable to fetch data from firebase react native

I have created a database in firebase the schema is below:
Now All I have been trying to do is just have it show up when I do a console log but nothing shows up.
Below is the code for my JobsActions.js
import firebase from 'firebase';
import {
JOBS_FETCH_SUCCESS
} from './types';
export const jobsFetch = () => {
return (dispatch) => {
firebase.database().ref('/jobs')
.on('value', snapshot => {
dispatch({ type: JOBS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
This is my reducer:
import {
JOBS_FETCH_SUCCESS
} from '../actions/types';
const INITIAL_STATE = {
// jobs: 'RCCA'
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case JOBS_FETCH_SUCCESS:
console.log(action);
return state;
//return action.payload;
default:
return state;
}
};
This is the JobsList
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { View, Text } from 'react-native';
import { jobsFetch } from '../actions';
class JobsList extends Component {
componentWillMount() {
this.props.jobsFetch();
}
render() {
return (
<View style={{ paddingTop: 20 }}>
<Text>Hello</Text>
</View>
);
}
}
export default connect(null, { jobsFetch })(JobsList);
I have authentication using firebase and its not a connection to firebase thats an issue, From what I see, it seems like maybe the ref path is wrong in the Actions file?
You main problem here is not with Firebase actually, since I believe everything else is allright, but with React-Redux.
When you are connecting a component to the store, the connect function recieves two functions. The first one (usually called mapStateToProps) recieves the state and returns an object that will be added to the props. In this case, you are not using it, so passing null is a valid decision.
The second one (usually called mapDispatchToProps) receives the dispatch as a parameter and should return an object with the functions that will be inserted to the props that can be used to dispatch new actions. In this case, you are just passing an object as the second parameter of the connect { jobsFetch }.
When you do this.props.jobsFetch(); you are actually returning the function that receives the dispatch, so nothing is actually executed.
Your mapDispatchToProps should be something similar to this
const mapDispatchToProps = dispatch => {
return {
jobsFetch : () => dispatch(jobsFetch())
}
}
export default connect(
null,
mapDispatchToProps
)(JobsList)
Here, I'm assuming that you are in fact using Redux thunk since you are returning a function that receives the dispatch as a parameter in your actions.
As you may see, we first call the jobsFetch() in order to get the function that receives the reducer, and then we dispatch it.
Let me know if this does not work! There may be something else that is not correct, but this is something that should be addressed. Hope it helps!

Use async react-select with redux-saga

I try to implement a async react-select (Select.Async). The problem is, we want to do the fetch in redux-saga. So if a user types something, the fetch-action should be triggered. Saga then fetches the record and saved them to the store. This works so far.
Unfortunately loadOptions has to return a promise or the callback should be called. Since the newly retrieved options get propagated with a changing property, I see no way to use Select.Async together with saga to do the async fetch call. Any suggestions?
<Select.Async
multi={false}
value={this.props.value}
onChange={this.onChange}
loadOptions={(searchTerm) => this.props.options.load(searchTerm)}
/>
I had a hack where i assigned the callback to a class variable and resolve it on componentWillReceiveProps. That way ugly and did not work properly so i look for a better solution.
Thanks
redux-saga is for handling side effects like asynchronously receiving options for react-select. That's why you should leave the async stuff to redux-saga. I have never used react-select but by just looking at the documentation I would solve it this way:
Your component gets very simple. Just get value and options from your redux store. optionsRequested is an action creator for the OPTIONS_REQUESTED action:
const ConnectedSelect = ({ value, options, optionsRequested }) => (
<Select
value={value}
options={options}
onInputChange={optionsRequested}
/>
)
export default connect(store => ({
value: selectors.getValue(store),
options: selectors.getOptions(store),
}), {
optionsRequested: actions.optionsRequested,
})(ConnectedSelect)
A saga definition watches for OPTIONS_REQUESTED action that is trigged by onInputChange, loads the data with given searchTerm from server and dispatches OPTIONS_RECEIVED action to update redux store.
function* watchLoadOptions(searchTerm) {
const options = yield call(api.getOptions, searchTerm)
yield put(optionsReceived(options))
}
In other words: Make your Component as pure as possible and handle all side-effect/async calls in redux-saga
I hope this answer was useful for you.
The main idea is that you are capable to dispatch redux actions using application context from
import React from 'react';
import { connect } from 'react-redux';
import Select from '#components/Control/Form/Skin/Default/Select';
import { reduxGetter, reduxSetter, required as req } from '#helpers/form';
import { companyGetTrucksInit } from "#reduxActions/company";
import AppContext from '#app/AppContext';
const FIELD_NAME = 'truck';
export const getReduxValue = reduxGetter(FIELD_NAME);
export const setReduxValue = reduxSetter(FIELD_NAME);
const SelectCompanyTruck = (props) => {
const {
required,
validate=[]
} = props;
const vRules = [...validate];
if (required)
vRules.push(req);
return (
<AppContext.Consumer>
{({ dispatchAction }) => (
<Select
loadOptions={(inputValue, callback) => {
function handleResponse(response) {
const { data: { items } } = response;
const options = items.map(i => ({ label: i.name, value: i.id }));
callback(options);
}
dispatchAction(companyGetTrucksInit, { resolve: handleResponse, inputValue });
}}
name={FIELD_NAME}
{...props}
/>
)}
</AppContext.Consumer>
);
}
export default SelectCompanyTruck;

Resources