1

I am trying to get my Redux store fields to automatically populate a method I have imported. Am I going about this the right way in order to get this done? Do I need to create a mapping options for each field?

I have each of my dropdowns inserted with a PopulateDropdown list and the fields in each of them but I need them split as per the id and text.

Am I accessing my redux store correctly below? I have the array declared on up my function component by using const fields = useSelector(state => state.fields);

Update I have the method inserted into where the dropdowns should be however I don't think I am accessing the data correctly which is causing the problem. The fields array has been de-structured into the six different fields for each dropdown and different mappingOptions have been created for each one.

What do I need to do to get the data into the method? the examples I have seen have static arrays declared on the component rather than use the Redux store.

const fields = useSelector(state => state.fields);
// can destructure individual fields
const { diveSchoolList, currentList, regionList, diveTypeList, visibilityList, diveSpotList } = fields;

populateDropdown method that I have imported

export const PopulateDropdown = ({ dataList = [], mappingOptions, name, label }) => {

const { title, value } = mappingOptions;

return (
<FormControl style={{ width: 200 }} >
  <InputLabel id={label}>{label}</InputLabel>
  <Select labelId={label} name={name} > 
  {dataList.map((item) => (
  <MenuItem value={item[value]}>{item[title]}</MenuItem>
  ))}
  </Select>
</FormControl>
);
};

imported dropdown menu

       <PopulateDropdown
           dataList={diveType}
           mappingOptions={mappingOptions}
           name="fieldName"
           label="Select dive type"
           value={dive.typeID}
           onChange={handleChange}/>

enter image description here

Update

I have updated my action, reducer and populateFields method however I am still having trouble mapping the redux data to my two property fields. In the Redux tree the fields should be under the fields.data.fieldlists as they print when I console log them.

What way should I be populating them into the titleProperty etc? It is currently looking like it might be populating but a large box drops downs that I can't see any values inside.

// select user object from redux
        const user = useSelector(state => state.user);

        // get the object with all the fields
        const fields = useSelector(state => state.fields);

        // can destructure individual fields
        const { diveSchoolList = [],
                currentList = [],
                regionList = [],
                diveTypeList = [],
                visibilityList = [],
                diveSpotList = [],
                marineTypeList = [],
                articleTypeList = []
        } = fields;

 .........


<PopulateDropdown
   dataList={fields.data.diveTypeList} // the options array
   titleProperty={fields.data.diveTypeList.diveTypeID} // option label property
   valueProperty={fields.data.diveTypeList.diveType} // option value property
   label="Dive Type Name" // label above the select
   placeholder="Select dive type" // text show when empty
   value={dive.typeID} // get value from state
   onChange={handleChange(setDive.typeID)} // update state on change
/>
James Gee
  • 129
  • 1
  • 3
  • 24
  • You need to use `useSelector` from redux-toolkit or `connect` function from react-redux to inject the redux data into your component. Have you done that properly? – Ajeet Shah Mar 20 '21 at 20:31
  • I recommend you use the redux hooks to accomplish this. as previously said use the useSelector to get data and the useDispatch to dispatch actions. are you sure you did all that right? – Michael Mar 20 '21 at 21:06
  • this answer will help: https://stackoverflow.com/questions/66648507/using-redux-store-data-to-populate-dropdown-fields-in-react/66650274#66650274 – Amir Achhodi Mar 21 '21 at 07:04
  • Hey Jimbo, [I helped you](https://stackoverflow.com/a/66416120/10431574) set up the redux store a few weeks ago. How is it looking now? If you could share a Github repo or a CodeSandbox it would help me better understand what the data structures are like. I definitely think we can create a generalized component that will work for all 6 lists with just a few props for configuration. – Linda Paiste Mar 24 '21 at 00:37
  • I think it might make you data easier to work with if every list had the same properties like `id` and `label`, but I don't know if that's feasible. – Linda Paiste Mar 24 '21 at 00:39
  • Hi Linda, Yes you've been a great help. :) My app is really big now so it is too big to put it all on codesandbox. Is the attached link ok or do you want access to my github repo? https://codesandbox.io/s/shy-rgb-je0je?file=/src/Components/DiveLogForm.component.js – James Gee Mar 24 '21 at 00:56
  • And yes I have all my data showing in the store ok as i can see it in my Redux dev tools. :) I am starting to get the hang of it, it's a good bit to get used too. – James Gee Mar 24 '21 at 01:00
  • 1
    It looks like what you have is mostly correct! I don't think you actually need to pass a `name` property to the select. Also instead of creating a `mappingOptions` object I would have the `PopulateDropdown` component take two separate properties `valueProperty` and `titleProperty`. I can write you an answer but it's not as in-depth as the last one because you're getting the hang of this! I see you are fetching all field data in a single API call which is great. – Linda Paiste Mar 24 '21 at 01:10
  • Anything would be a good help. Yes I am fine with the backend part, just need to brush up on my front-end skills. – James Gee Mar 24 '21 at 01:24

1 Answers1

1

Your PopulateDropdown component looks correct except that we need it to use the value and onChange that we passed down as props.

My personal preference would be to use separate properties valueProperty and titleProperty instead of passing a single mappingOptions. That way you don't need to create objects for every dropdown, you just set the two properties in your JSX. You could get rid of this part entirely if you normalized your data such that the elements of every list have the same properties id and label.

<PopulateDropdown
  dataList={diveTypeList} // the options array
  titleProperty={"diveTypeId"} // option label property
  valueProperty={"diveType"} // option value property
  label="Dive Type Name" // label above the select
  placeholder="Select dive type" // text show when empty
  value={dive.typeID} // get value from state
  onChange={handleChange("typeId")} // update state on change
/>
export const PopulateDropdown = ({
  dataList = [],
  valueProperty,
  titleProperty,
  label,
  ...rest // can just pass through all other props to the Select
}: Props) => {
  return (
    <FormControl style={{ width: 200 }}>
      <InputLabel id={label}>{label}</InputLabel>
      <Select {...rest} labelId={label}>
        {dataList.map((item) => (
          <MenuItem value={item[valueProperty]}>{item[titleProperty]}</MenuItem>
        ))}
      </Select>
    </FormControl>
  );
};

It looks like the ids in currentId are actually a number, so at some point in your code you will want to convert that with parseInt because e.target.value is always a string, though maybe the backend can handle that.


Loading the API Data

It looks like you figured out how to fetch all of the fields in one API call which is great. You are saving it to a property fields on the fields reducer which creates the structure state.fields.fields. Since you are replacing the whole state, you can just return the whole thing as the entire slice state.

You can initialize your state object with empty arrays, or you can use an empty object {} as your initial state and fallback to an empty array when you destructure the arrays off of it, like const {diveSchoolList = [], currentList = []} = fields.

export const requireFieldData = createAsyncThunk(
  "fields/requireData", // action name
  // don't need any argument because we are now fetching all fields
  async () => {
    const response = await diveLogFields();
    return response.data;
  },
  // only fetch when needed: https://redux-toolkit.js.org/api/createAsyncThunk#canceling-before-execution
  {
    // _ denotes variables that aren't used -  the first argument is the args of the action creator
    condition: (_, { getState }) => {
      const { fields } = getState(); // returns redux state
      // check if there is already data by looking at the didLoadData property
      if (fields.didLoadData) {
        // return false to cancel execution
        return false;
      }
    }
  }
);
const fieldsSlice = createSlice({
  name: "fields",
  initialState: {
    currentList: [],
    regionList: [],
    diveTypeList: [],
    visibilityList: [],
    diveSpotList: [],
    diveSchoolList: [],
    marineTypeList: [],
    articleTypeList: [],
    didLoadData: false,
  },
  reducers: {},
  extraReducers: {
    // picks up the pending action from the thunk
    [requireFieldData.pending.type]: (state) => {
      // set didLoadData to prevent unnecessary re-fetching
      state.didLoadData = true;
    },
    // picks up the success action from the thunk
    [requireFieldData.fulfilled.type]: (state, action) => {
      // want to replace all lists, there are multiple ways to do this
      // I am returning a new state which overrides any properties
      return {
        ...state,
        ...action.payload
      }
    }
  }
});

So in the component we now only need to call one action instead of looping through the fields.

useEffect(() => {
    dispatch(requireFieldData());
}, []);
Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • If the drop downs look they are populating however I can't actually read the values as it is just one big white box drops down, what does it sound like the error is related to? – James Gee Mar 25 '21 at 00:01
  • Sounds like we are getting a `MenuItem` for each option but that we are not extracting the right label ( which would be the `children` prop on the `MenuItem`). – Linda Paiste Mar 25 '21 at 00:04
  • This part: `{item[titleProperty]}` – Linda Paiste Mar 25 '21 at 00:05
  • What way should I be populating them in this case? It looked like it might be populating them with the likes of valueProp={fields.data.diveTypeList.diveType) however I am getting errors when the page re-renders like "fields.data is undefined" and my store doesn't populate. – James Gee Mar 25 '21 at 00:18
  • I thought I could just enter the dataList name as it is de-structured at the top then the specific row name. – James Gee Mar 25 '21 at 00:19
  • I am able to access my non Redux user field no problem with "props.user.userID" however this won't work the same for fields. – James Gee Mar 25 '21 at 00:20
  • I'm not sure if I'm understanding your comment, but `valueProperty` and `titleProperty` are strings which determine what property name to look for on the object. `"fields.data is undefined"` sounds like an issue higher up. I know I made some changes because you had `state.fields.fields` and I made it just be `state.fields.` Sounds like an issue in `requireFieldData`? – Linda Paiste Mar 25 '21 at 00:25
  • add a property to `extraReducers` that handles errors from the thunk and see what it is picking up. – Linda Paiste Mar 25 '21 at 00:25
  • I don't mind having to access it by doing fields.data.fieldList as long as it works ok. If the mapStateToProps at the bottom of my component still has const { fields } = state.fields? is this correct or should it be const {fields} = state.fields.data? – James Gee Mar 25 '21 at 00:45
  • The extra 'data' part of my fields tree seems to come from the response .data of Thunk. – James Gee Mar 25 '21 at 00:46
  • Got it at last. The strings part was the problem! :) Thanks for your help. – James Gee Mar 25 '21 at 00:55