React-redux dispatch action onclick using hooks and redux toolkit - redux

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.

Related

Is it possible to manually dispatch thunk state in createAsyncthunk

Hey fellow programmers,
Been having fun learning react-redux lately, but I do have one question that bothers me.
My understanding is that, by using createAsyncThunk it will automatically generates action type constants. (pending, fulfilled, and rejected)
What I wanted to know is that is there any way to manually dispatch action type during createAsyncthunk , so that we can have more flexibility in our code.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { userAPI } from './userAPI'
// First, create the thunk
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId).then(
...
dispatch(fulfilled) // is this possible ?
).catch(
dispatch(rejected) // is this possible ?
)
return response.data
}
)
// Then, handle actions in your reducers:
const usersSlice = createSlice({
name: 'users',
...,
extraReducers: {
// Add reducers for additional action types here, and handle loading state as needed
[fetchUserById.fulfilled]: (state, action) => {
// Add user to the state array
state.entities.push(action.payload)
}
}
})
// Later, dispatch the thunk as needed in the app
dispatch(fetchUserById(123))
The point of createAsyncThunk is that it generates those action types, _and dispatches them for you automatically. You definitely do not need to dispatch(fulfilled()) yourself, because that's what createAsyncThunk does for you - you just need to return a promise that either resolves or reject, and it dispatches the fulfilled/rejected action types based on that.
You do get access to thunkAPI.dispatch, so you can dispatch other actions if necessary, but you don't need to worry about the fulfilled/rejected actions yourself.

When to save the state tree?

The Redux manual says every reducer should be a pure function and even no API call should be made, I then curious to know, then, when should I get chance to save my App state tree to an external storage or the backend?
You can save your redux store using and action with the Redux Thunk middleware.
Lets say you want to want to save the store when the user clicks save. First, define an action to do the save:
actions/save.js
import fetch from 'isomorphic-fetch'
export const save = state => {
return () => {
fetch('/api/path/to/save', {
body: JSON.stringify(state),
headers: {
'content-type': 'application/json'
}
method: 'POST'
}
}
}
Then in your component:
components/SaveButton.js
import React from 'react'
import { connect } from 'react-redux';
import { save } from '../actions/save'
const SaveButton = props => {
let { onSave, state } = props
return <button onClick={onSave(state)}>Save</button>
}
const mapStateToProps = state => {
return {state}
}
const mapDispatchToProps = dispatch => {
return {
onSave: state => dispatch(save(state))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SaveButton)
You shouldn't do that as part of your reducer.
Instead, whenever you want to save some part of your state, you should dispatch an asynchronous action (with the help of middleware like redux-thunk) perhaps called SAVE_XYZ with it's payload being the part of the store you want to save.
dispatch(saveXYZ(data))
saveXYZ needs to be an async action creator that will dispatch the API call to persist your data, and handle the response accordingly.
const saveXYZ = payload => dispatch => {
dispatch(saveXYZPending());
return apiCallToStore(...)
.then(data => saveXYZDone())
.catch(err => saveXYZError());
}
You can read more on async actions and how to handle them.
Two basic approaches:
Use store.subscribe(callback), and write a callback that gets the latest state and persists it after some action has been dispatched
Write a middleware that persists the state when some condition is met
There's dozens of existing Redux store persistence libraries available that will do this work for you.

using bindActionCreators, this.props.dispatch in react-redux disptach vs redux

I've read about bindActionCreators, i've compiled a resumen here:
import { addTodo,deleteTodo } from './actionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addTodo, deleteTodo }, dispatch)
}
*short way
const mapDispatchToProps = {
addTodo,
deleteTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
another code use like this:
function mapDispatchToProps(dispatch) {
let actions = bindActionCreators({ getApplications });
return { ...actions, dispatch };
}
why previous code with bindActionCreators , don't need disptach parameter?
i've tried this way to get dispatch on this.props (but not working):
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop}, dispatch );
};
const withState = connect(
null ,
mapDispatchToProps,
)(withGraphqlandRouter);
why I had to change my old short way:
const withState = connect(
null ,
{ appSubmitStart, appSubmitStop}
)(withGraphqlandRouter);
in order to get this.props.dispatch()? because i neede to use dispatch for an isolated action creator inside a library with js functions. I mean before I don't needed use "bindActionCreators", reading this doc:
https://redux.js.org/api-reference/bindactioncreators
"The only use case for bindActionCreators is when you want to pass some action creators down to a component that isn't aware of Redux, and you don't want to pass dispatch or the Redux store to it."
I'm importing:
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
what is the difference using redux pure, and react-redux?
really I need "bindActionCreators" in my new code? because without this i can't see this.props.dispatch()
UPDATE:
I've found this solutions to get this.props.dispatch working:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators ({ appSubmitStart, appSubmitStop, dispatch }, dispatch ); // to set this.props.dispatch
};
does anyone can explain me? how i can send same distpach like a creator ?
First let's clear our minds regarding some of the key concepts here:
bindActionCreators is a util provided by Redux. It wraps each action creators to a dispatch call so they may be invoked directly.
dispatch is a function of the Redux store. It is used to dispatch actions to store.
When you use the object shorthand for mapState, React-Redux wraps them with the store's dispatch using Redux's bindActionCreators.
connect is a function provided by React-Redux. It is used to connect your component to the Redux store. When you connect your component:
It injects dispatch to your component only if you do not provide your customized mapDispatchToProps parameter.
Regarding what happened above to your code:
Component will not receive dispatch with customized mapDispatchToProps
In the code here:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop, dispatch }, // a bit problematic here, explained later
dispatch
); // to set this.props.dispatch
};
You are providing your own mapDispatch, therefore your component will not receive dispatch. Instead, it will rely on your returned object to contain the action creators wrapped around by dispatch.
As you may feel it is easy to make mistake here. It is suggested that you use the object shorthand directly, feeding in all the action creators your component will need. React-Redux binds each one of those with dispatch for you, and do not give dispatch anymore. (See this issue for more discussion.)
Writing customized mapState and inject dispatch manually
However, if you do need dispatch specifically alongside other action dispatchers, you will need to define your mapDispatch this way:
const mapDispatchToProps = (dispatch) => {
return {
appSubmitStart: () => dispatch(appSubmitStart),
appSubmitStop: () => dispatch(appSubmitStop),
dispatch,
};
};
Using bindActionCreators
This is exactly what bindActionCreators does. Therefore, you can simplify a bit by using Redux's bindActionCreators:
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(
{ appSubmitStart, appSubmitStop }, // do not include dispatch here
dispatch
);
};
As mentioned above, the problem to include dispatch in the first argument is that it essentially gets it wrapped around by dispatch. You will be calling dispatch(dispatch) when you call this.props.dispatch.
However, bindActionCreators does not return the object with dispatch. It's passed in for it to be called internally, it does not give it back to you. So you will need to include that by yourself:
const mapDispatchToProps = (dispatch) => {
return {
...bindActionCreators({appSubmitStart, appSubmitStop}, dispatch),
dispatch
};
};
Hope it helped! And please let me know if anything here is unclear :)
I have made some changes to your code please try this
import * as Actions from './actionCreators'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
const mapStateToProps = (state)=>(
{
todos: state.todos
}
)
const mapDispatchToProps = (dispatch)=> (
bindActionCreators(Actions, dispatch)
)
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

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;

Redux: Shared actions

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.

Resources