Using args or props to derive data from Redux using selectors

666 Views Asked by At

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

2

There are 2 best solutions below

0
jansyb04 On BEST ANSWER

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));
2
markerikson On

Yeah, your createSelector call is wrong.

If you want your "output function" to take 3 arguments, then you need to write 3 separate "input functions". Each input function should extract and return a value, and those become the arguments for the output function.

So, you need something like this:

export const getCustomViewData = createSelector(
  getCustomView,
  // these should also read values from `state` or any other args
  getId,
  getCustomViewType,
  (customView, id, customViewType) => {
    // return calculation results here
  }
);

See the Redux docs Deriving Data with Selectors usage guide page for more details on how to use Reselect.