I want to select an entire slice in redux like the following:
interface AuthState {
user?: User;
shouldShowLogin: boolean;
}
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setUser: ...,
showLogin: ...,
hideLogin: ...,
},
});
export const useAuth = () => useSelector((state) => state.auth);
So I could do something like const { user, shouldShowLogin } = useAuth(); within components. This is handier in most cases but I'm not sure if it's bad practice/inefficient.
I want to do something similar for dispatch (i.e. abstract away useDispatch()). Maybe something like const setUserWithDispatch = (info) => useDispatch()(info); so I can do simply setUserWithDispatch(userInfo) in my components, without having to do useDispatch() and then dispatch(setUser(userInfo)).
Ideally, I'd like to do const { user, shouldShowLogin, setUser, showLogin, hideLogin } = useAuth(); but I'm not sure how to go about it. Again, is this bad practice/inefficient? Why doesn't redux configure this automatically?
The component will rerender when the value selected from useSelector changes. Consequently, you generally want to select the minimum amount of information that you need in order to minimize rerenders.
If there are multiple properties in state.auth and you only care about one then the downside to this destructuring approach is that it will cause the component to rerender when other properties of the object change. In your example you seem to be using all of the properties so it shouldn’t matter.
I think you will have a hard time doing something custom with dispatch without violating the rules of hooks. Hooks cannot be called conditionally and must be called at the top level of the component. You cannot call useDispatch (or a custom hook that uses it) inside of an event handler callback, inside a useEffect function, etc.
The current implementation of the useDispatch hook means that you call useDispatch once per component to get access to the dispatch function for the current store based on the react-redux context. The returned dispatch function is a pure function (not a hook) and can be used anywhere.
You could use redux’s bindActionCreators to bind actions to the dispatch for a particular store instance. This sounds like what you are trying to do, but it is advised against. The downside is that you create an unnecessary coupling between the actions and a particular store instance. You would lose the ability to use a different store instance for testing.
Related
Inside a slice file we export all the the actions from that slice. For example:
export const {signoutUser, updateProfile, authenticateUser, clearUserState} = sliceName.actions;
And then we import useDispatch and particular actions from the slice or action file based on your folder structure. For example
import {clearUserState} from './slice';
import { useDispatch } from 'react-redux';
export const Component () {
const dispatch = useDispatch(clearUserState());
//rest component body
}
Now instead I am exporting a custom hook from the slice file like mentioned below:
export const useUserDispatch = () => {
const dispatch = useDispatch();
const userDispatch = {
signoutUser: (data) => dispatch(signoutUser(data)),
updateProfile: (data) => dispatch(updateProfile(data)),
authenticateUser: (data) => dispatch(authenticateUser(data)),
clearUserState: () => dispatch(clearUserState())
};
return {userDispatch}
};
And then i can just import that hook and use like
const {userDispatch}=useUserDispatch();
//inside component
<button onClick={userDispatch.clearUserState()}>Dispatch Button</button>
I just wanted to know if it's something that's not recommended in terms of redux way of writing code or am I doing anything wrong, it works perfectly fine though.
There is nothing wrong with your code. and the question can not be answered to pros and cons based on my experience, redux and all other open-source packages consider base common cases which people are using in the everyday app. There might be some suggestions for improvement but not best-case explanations for every app. you can just consider following and decide yourself
You can not use them as you mentioned useUserDispatch().clearUserState()
e.g.
<button onCleack={useDispatcher().clearUserState}>clear</button>
Hooks can be called conditionally so calling them as this level in the UI part might be conditionally canceled which is a really common case
every time this hook is called a hole new object is created for dispatchers
Also, useDispatch doesn't receive any argument and returns the nearest redux provider store.dispatch. see source code
Note: Redux suggests having one state for all of your apps and doesn't wrap part of your code with multiple providers.
Remember if you need this one of dispatcher (e.g. updateProfile) from some other part of the code, you may need to use this hook which is a waste of resources, or use it directly which shows a is a little bit of uncertainty and be not consistent from the other version (just a little is not a big case).
There are other options to handle these cases.
Remember what redux suggests for one provider, if you accept that you can also write your own dispatcher.
const storeDispatch = store.dispatch
// from slice file export dispatcher instead of action creator
const updateProfile = sliceName.actions.updateProfile;
export const updateProfileDispatcher = (data) => storeDispatch(updateProfile(data))
This can not only be used in your component but also can be used outside of react component inside the logic
Note: using a dispatcher outside the react is not the standard or recommended pattern and you might not want to use it.
You can also pass dispatch as the argument, something like thunk dispatcher
const updateProfile = dispatch => (data) => dispatch(updateProfile(data))
// or alongside of payload data
const updateProfile = (dispatch, data) => dispatch(updateProfile(data))
// also can set the default value of dispatch to store.dis
const updateProfile = (data, dispatch=storeDispatch) => dispatch(updateProfile(data))
// component
const dispatch = useDispatch()
<ProfileComponent onUpdate={updateProfile(dispatch)} />
I have tried pass value from parent to grandchild component, and it works. While I am thinking if there is another simpler or other way of passing props in shorter path.
What I did is quite cumbersome in codesandbox
There may be a common problem in react world called prop drilling by passing data to children only using props.
I would recommend only 2-level passing, if you need pass data deeper then you probably doing something wrong.
Use one of popular state management library (if your project is big) or React context (which is awesome)
Create a folder called /contexts and put contexts there. The structure of files can be like shown below:
First you need to create a context itself
type ClientContextState = {
data: User;
set: (data: User) => void;
logout: () => void;
};
// empty object as a default value
export const UserContext = createContext<UserContextState>({} as UserContextState);
Then create a Provider wrapper component
export const UserProvider = ({ children }: Props) => {
const [data, setData] = useState<User>({});
const sharedState = {
data,
set: setData
logout: () => setData(null)
}
return <UserContext.Provider value={sharedState}>{children}</UserContext.Provider>
});
You may also want to have an alias of useContext hook:
export const useUser = () => {
return useContext(UserContext);
};
After all this whenever you wrap your components or app to <UserProvider>...</UserProvider> you can use our hook to access data and methods form sharedState from any place you want:
export LogoutButton = () => {
const {data, logout} = useUser();
return <Button onClick={() => logout()}>Logout from {data.name}</Button>
}
Whenever you want to pass props or data from Grandparent to child component, always use react-redux. This is useful to maintain the state and access the data from anywhere/any component.
Another way is to use useContext hooks which you can use to pass the props
Following are the steps to use useContext hooks
Creating the context
The built-in factory function createContext(default) creates a context instance:
import { createContext } from 'react';
const Context = createContext('Default Value');
The factory function accepts one optional argument: the default value.
Providing the context
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
To set the value of context use the value prop available on the
<Context.Provider value={value} />:
function Main() {
const value = 'My Context Value';
return (
<Context.Provider value={value}>
<MyComponent />
</Context.Provider>
);
}
Again, what’s important here is that all the components that’d like later to consume the context have to be wrapped inside the provider component.
If you want to change the context value, simply update the value prop.
Consuming the context: Consuming the context can be performed in 2 ways.
The first way, the one I recommend, is to use the useContext(Context) React hook:
import { useContext } from 'react';
function MyComponent() {
const value = useContext(Context);
return <span>{value}</span>;
}
Generally it's helpful to consider whether moving state down the hierarchy would be the simplest route. That means lifting the component instantiation to a place closer to the state being used. In your example, that could mean Component_data is used inside Component and passed to its children there, removing one step in the nested data flow. Even better, would be that Child.a accesses Component_data.A directly.
In a real app with cases where accessing the data directly is less feasible, a solution I lean towards is using Context to set data in the parent that retrieves it, and then I can access it however deeply nested the component might be that needs it.
i.e. in App I would create the Context provider, and in ChildA I access it via useContext hook.
Further reading
https://reactjs.org/docs/context.html
https://overreacted.io/before-you-memo/#solution-1-move-state-down (this post is about an alternative to using useMemo but has an illustrative example of why moving state down is a good thing)
Now if i want to change value in store i should do following steps:
Go to constants/actionTypes file, create a line with action type
Go to actions and create action function
In each component where i use it i should create a function for mapDispatchToProps
In reducer i should write a logic of changing
Whats the point of such complexity?
Will it be wrong if i will do just one file with actions which will change the state? For example:
// actions.js
export const setCategories = (payload, setState, currentState) => setState({ categories: payload })
export const addCategory = (payload, setState, currentState) => setState({ categories: [...currentState.category, payload] })
To make it work i can create just couple of universal functions for all projects
1) getActions, which authomaticly collects all exports from actions.js and provide them to mapDispatchToProps, so in all components we could just write
const mapDispatchToProps = getActions
code of it can be something like
// actionsDispatcher.js
import * as actions from 'actions'
const getActions = (dispatch, ownProps) => {
Object.keys(actions).forEach(actionName => {
const fn = actions[actionName]
actions[actionName] = payload => dispatch({ action: fn, payload, type: _.toSnakeCase(actionName) })
}
return actions
}
which means we pass to dispatch the function of action from actions.js
2) setState which will work similary to react function, but for redux state
then in reducer function we just right
function rootReducer(state = initialState, action) {
if (action.action) {
action.action(action.payload, setState, state)
}
// here we make it extandable for ordinary way if required
if (action.type === '...') {
// ...
}
}
and nothing else...
So the question is whats wrong in such approach that will require for coder just write a function in one file 'actions.js' and call it from any component as props.someActionName(someParams) instead of changing 4 differents files?
Thank you
Redux is supposed to make complex requirements easier to implement but if you have simple requirements then it makes implementing these requirements more complicated.
The motivation mentions CQRS(Command Query Responsibility Segregation) that separates how you read from store (in redux with selectors and I'm a big fan of reselect) with how you write to it (with action and reducers).
The actions and reducers are the command (write) part of CQRS and is event sourcing, redux is sometimes referred to as an event store. This enables you to add or remove handlers (reducers or middle ware) for your events (actions) that can update the store, dispatch other events (=actions), do asynchronous stuff, write to local storage.
If you need to do all these things in one function (async fetch, write to local storage, call other functions (dispatch other actions),...) then that function becomes unmanageable.
Even if the function only calls other functions then it still needs to know the entire process of certain action. But if (for example) you had a local storage middleware that would write to storage on certain actions then no other code needs to know how or when it's called. So when logic of writing to local storage changes it is limited to the local storage middle ware.
This is the advantage of handlers (reducers, middleware) listening to events (actions), the handler only needs to know about a small portion of the process, not the entire process.
With event resourcing we also know why the state has a certain value instead of only knowing what the state is, the article states:
However there are times when we don't just want to see where we are, we also want to know how we got there.
Another big advantage of an event store is that you can re create the data by playing back the events. All this is excellently done with redux def tools.
Here is a great book on React with Redux.
Conventional-redux is a:
Library for small and medium react applications, it wraps the react-redux and provides API based on convention over configuration pattern without breaking redux compatibility.
You simply define an interactor:
class CounterInteractor {
// initial state
defaultState() {
return 0;
}
// actions:
doubleAsync() {
setTimeout(() => { this.dispatch('counter:double') }, 500)
}
// reduce methods:
onIncrement() {
return this.state + 1;
}
onDouble() {
return this.state * 2;
}
}
And dispatch actions to that interactor from your connected component component. That's it!
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.
In my project I have action creator that depend on values that are in the state of the application to generate a new value or to decide what action to dispatch. My question is to know which is the right way to do it. I thought of two ways. Access those values within the action creator:
export const changePreviousPage = () => {
return (dispatch, getState) => {
let pagination = getState().appReducers.availability.pagination;
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
dispatch({
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
});
}
};
The other option I thought was to pass the value from the component to the action creator:
In my component
class Pagination extends Component {
...
handlePreviousPage() {
const {pagination} = this.props;
this.props.changePreviousPage(pagination);
}
...
}
In my action creator
export const changePreviousPage = pagination => {
let previousPage = pagination.actualPage != 1 ? pagination.actualPage - 1 : pagination.actualPage;
return{
type: types.CHANGE_PREVIOUS_PAGE,
previousPage
}
};
What is the best way to address it ?
In my opinion always use/retrieve the state at the closest time to execution, here the action creator (or rather more specifically the thunk you are returning that would then execute).
Remember that dispatch may have any number of middleware running before the actual store.dispatch call. This can include async middleware, so the state may have changed in between calling the dispatch and the store.dispatch call it will ultimately run.
Another one to consider is you may be dispatching multiple things in an action creator which change the state and invalidate what you passed into the action creator at the top. Also a reason why I consider let state = getState() at the top of an action creator a bad idea unless you are very sure nothing is going to change during your processing (as soon as you involve any API calls I would always use getState() again instead of using a stored variable).
Also putting data from state into props (using a redux container and connect helper method) will cause a rerender every time this changes, which could have a performance impact in some cases.
My personal coding preference is also to keep things as simple as possible in mapDispatchToProps (assuming that is where you're passing in your handlers like handlePreviousPage) and avoid any data processing (in your example it's not much, but you can easily see how that may get out of hand if you're preparing data for your action creator).