javascriptreactjsreduxreselect

Using args or props to derive data from Redux using selectors


TL;DR: I'm trying to use an id and type parameters in my selectors but the params are undefined. What is the correct way of doing this using reselect and createStructuredSelector?

I'm writing selectors using reselect to get the data needed for a React component. The data is stored in a key/value format where the key is made up of a dynamic id and type value. The object looks like this:

customView: {
    'viewBy:deviceProfileId&id:5923f82a-80c2-4c88-bd0e-c105ad989ab2': { // type = 'deviceProfileId' & id = '5923f82a-80c2-4c88-bd0e-c105ad989ab2'
        list: { ... },
        meta: { ... }, 
        error: { ... }
     }

Users enter the id and type and trigger the data fetching from API. I have the basic selector but I'm stuck trying to get the data needed for each entry using these dynamic values - they continue to come out undefined. What am I missing? Is there a better way to accomplish this?

// reducer.js : Fn to generate key
export const customViewKey = ({ customViewType, id } = {}) => {
  const typeKey = (customViewType && `viewBy:${customViewType}`) || '';
  const idKey = (id && `id:${id}`) || '';
  const namespace = `${typeKey}&${idKey}`;
  return `${namespace}`;
};

// selector.js
const getCustomView = ({ customView }) => customView; // Basic selector works

export const getCustomViewData = createSelector(
  getCustomView,
  (customView, id, customViewType) => { // id and customViewType are undefined
    return customView && customView[customViewKey({ customViewType, id })];
  }
);

// index.js 
export const CustomViewsPage = ({ classes, getCustomView, customViewMap, customViewType, id }) => {  
  const [idValue, setIdValue] = useState('');
  const [type, setType] = useState('');

  const handleTypeChange = (e) => {
    let typeEntered = e ? e.value : '';
    setType(typeEntered);
  };

  const handleInputChange = (e) => {
    setIdValue(e.target.value);
  };
  const handleSubmit = (e) => { // Fn called when user hits submit 
    e.preventDefault();
    getCustomView({ id: idValue, customViewType: type });
  };

return (
    <div>
            <form onSubmit={handleSubmit} className={classes.customViewForm}>
                <div>
                  <p>Select type of custom view:</p>
                  <Select
                    placeholder="View messages by"
                    options={customViewTypes}
                    onChange={handleTypeChange}
                    isClearable
                  />
                </div>
                <div>
                  <p>Enter ID for device, project or device profile:</p>
                  <InputBase
                    placeholder="5923f82a-80c2-4c88-bd0e-c105ad989ab2"
                    required
                    onChange={handleInputChange}
                    fullWidth
                  />
                </div>
                <label htmlFor="create-custom-view-btn">
                  <Input id="create-custom-view-btn" type="submit" />
                  <Button
                    component="span"
                    variant="outlined"
                    color="primary"
                    endIcon={<SendRounded />}
                    onClick={handleSubmit}
                  >
                    Create view
                  </Button>
                </label>
            </form>

</div>

const mapDispatchToProps = {
  getCustomView: requestCustomView,
};

const mapStateToProps = createStructuredSelector({
  customViewMap: getCustomViewMap,
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CustomViewsPage));


Solution

  • I figured out the solution thanks to @markerikson's answer and this blog.

    These are the selectors I'm using:

    const getCustomView = ({ customView }) => customView;
    
    const getId = (_, id) => id;
    
    const getCustomViewType = (_, id, customViewType) => customViewType;
    
    export const getCustomViewData = createSelector(
      getCustomView,
      getId,
      getCustomViewType,
      (customView, id, customViewType) => {
        return customView && customView[customViewKey({ customViewType, id })]; // WORKS!
      }
    );
    
    export const getCustomViewMap = createSelector(getCustomViewData, getDomainMap);
    

    And I'm using useSelector to call the selector from my component this way:

    const selectedCustomView = useSelector(state => getCustomViewMap(state, idValue, type));