The example code for creating a selector that uses the props is as follows:
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos()
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
return mapStateToProps
}
connect(makeMapStateToProps)(Component);
However, this means the memoized selectors will be lost after the component gets unmounted (unlike the selectors defined in a file).
Is there a way in Reselect library to save these selectors?
If not, what would be the best approach of saving them? I am thinking of caching, but I would need one selector for every value.
Related
By the sake of understanding better createSelector, I'm trying to use it instead of useSelector
const domainEntitlements = useSelector((state) => state.objects.domainEntitlements[match.params.id]);
const domainEntitlement = createSelector((state) => state.objects.domainEntitlements, (domainEntitlements) => (domainEntitlements));
console.log(domainEntitlements, domainEntitlement);
the useSelector logs an object (correct), but the createSelector logs an equalityCheck function.
Am I doing something very stupid or why I can get the data with useSelector and not with createSelector?
CreateSelector returns a function, this function you can pass to useSelector as an argument.
CreateSelector is used to specify logic to where data is located in the state or how data is derived only once and re use them in other selectors. Here is how you can use your selector in the component:
const selectObjects = (state) => state.objects;
const selectDomainEntitlements = createSelector(
[selectObjects], //re use selectObjects
(objects) => objects.domainEntitlements
);
const createSelectDomainEntitlementById = (id) =>
createSelector(
[selectDomainEntitlements], //re use selecting domain entitlements
(domainEntitlements) => domainEntitlements[id]
);
//example of using the selector in your component
const Component = () => {
const domainEntitlement = useSelector(
createSelectDomainEntitlementById(match.params.id)
);
};
A more detailed explanation on selectors and how to possibly optimize your app by using memoization can be found here
Maybe this is not necessarily a reselect question
const makeSelectError = () => createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
and in reselect we use
const mapStateToProps = createStructuredSelector({
error: makeSelectError(),
});
why can't we use like below?
const makeSelectError = createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
and use like below in reselect
const mapStateToProps = createStructuredSelector({
error: makeSelectError,
});
Are there any issues/disadvantages with my code, or is that a standard practice?
Not only is the second way valid and correct it has other advantages.
With the first snippet you provide:
const makeSelectError = () => createSelector(
selectGlobal,
(globalState) => globalState.get('error')
);
makeSelectError is a factory function in that every time it is called it is returning a new and unique selector.
This means that every time a simple mapStateToProps function is called a new selector will be made and the result of the selector will be computed again.
This means that you will be losing the key benefit of reselect that is memoization.
So for simple cases you could just do the following:
const getSomePieceOfState = state => state.someArea.someDetail;
const getAnotherPieceOfState = state => state.anotherArea.anotherItem;
const getSomeCombinedState = createSelector(
getSomePieceOfState,
getAnotherPieceOfState,
(somePiece, anotherPiece) => somePiece + anotherPiece
);
const mapStateToProps = state => ({
someProp: getSomeCombinedState(state)
});
Note. it's common to prefix the name of selectors with get and to prefix the name of a selector factory (a function that returns a selector) with makeGet.
Some times creating a selector factory is necessary though if you wan't to make a selector that is dependant on a property that is not in the state.
You can read more about that here Accessing React Props in Selectors
You're doing it absolutely right in the second example. This is a standard practice.
There's no need to do wrap makeSelectError to another function.
I'm having some trouble making use of the Config.skip property inside of my graphql() wrapper.
The intent is for the query to be fired with an argument of currentGoalID, only after a user has selected an item from the drop-down (passing the associated currentGoalID) , and the (Redux) state has been updated with a value for currentGoalID.
Otherwise, I expect (as per Apollo documentation) that:
... your child component doesn’t get a data prop at all, and the options or props methods are not called.
In this case though, it seems that my skip property is being ignored based upon the absence of a value for currentGoalID, and the option is being called because the webpack compiler/linter throws on line 51, props is not defined...
I successfully console.log the value of currentGoalID without the graphql()
wrapper. Any idea why config.skip isn't working? Also wish to be advised on the proper use of this in graphql() function call. I've excluded it here, but am unsure of the context, thanks.
class CurrentGoal extends Component {
constructor(props) {
super(props)
}
render (){
console.log(this.props.currentGoalID);
return( <p>Current Goal: {null}</p>
)
}
}
const mapStateToProps = (state, props) => {
return {
currentGoal: state.goals.currentGoal,
currentGoalID: state.goals.currentGoalID,
currentGoalSteps: state.goals.currentGoalSteps
}
}
const FetchGoalDocByID = gql `
query root($varID:String) {
goalDocsByID(id:$varID) {
goal
}
}`;
const CurrentGoalWithState = connect(mapStateToProps)(CurrentGoal);
const CurrentGoalWithData = graphql(FetchGoalDocByID, {
skip: (props) => !props.currentGoalID,
options: {variables: {varID: props.currentGoalID}}
})(CurrentGoalWithState);
// export default CurrentGoalWithState
export default CurrentGoalWithData
See the answer here: https://stackoverflow.com/a/47943253/763231
connect must be the last decorator executed, after graphql, in order for graphql to include the props from Redux.
Currently my code works, but only for the forms specified specifically in the combine reducer function. But, I would like to have my code work generally for all forms loaded in my single page app.
Here is the relevant code:
import { reducer as formReducer } from 'redux-form';
export default combineReducers({
someReducer,
anotherReducer,
form: formReducer.plugin({
specificFormId: (state, action) => { // <-- I don't want this only for specificFormId, I want this to happen for all my forms,
// or at least have a dynamic way of adding more forms
const {type, payload} = action;
switch(type) {
case 'RESET_LINK_TYPE_FIELDS': {
return {
...state,
registeredFields: {
...state.registeredFields,
// Do some custom restting here based on payload
}
};
}
default:
return state;
}
}
})
});
So, anytime my <Field ..of a certain type/> fires off this the RESET_LINK_TYPE_FIELDS action, I want the correct form to respond to it.
In the action payload, I can specifically the form identifier or anything else I would need to make this work.
In fact, if the .plugin let me do my own form state slicing, I could easily do this, but because it forces me to pass an object, with a hardcoded form identifier it doesn't work.
Is there a way to have the plugin give me the WHOLE form state, and then I will slice as needed, and return state as needed based on payload?
There is currently no way to do this with the existing API.
You could jury rig a solution by wrapping the redux-form reducer in your own thing.
export default combineReducers({
someReducer,
anotherReducer,
form: resetHack(formReducer)
})
function resetHack(formReducer) {
return (state, action) => {
if(action.RESET_LINK_TYPE_FIELDS) {
// manipulate slice somehow
} else {
return formReducer(state, action)
}
}
}
Is it anyway possible to submit also the empty values on a form? If not how do you properly initialize the form from state?
If it is not possible do I really need to do aField: this.props.data.aField || '' for every field I want to initialize? This seems like a lot of typing and repeating especially on forms which have FormSections and nesting.
If it would be possible I could just do something in the lines of this.
handleInit() {
const { patient, initialize } = this.props;
initialize({
patient.aField,
// Other fields
});
}
Not sure if this is applicable to your scenario, but you can specify initial form values in the mapStateToProps phase:
const mapStateToProps = state => {
return {
initialValues: { ...state.patient } // Use this property to set your initial data
};
}
This is also explained here: http://redux-form.com/6.6.1/examples/initializeFromState/