0

This is my first attempt to refactor code from a class component to a functional component using React hooks. The reason we're refactoring is that the component currently uses the soon-to-be-defunct componentWillReceiveProps lifecylcle method, and we haven't been able to make the other lifecycle methods work the way we want. For background, the original component had the aforementioned cWRP lifecycle method, a handleChange function, was using connect and mapStateToProps, and is linking to a repository of tableau dashboards via the tableau API. I am also breaking the component, which had four distinct features, into their own components. The code I'm having issues with is this:

const Parameter = (props) => {
let viz = useSelector(state => state.fetchDashboard);
const parameterSelect = useSelector(state => state.fetchParameter)
const parameterCurrent = useSelector(state => state.currentParameter)
const dispatch = useDispatch();
let parameterSelections = parameterCurrent;

useEffect(() => {
    let keys1 = Object.keys(parameterCurrent);
    if (
        keys1.length > 0 //if parameters are available for a dashboard
    ) {
        return ({
            parameterSelections: parameterCurrent
        });
    }
}, [props.parameterCurrent])

const handleParameterChange = (event, valKey, index, key) => {
    parameterCurrent[key] = event.target.value;
    console.log(parameterCurrent[key]);
    return (
        prevState => ({
            ...prevState,
            parameterSelections: parameterCurrent
        }),
        () => {
            viz
                .getWorkbook()
                .changeParameterValueAsync(key, valKey)
                .then(function () {
                    Swal.fire({
                        position: "center",
                        icon: "success",
                        title:
                            JSON.stringify(key) + " set to " + JSON.stringify(valKey),
                        font: "1em",
                        showConfirmButton: false,
                        timer: 2500,
                        heightAuto: false,
                        height: "20px"
                    });
                })

                .otherwise(function (err) {
                    alert(
                        Swal.fire({
                            position: "top-end",
                            icon: "error",
                            title: err,
                            showConfirmButton: false,
                            timer: 1500,
                            width: "16rem",
                            height: "5rem"
                        })
                    );
                });
        }
    );
};
const classes = useStyles();
return (
    <div>
        {Object.keys(parameterSelect).map((key, index) => {
            return (
                <div>
                    <FormControl component="fieldset">
                        <FormLabel className={classes.label} component="legend">
                            {key}
                        </FormLabel>
                        {parameterSelect[key].map((valKey, valIndex) => {
                            console.log(parameterSelections[key])
                            return (
                                <RadioGroup
                                    aria-label="parameter"
                                    name="parameter"
                                    value={parameterSelections[key]}
                                    onChange={(e) => dispatch(
                                        handleParameterChange(e, valKey, index, key)
                                    )}
                                >
                                    <FormControlLabel
                                        className={classes.formControlparams}
                                        value={valKey}
                                        control={
                                            <Radio
                                                icon={
                                                    <RadioButtonUncheckedIcon fontSize="small" />
                                                }
                                                className={clsx(
                                                    classes.icon,
                                                    classes.checkedIcon
                                                )}
                                            />
                                        }
                                        label={valKey}
                                    />
                                </RadioGroup>
                            );
                        })}
                    </FormControl>
                    <Divider className={classes.divider} />
                </div>
            );
        })

        }
    </div >
)}; 
export default Parameter;

The classes const is defined separately, and all imports of reducers, etc. have been completed. parameterSelect in the code points to all available parameters, while parameterCurrent points to the default parameters chosen in the dashboard (i.e. what the viz initially loads with).

Two things are happening: 1. Everything loads fine on initial vizualization, and when I click on the Radio Button to change the parameter, I can see it update on the dashboard - however, it's not actually showing the radio button as being selected (it still shows whichever parameter the viz initialized with as being selected). 2. When I click outside of the Filterbar (where this component is imported to), I get Uncaught TypeError: func.apply is not a function. I refactored another component and didn't have this issue, and I can't seem to determine if I coded incorrectly in the useEffect hook, the handleParameterChange function, or somewhere in the return statement. Any help is greatly appreciated by this newbie!!!

1 Answers1

1

This is a lot of code to take in without seeing the original class or having a code sandbox to load up. My initial thought is it might be your useEffect

In your refactored code, you tell your useEffect to only re-run when the props.parameterCurrent changes. However inside the useEffect you don't make use of props.parameterCurrent, you instead make use of parameterCurrent from the local lexical scope. General rule of thumb, any values used in the calculations inside a useEffect should be in the list of re-run dependencies.

useEffect(() => {
    let keys1 = Object.keys(parameterCurrent);
    if (
        keys1.length > 0 //if parameters are available for a dashboard
    ) {
        return ({
            parameterSelections: parameterCurrent
        });
    }
}, [parameterCurrent])

However, this useEffect doesn't seem to do anything, so while its dependency list is incorrect, I don't think it'll solve the problem you are describing.

I would look at your dispatch and selector. Double check that the redux store is being updated as expected, and that the new value is making it from the change callback, to the store, and back down without being lost due to improper nesting, bad key names, etc...

I'd recommend posting a CodeSandbox.io link or the original class for further help debugging.

Spidy
  • 39,723
  • 15
  • 65
  • 83
  • Thank you! I tried the code without the useEffect hook (I thought I needed it to replace the lifecycle method), but IT was actually causing the second error, which was definitely the bigger of the two problems. I would have loved to have posted this and the original on CodeSandbox, but the dashboards can only be viewed within our VPN, so I didn't think it would actually help. When I figure out the radio button solution, I'll update the answer. Thank you again so much Spidy! – Rachel Stevens Mar 14 '20 at 03:36