React-Redux: Add custom third parameter to mapStateToProps - possibly connectAdvanced? - reactjs

I have an application using React and Redux.
Several parts of my data model are potientially lazy, so a common pattern when reading data from the redux state is: Lookup if lazy data is already loaded, if not, dispatch a ReduxThunk-action that fetches the data.
To be able to trigger this process within mapStateToProps I want to implement an own connect function that passes a resolve function as third parameter to mapStateToProps. This resolve function should have access to dispatch, so I could trigger the ReduxThunk-action loading of the lazy data. My mapStateToProps function should look like this:
const mapStateToProps = (state, ownProps, resolve) => {
var myData;
if (state.potentiallyLazy instanceof LazyLink) {
resolve(state.potentiallyLazy);
}
else {
myData = state.potentiallyLazy;
}
return {
myData
}
}
Any help how to achieve that, or a hint for a different approach? I had a look at connectAdvanced(), but didn't really find a solution with that.

This is a bad idea on a couple levels.
First, your mapState functions should be pure, synchronous, and have no side effects, same as your reducers.
Second, it looks like you're keeping class instances in your store state, which is discouraged because it will break time-travel debugging.
If you need to trigger loading behavior, I would suggest putting it inside the component, in the lifecycle methods.

You can extend the connect HOC to add your resolve function like
export connectAdvanced = (...args) => {
const resolve = () => {} // your resolve function here
const [mapStateToProps] = args;
const overridenMapStateToProps = (state, ownProps) => {
mapStateToProps(state, ownProps, resolve)
}
const restParams = [overridenMapStateToProps, ...args.slice(1)];
return (Component) => connect.apply(null, restParms)(Component);
}
and use it like
const mapStateToProps = (state, ownProps, resolve) => {
var myData;
if (state.potentiallyLazy instanceof LazyLink) {
resolve(state.potentiallyLazy);
}
else {
myData = state.potentiallyLazy;
}
return {
myData
}
}
export default connectAdvanced(mapStateToProps)(App);
However this approach no longer keeps your mapStateToProps function pure and you should consider moving the lazy loading action in the lifecycle methods, which is a better and correct way to do this

Related

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

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;
}

React Js - Combine Redux and Services layers

After some researches, I found some questions on stackoverflow about what I am trying to achieve, however, I don't feel that these questions and their answers gives me the "answers" or the "directions" i am looking for..
Note: I am pretty new to react even if I already made 2 projects and implemented redux into one of them. However, I ain't new at all in C# or in Go, even less in C. Based on my experience, I am just used to some architectures and I would like to reproduce one of them.
Here is a pretyy good schema from a similar question of mine:
Situation:
So let say I have pages that contains Components. I want these pages/compoments to display some stuff. One of my functionnality is to discover a map and for that, when the client moves, he gets new parts from my API. However, I don't wanna ask the server to give me the new parts and the ones I discovered already.
My idea about it would be to use a service MapService.js. This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones (concat).
However, I have to be logged for this, so I would like an ApiService.js that would store my authentication data and automatically put them in each of my requests.
Based on what I said, we would have something as:
Page -> Component -> Service -> API
From this, the API response would be gotten by my service, handled, then returned to the component. Handled means (data added to the previous then all returned)
I saw on internet one question that was referring "MVCS" (Model View Controller Service) pattern and I think I am looking for something as but I am not sure about how to implement it in ReactJs.
Redux seems to be something that you put all around and everywhere in your solution. What I would like is to use it as a "repository" let say, to be able to manage it from a service and not from the component itself. However, a service should be a single instance shared across the app and I don't know if something such as dependency injection could be the solution in ReactJS
Feel free to ask any edit if you need more details :)
Thanks for your help !
Here is a minimal example of Redux middleware usage. Usually, redux devs are using libraries (that give you a middleware) to have access to more appropriate APIs.
Redux middleware are chained, so each middleware can call the next middleware. The first middleware of the chain is called every time dispatch function (you can have it from react-redux connect) is called. In a middleware, if there is no next middleware it is the reducers that will be called. The next middleware can be call asynchronously after receiving an action. (Redux docs will still be better than my explainations).
In my example there is a catService that provide function that call rest API. Your services can be anything (a Class instance or a singleton for example). Usually in React/Redux stack, devs don't use object oriented development.
If a component dispatch getCat(123), the catMiddleware will be called (synchronously). Then requestGetCat will be called with the id 123. When the promise returned by requestGetCat will be resolved a setCat action will be send through the reducers to update the redux state. Once the redux state is done, the component listening for cats items object will be update too (triggering a rerender).
That can look very complexe, but in fact, it is very scalable and convenient.
// catService.js
// return a promise that return a cat object
const requestGetCat = id =>
fetch(`www.catcat.com/api/cat/${id}`)
.then(response => response.json())
// catTypes.js
export const GET_CAT = 'GET_CAT'
export const SET_CAT = 'SET_CAT'
// catActions.js
export const getCat = id => ({
type: GET_CAT,
id
})
export const setCat = (cat, id) => ({
type: SET_CAT,
id,
cat
})
// catReducer.js
const initialState = {
items: {}
}
const catReducer = (state = initialState, action) => {
if (action.type === SET_CAT) {
return {
items: {
...state.items,
[action.id]: action.cat
}
}
}
}
// catMiddleware.js
const handleGetCat = (next, action) => {
requestGetCat(action.id)
.then(cat => next(setCat(cat, action.id)))
// after retrieving the cat send an action to the reducers (or next middleware if it exist)
}
const actionHandlers = {
[GET_CAT]: handleGetCat
}
// receive every actions passing by redux (if not blocked)
// store: { dispatch, getState }
// next: next middleware or reducers (that set redux state)
// action: a redux action (dispatched) with at least type property
const catMiddleware = store => next => action => {
const handler = actionHandlers[action.type]
if (handler) {
handler(next, action)
} else {
// passing the action to the next middleware (or reducer - when there is no next middleware)
next(action)
}
}
// you have to apply your middleware
// and your reducer (see redux doc)
This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones
This is something I've wanted to do in the past, but never implemented a solution for.
The issue is that you essentially want to "cross the streams"..
In Redux there are two separate streams, ie dispatch an action to update the store, and read data from the store. Each of these are executed separately from a component. Combined, they can be used in a cycle by calling an action to load data into the store which triggers an update of the component which then reads from the store.
Basically you can't have non-component code that reads from the store, and if the data is missing, fires an action to load the data, then returns the data.
Thinking about it now, I'm wondering if the way to do this without adding logic to your view component is to wrap it in a component (HOC) that provides the logic.
The HOC will check the state for the location specified in the props. If it doesn't find it, it will dispatch an action to fetch it and render a loading display. When the state is updated with the new location it will update and render the wrapped component.
You could optionally always render the wrapped component and have it cope with the missing location until it is updated with the location set..
untested brain-dump below
loader HOC:
import React, { useEffect } from "react";
import actions from "./actions";
function withLocationLoader(Component) {
const Wrapper = function ({ location, locations, loadLocation, ...props }) {
useEffect(() => {
if (!locations[location]) {
loadLocation(location);
}
}, [locations]);
if (locations[location]) {
return <Component locations={locations} {...props} />;
}
return <div>Loading...</div>;
}
const mapStateToProps = (state, ownProps) => {
return { locations: state.locations };
};
const mapActionsToProps = {
loadLocation: actions.loadLocation,
};
return connect(
mapStateToProps,
mapActionsToProps
)(Wrapper);
}
export { withLoader };
component:
function MyBareComponent({ locations }) {
return <div>{JSON.stringify(locations)}</div>;
}
const MyComponent = withLocationLoader(MyBareComponent);
export { MyComponent };
actions: (utilising redux-thunk middleware)
function setLocation(location, data) {
return { type: "SET_LOCATION", payload: { location, data } };
}
export function loadLocation(location) {
return dispatch =>
Promise.resolve({ geoData: "" }) // mock api request
.then(data => dispatch(setLocation(location, data)));
}

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!

Refresh component after remove an object - ReactJS

I would like to ask for your opinion about this. Basically what I want now is to refresh the component list after I remove an item on the object lists. Currently I can successfully removed an item via deleteHeroes(list,index) function but my component is not refreshing at all to reflect the removed item. Can you should some light on how can I do it? here's my code below:
componentDidMount(){
// Fetch lists of heroes
this.props.getAllHeroes();
}
renderHeroesList(){
var listData = this.props.heroes.map((heroes,index) => (
<HeroesListComponent key={heroes.id} heroes={heroes} onDelete = { () => this.deleteHeroes(heroes,index)} />
));
return listData;
}
// Remove item on heroes list
deleteHeroes(list,index){
const heroesProps = this.props.heroes;
heroesProps.splice(heroesProps.indexOf(index), 1);
}
render(){
return(
{ this.renderHeroesList() }
);
function mapStateToProps(state){
return {
heroes: state.heroes.data
}
}}
function mapDispatchToProps(dispatch){
return bindActionCreators ({
getAllHeroes: getAllHeroes,
deleteHero: deleteHero,
}, dispatch);
}
You have a side effect, which should be avoided at all cost. In your case, it is that you are mutating the internal reference of heroes props. So typical plan to avoid this problem is to clone the props and then dispatch a new action with the new props data. So your code should look like:
deleteHeroes(list,index){
const clonedHeroesProps = this.props.heroes.slice(0); // clone the array
heroesProps.splice(heroesProps.indexOf(index), 1);
dispatch({type: 'SAVE_HEROES', heroes: clonedHeroesProps});
}
The better, more Reactish way would be by using Immutability Helpers:
deleteHeroes(list,index){
const clonedHeroesProps = update(heroesProps, {$splice: [[heroesProps.indexOf(index), 1]]});
dispatch({type: 'SAVE_HEROES', heroes: clonedHeroesProps});
}
Because you did not notify anything about your change.
You must dispatch an action after deleteHeroes, something like this
deleteHeroes(list,index){
const { heroesProps, dispatch }= this.props;
heroesProps.splice(heroesProps.indexOf(index), 1);
dispatch({type: 'SAVE_HEROES', heroes: heroesProps});
}
// and somewhere in reducer
case SAVE_HEROES:
return {...state, heroes: action.heroes}
and write the appropriate function to reducer.
But let component delete hereos (alter state), you broke the idea of redux.
Instead, the component should not directly modify heroes, you dispatch an action like 'DELETE_HEROES' and let reducer do the rest.
You have 2 main options to consider:
put the data (state) to shared common ancestor, that is standard strategy from react: https://facebook.github.io/react/docs/lifting-state-up.html
put all the state to Redux (https://github.com/reactjs/react-redux), then u connect your component and display based on the redux state. this case, u do not have to do any thing after deleting, redux framework will take care of the data flow and ui refresh

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