Using the Context API as a way of mimicking useSelector and useDispatch with redux v5 - reactjs

I'm working on a React project where I'm constrained to using React Redux v5, which doesn't include useDispatch and useSelector.
Nonetheless I really would like to have these hooks (or something like them) available in my app.
Therefore, I've created a wrapper component at the top level of the app which I connect using redux's connect function.
My mapStateToProps and mapDispatchToProps then just look like this:
const mapDispatchToProps = (dispatch: DispatchType) => {
return {
dispatch,
};
};
const mapStateToProps = (state: StateType) => {
return {
state,
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MainLayout);
In my wrapper component, I then pass the dispatch and the state into the value:
<DispatchContext.Provider value={{ state, dispatch }}>
{children}
</DispatchContext.Provider>
Finally, I have a hook that looks like this:
const useSelectAndDispatch = () => {
const context = useContext(DispatchContext);
if (context === null) {
throw new Error("Please use useDispatch within DispatchContext");
}
const { state, dispatch } = context;
function select(selector) {
return selector(state);
}
return { dispatch, select };
};
I then use dispatch and selector in my components via useSelectAndDispatch.
I was wondering if this is an appropriate way to go about this issue, and whether I can expect any performance problems. I am using reselect, and have a good understanding of memoization. I'm just looking for opinions, since I've heard that the redux team held off implementing useDispatch and useSelector for a long time because of performance issues.
Many thanks for any opinions!

This will cause significant peformance problems. Your mapStateToProps is using the entire state object, so every time anything changes in the state, the provider must rerender. And since the provider rerendered with a new value, so too must every component that consumes the context. In short, you will be forcing most of your app to rerender anytime anything changes.
Instead of using mapStateToProps and mapDispatchToProps, i would go back to the actual store object, and build your hooks from that. Somewhere in your app is presumably a line of code that says const store = createStore(/* some options */).
Using that store variable, you can then make some hooks. If i can assume that there's only one store in your app, then the dispatch hook is trivial:
import { store } from 'wherever the store is created'
export const useMyDispatch = () => {
return store.dispatch;
}
And the selector one would be something like this. It uses .subscribe to be notified when something in the store changes, and then it uses the selector to pluck out the part of the state that you care about. If nothing changed, then the old state and new state will pass an === check, and react skips rendering. If it has changed though, the component renders (only the component that called useMySelect plus its children, not the entire app)
export const useMySelector = (selector) => {
const [value, setValue] = useState(() => {
return selector(store.getState());
});
useEffect(() => {
const unsubscribe = store.subscribe(() => {
const newValue = selector(store.getState());
setValue(newValue);
});
return unsubscribe;
}, []);
return value;
}

Related

why react hook init before redux props

this is some kind of code so be gentle :D
i have some page that use react hooks and redux and the redux state work
i checked it it load data. the scenario is i have page that load input boxes from redux thunk and save the changes with hooks and redux thunk
const MyComponent = (props) => {
const { reduxProp } = props
const [t1, setT1] = useState('')
useEffect( () => {
reduxActionLoadData // t1 , t2 data
setT1(reduxProp.t1)
setT2(reduxProp.t2)
},[])
return (
<div>
<input value={t1} onChange={ (e) => { setT1(e.target.value) } />
<input value={t2} onChange={ (e) => { setT2(e.target.value) } />
</div>
)
}
const mapStateToProp = (state) => (
{
reduxProp: state.reduxProp
})
const mapDispatchToProp = (dispatch) => (
{
reduxActionLoadData = connectActionCreators(reduxActionLoadDataAction, dispatch)
})
export default const connect(mapStateToProps, mapDispatchToProps)(MyComponent)
when i check props it is loaded, redux state is loaded too
but hooks init with empty value
i try to init them with reduxProp.t1, reduxProp.t2 no luck there too
the thing is when i run app it works fine but when i refresh the page it fails
and sometimg it fails when i run app too
i try to use loading state and do stuff after loading = true no luck the either
For one, your mapDispatchToProp currently does not seem to do anything, since with the = it will be parsed as a method body, not as an object - so you are not connecting your actions at all. It should look more like this:
const mapDispatchToProp = (dispatch) => (
{
reduxActionLoadData: connectActionCreators(reduxActionLoadDataAction, dispatch)
})
Then you should instead be using the map object notation:
const mapDispatchToProp = {
reduxActionLoadData: reduxActionLoadDataAction
}
and the you also have to actually use that in your component:
useEffect( () => {
props.reduxActionLoadData()
},[])
That will still create an asynchronous effect, so reduxProp.t1 will not change immediately.
You will probably have to update those later in a separate useEffect.
useEffect( () => {
setT1(reduxProp.t1)
},[reduxProp.t1])
All that said, there is no reason to use connect at all, since you could also use the useSelector and useDispatch hooks. connect is a legacy api that you really only need to use with legacy class components.
Generally I would assume that you are right now learning a very outdated style of Redux by following an outdated tutorial. Modern Redux does not use switch..case reducers, ACTION_TYPE constants, hand-written action creators, immutable reducer logic or connect - and it is maybe 1/4 of the code of old-style Redux.
Please follow the official Redux tutorial for an understanding of mondern Redux. https://redux.js.org/tutorials/essentials/part-1-overview-concepts

React useEffect inside const function with MobX

I have some React Redux code written in Typescript that loads some data from my server when a component mounts. That code looks like this:
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { MyAction } from 'my/types/MyAction';
export const useDispatchOnMount = (action: MyAction) => {
const dispatch = useDispatch();
return useEffect(() => {
dispatch(action);
}, [dispatch]);
};
This is simple enough - it uses the useEffect hook to do what I want. Now I need to convert the code so that it uses MobX instead of Redux for persistent state. If I have my own MobX store object called myStore, and myStore has a async method "loadXYZ" that loads a specific set of data XYZ, I know I can do this inside my component:
useEffect(() => {
async functon doLoadXYZ() {
await myStore.loadXYZ();
}
doLoadXYZ();
}, []);
This does indeed work, but I would like to put all this into a single fat arrow function that calls useEffect, much like what the useDispatchOnMount function does. I can't figure out the best way to do this. Anyone know how to do this?
EDIT: After further digging, it looks more and more like what I am trying to do with the Mobx version would break the rules of Hooks, ie always call useEffect from the top level of the functional component. So calling it explicitly like this:
export const MyContainer: React.FC = () => {
useEffect(() => {
async functon doLoadXYZ() {
await myStore.loadXYZ();
}
doLoadXYZ();
}, []);
...
};
is apparently the best way to go. Butthat raises the question: is the redux version that uses useDispatchOnMount a bad idea? Why?
You can do this if you don't use async/await in the useEffect. If you are fetching data, I would store it in myStore and use it directly out of the store instead of using async/await. It might look something like this:
export const SomeComp = observer(function SomeComp() {
const myStore = useStore() // get the store with a hook or how you are getting it
useEffect(myStore.loadXYZ, [myStore])
return <div>{myStore.theLoadedData}</div>
})
In loadXYZ you just store the data the way you want and use it. The component observing theLoadedData will re-render when it comes back so you don't need to have async/await in the component.

React get state from Redux store within useEffect

What is the correct way to get state from the Redux store within the useEffect hook?
useEffect(() => {
const user = useSelector(state => state.user);
});
I am attempting to get the current state within useEffect but I cannot use the useSelector call because this results in an error stating:
Invariant Violation: Hooks can only be called inside the body of a function component.
I think I understand why as it breaks one of the primary rules of hooks.
From reviewing the example on the Redux docs they seem to use a selectors.js file to gather the current state but this reference the mapStateToProps which I understood was no longer necessary.
Do I need to create some kind of "getter" function which should be called within the useEffect hook?
Don't forget to add user as a dependency to useEffect otherwise your effect won't get updated value.
const user = useSelector(state => state.user);
useEffect(() => {
// do stuff
}, [user]);
You can place useSelector at the top of your component along with the other hooks:
const MyComponent = () => {
...
const user = useSelector(state => state.user);
...
}
Then you can access user inside your useEffects.
I found using two useEffects to works for me, and have useState to update the user (or in this case, currUser).
const user = useSelector(state=>state.user);
const [currUser, setCurrUser] = useState(user);
useEffect(()=>{
dispatch(loadUser());
}, [dispatch]);
useEffect(()=>{
setCurrUser(user);
}, [user]);
You have to use currUser to display and manipulate that object.
You have two choices.
1 - If you only need the value from store once or 'n' time your useEffect is called and don't want to listen for any changes that may occur to user state from redux then use this approach
//import the main store file from where you've used createStore()
import {store} from './store' // this will give you access to redux store
export default function MyComponent(){
useEffect(() =>{
const user = store.getState().user;
//...
},[])
}
2 - If you want to listen to the changes that may occur to user state then the recommended answer is the way to go about
const MyComponent = () => {
//...
const user = useSelector(state => state.user);
useEffect(() => {
//...
},[])
//...
}
const tournamentinfofromstore=useSelector(state=>state.tournamentinfo)
useEffect(() => {
console.log(tournamentinfofromstore)
}, [tournamentinfofromstore])
So the problem is that if you change the state inside the useEffect that causes a rerender and then again the useEffect gets called "&&" if that component is passing data to another component will result in infinite loops.and because you are also storing that data in the child component's state will result in rerendering and the result will be infinite loop.!!
Although it is not recommended, you can use store directly in your component, even in the useEffect.
First, you have to export store from where it is created.
import invoiceReducer from './slices/invoiceSlice';
import authReducer from './slices/authSlice';
export const store = configureStore({
reducer: {
invoices: invoicesReducer,
auth: authReducer,
},
});
Then you can import it to a React Component, or even to a function, and use it.
import React, { useEffect } from 'react';
import { store } from './store';
const MyComponent = () => {
useEffect(()=> {
const invoiceList = store.getState().invoices
console.log(invoiceList)
}, [])
return (
<div>
<h1>Hello World!</h1>
</div>
)
}
export default MyComponent
You can study the API for Store in here.
You can also see why this approach is not recommended in
here.
Or, if you are interested in using redux store outside a react component, take a look at this blog post.
To add on top of #Motomoto's reply. Sometimes you depend on store to be loaded before useEffect. In this case you can simply return in if the state is undefined. useEffect will rerender once the store is loaded
const user = useSelector(state => state.user);
useEffect(() => {
if(user === undefined){
return}else{
// do stuff
}}, [user]);
I'm having the same issue, The problem to the useSelector is that we cant call it into the hook, so I can't be able to update with the action properly. so I used the useSelector variable as a dependency to the useEffect and it solved my problem.
const finalImgData_to_be_assigned = useSelector((state) => state.userSelectedImg);
useEffect(()=>{
console.log('final data to be ready to assign tags : ', finalImgData_to_be_assigned.data);
}, [finalImgData_to_be_assigned ])

Redux, Reselect and ImmutableJS causing unnecessary renders on child components

Based on all the Redux and Reselect docs I have just read and re-read the below selector should only do the thing.toJS() processing if the Immutable Map that getThing() returns is not equal to the previous one.
...
// Selector
import { createSelector } from 'reselect'
const getThing = (state, thingId) => {
return state.entities.getIn(['things', thingId])
}
const makeThingSelector = () => {
return createSelector(
[getThing],
(thing) => {
return thing.toJS()
}
)
}
export default makeThingSelector
...
// Container
const makeMapStateToProps = () => {
return (state, ownProps) => {
const { thingId } = ownProps
const things = select.makeThingsSelector()(state, thingId)
return {
hasNoThings: things.length === 0,
things
}
}
}
const Container = connect(
makeMapStateToProps,
mapDispatchToProps
)(Component)
...
This holds true unless I have a child 'smart' component. In this case, when the parent triggers a render, the selector called in the child component's container always processes the value regardless of whether the result is new or not.
I have been trying to encapsulate the ImmutableJS API inside my selectors but this means to avoid a re render on these nested components every time their parents update I have to do a deep equality check in the shouldComponentUpdate function. This is expensive and doesn't seem like a decent solution.
The app state is normalised so the updated part of the state tree is not a hierarchical parent to the part of the state that the child component is dependent on.
Am I missing something key here?
On every store update react-redux performs following steps (putting all internal complexities aside):
Calls mapStateToProps and mapDispatchToProps.
Shallowly comparing resulted props
Re-renders Component In case new props differs from previous one.
This way mapStateToProps will be called on every store update by-design. So will following line of code:
...
const things = select.makeThingsSelector()(state, visitId)
...
As you can see new reselect selector will be created every time effectively preventing any memoization (there are no global state in reselect, memoization happens per selector).
What you have to do is change your code so that one and the same selector will be used on every invocation of mapStateToProps:
const thingSelector = select.makeThingsSelector();
...
const makeMapStateToProps = () => {
return (state, ownProps) => {
const { visitId } = ownProps
const things = thingSelector(state, visitId)
return {
hasNoThings: things.length === 0,
things
}
}
}
UPDATE: Also I don't see any reason to use factory-style makeThingsSelector and makeMapStateToProps. Why not just go with something like:
...
// Selector
export default createSelector(
[getThing],
(thing) => thing.toJS()
);
...
// Container
const mapStateToProps = (state, ownProps) => {
const { visitId } = ownProps
const things = select.thingsSelector(state, visitId)
return {
hasNoThings: things.length === 0,
things
}
}
const Container = connect(
mapStateToProps,
mapDispatchToProps
)(Component)
...
Since the redux state in this application uses the ImmutableJS data structure, Reselect may not be necessary.
Firstly, ImmutableJS manipulates only the slice of the data structure affected by a change operation and therefore all changes to the larger state may not affect the slice being passed to the container.
Secondly, the redux connect function returns a pure container by default and upon encountering same slice will not re-render. However, the mapStateToProps will be invoked since the whole state and possibly the ownProps have changed.
For finer control, the rendering of same container can be linked directly to changes to a particular slice of the state and ownProps by adding areStatesEqual and areOwnPropsEqual predicate properties to the fourth parameter of the connect function (better known as the options object).
const mapStateToProps = ({ entities }, { thingId }) => {
const things = entities.getIn(['things', thingId]).toJS();
return {
hasNoThings: things.length === 0,
things
};
};
const Container = connect(
mapStateToProps,
mapDispatchToProps,
undefined, {
areOwnPropsEqual: (np, pp) => np.thingId === pp.thingId,
areStatesEqual: (ns, ps) => ns.entities.get(‘things’).equals(
ps.entities.get(‘things’)
)
}
)(Component);
If both of these predicates are true, not only would the container and its children not re-render, the mapStateToProps would not even be invoked!

What are selectors in redux?

I am trying to follow this code in redux-saga
export const getUser = (state, login) => state.entities.users[login]
export const getRepo = (state, fullName) => state.entities.repos[fullName]
Which is then used in the saga like this:
import { getUser } from '../reducers/selectors'
// load user unless it is cached
function* loadUser(login, requiredFields) {
const user = yield select(getUser, login)
if (!user || requiredFields.some(key => !user.hasOwnProperty(key))) {
yield call(fetchUser, login)
}
}
This getUser reducer (is it even a reducer) looks very different from what I would normally expect a reducer to look like.
Can anyone explain what a selector is and how getUser is a reducer and how it fits in with redux-saga?
getUser is not a reducer, it is indeed a selector, that is, a function that knows how to extract a specific piece of data from the store.
Selectors provide an additional layer such that if you altered your store structure and all of a sudden your users were no longer at state.entities.users but instead at state.users.objects.entities (or whatever) then you only need to update the getUser selector and not every place in your app where you were making a reference to the old location.
That makes them particularly handy when it comes to refactoring your Redux store.
Selectors are getters for the redux state. Like getters, selectors encapsulate the structure of the state, and are reusable. Selectors can also compute derived properties.
You can write selectors, such as the ones you saw in redux-saga. For example:
const getUsersNumber = ({ users }) => users.length;
const getUsersIds = ({ users }) => users.map(({ id }) => id);
etc...
You can also use reselect, which is a simple “selector” library for Redux, that memoize selectors to make them more efficient.
Selectors are functions that take Redux state as an argument and return some data to pass to the component.
const getUserData = state => state.user.data;
Why should it be used?
One of the main reasons is to avoid duplicated data in Redux.
Your data object shape keeps varying as your application grows, so rather than making changes in all the related component.It is much recommended/easier to change the data at one place.
Selectors should be near reducers because they operate on the same state. It is easier for data to keep in sync.
Using reselect helps to memoize data meaning when the same input is passed to the function, returns the previous result rather than recalculating again.So, this enhances your application performance.
function mapStateToProps (state) {
return {
user: state.user,
}
}
initialState of reducer by user store
const initialState = {
isAdmin:false,
isAuth:false,
access:[1,2,5]
};
class AppComp extends React.Component{
render(){
const {user: { access:access}} = this.props;
const rand = Math.floor(Math.random()*4000)
return (<div>
{`APP ${rand} `}
<input type="button" defaultValue="change auth" onClick={this.onChangeUserAuth} />
<p>TOTAL STATUS COUNT IS {access.length}</p>
</div>)
}
}}
but you can use selector
var getUser = function(state) {
return state.user
}
const getAuthProp = createSelector(
getUser,
(user) => user.access
);
function mapStateToProps (state) {
return {
// user: state.user,
access: getAuthProp(state)
}
}
Main Problem is this component use all user: state.user and any changes in user (etc isAdmin ,isAuth, access) runs rerender this component which need only part of this store - access!!!
In Redux, whenever an action is called anywhere in the application,
all mounted & connected components call their mapStateToProps
function. This is why Reselect is awesome. It will just return the
memoized result if nothing has changed.
In the real world, you will most likely need the same certain part of
your state object in multiple components.
https://medium.com/#parkerdan/react-reselect-and-redux-b34017f8194c
The createSelector function provided by Reselect implements the most basic way to derive a selector from previous selectors. The simplest use case is to derive a selector from a single other selector. In this case, the parameters to createSelector are the input selector and a function transforming the result of that selector into the result of the new selector. For example
var getProducts = function(state) {
return state.products
}
import {getProducts} from '../app/selectors'
import {createSelector} from 'reselect'
export const getProductTitles = createSelector(
getProducts,
(products) => products.map((product) => product.get('title'))
)
This is equivalent to (ignoring memoization):
import {getProducts} from '../app/selectors'
export const getProductTitles = (state) => {
return getProducts(state).map((product) => product.get('title'))
}
The createSelector function can combine data from multiple selectors as well as from a single selector. We can pass any number of selectors to createSelector, and their results will be passed to the function passed as the final argument. For a (somewhat contrived) example:
const isInCheckout = createSelector(
getIsShippingPage,
getIsBillingPage,
getIsConfirmationPage,
(isShipping, isBilling, isConfirmation) =>
isShipping || isBilling || isConfirmation
)
is equivalent to
const isInCheckout = (state) => {
return (
getIsShippingPage(state) ||
getIsBilingPage(state) ||
getIsConfirmationPage(state)
)
}
common pattern when writing mapStateToProps functions with selectors is to return an object with each key storing the result of a particular selector. The createStructuredSelector helper function in Reselect lets us write this pattern with the minimum of boilerplate. For example, if we writ
const mapStateToProps = createStructuredSelector({
title: getProductTitle,
price: getProductPrice,
image: getProductImage
})
it is equivalent to
const mapStateToProps = (state) => {
return {
title: getProductTitle(state),
price: getProductPrice(state),
image: getProductImage(state)
}
}
https://docs.mobify.com/progressive-web/0.15.0/guides/reselect/

Resources