How can I access redux-toolkit store variables outside of React component? In React components I could access it with
import { useSelector } from 'react-redux';
const isAuthenticated: boolean = useSelector(
(state: RootState) => state.user.isAuthenticated,
);
Since useSelector isn't allowed to access outside of React. Or should I pass variables when I dispatch in React Component? I need access variable in createAsyncThunk API call
createAsyncThunk will pass in a thunkApi object as the second argument to the payload creator callback and on that object, you can call the getState method.
const fetchUserById = createAsyncThunk(
'users/fetchByIdStatus',
async (someArg, thunkApi) => {
const state = thunkApi.getState()
// whatever logic you need.
const response = await userAPI.fetchById(someArg)
return response.data
}
)
Related
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.
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)
I am getting this error even though I am using redux thunk. Redux store is also set up correctly (I think). I am creating a MERN app and I want to send a POST request to the backend (form submit) to create a new user. The request and response from the server are fine (checked using Postman). I just can not find where the problem is. The action creator which is causing this is :-
import axios from "axios";
export function signupUser(newuser) {
return function (dispatch) {
axios.post("/auth", newuser).then((res) => {
// dispatch
dispatch({
type: "ADDNEW_USER",
payload: res.data,
});
});
};
}
The store setup is :-
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
// const middleware = [thunk];
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__()
)
);
export default store;
The Signup.js component is ->
[Signup.js][1]
and the error is -> [here][2]
[1]: https://paste.ubuntu.com/p/gCX3wQPt9X/
[2]: https://imgur.com/a/6SDmydw
I have the following files:
index.js
const store = createStore(...)
ReactDOM.render(<Provider store={store}><BrowserRouter><App/></BrowserRouter></Provider>, document.getElementById('root'));
The App component: (App.js)
const App = withRouter(connect(mapStateToProps, mapDispatchToProps)(Main))
export default App
So then how i can access store.dispatch inside of the Main component?
If i try to do it by store.dispatch({...}) i get:
'store' is not defined no-undef
If mapDispatchToProps looks like:
const mapDispatchToProps = dispatch => ({
myAction1: () => dispatch(myAction1())
});
connect(mapStateToProps, mapDispatchToProps)(Main)
...then in the component, you can call this.props.myAction1()
If mapDispatchToProps uses bindActionCreators:
const actions = { myAction1, myAction2 };
const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
...then in the component, you can call this.props.myAction1() and this.props.myAction2()
If mapDispatchToProps is undefined:
connect(mapStateToProps)(Main)
then in the component, you can access this.props.dispatch
dispatch function should be available through this.props
this.props.dispatch()
Your component shouldn't access the store directly - connect abstracts that away.
Please see our new React-Redux docs page on connect: Dispatching Actions with mapDispatchToProps for a complete description of how to handle dispatching actions.
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)