11

I have a ReactJS app with filters and use a RESET function for resetting those filters.

What I also use: Redux, Redux Persist and React-router-dom.

If I have a look at the Redux Devtools, it seems to work. But the App does not rerender correctly, a refresh (f5) is necessary.

What I want to achieve: override the configuredFilters object with the initialState object.

This is my root reducer:

const rootReducer = (state = initialState, action) => {
  let newState = state;
  if (action.type === 'RESET_FILTERS') {
    storage.removeItem('persist:configuredFilters');
    // eslint-disable-next-line no-param-reassign
    newState = { ...newState,
      configuredFilters: JSON.parse(JSON.stringify(initialState.configuredFilters))
    };
  }
  return appReducer(newState, action);
};

Here is the diff (I configured two countries before):

Diff after the RESET action has been called

Here is the object (initial status if the page is loaded):

The redux object configuredFilters

The components are created with this component:

/* eslint-disable max-len */
import React from 'react';
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
import { Form, Select } from 'antd';
import PropTypes from 'prop-types';
import RenderTag from './RenderTag';
import * as Action from '../../store/configuredFilters/actions';

const propTypes = {
  data: PropTypes.shape({
    name: PropTypes.string.isRequired,
    label: PropTypes.string,
    placeholder: PropTypes.string,
    options: PropTypes.arrayOf(PropTypes.shape({})),
  }).isRequired,
};


const { Option } = Select;


const useData = () => {
  const dataFromRdx = useSelector(
    (state) => ({
      configuredFilters: state.configuredFilters,
    }),
    shallowEqual
  );
  return { dataFromRdx };
};

const FixedListSelect = ({
  data: {
    name, label, placeholder, options,
  },
}) => {
  const { dataFromRdx } = useData();

  const {
    configuredFilters: {
      data: {
        search: searchTerm,
      },
    },
  } = dataFromRdx;


  const dispatch = useDispatch();
  const dispatchFns = {
    setConfiguredFilters: (key, value) => {
      dispatch(Action.setConfiguredFilters(key, value));
    },
  };

  const setRdxVal = (id, currVal) => {
    dispatchFns.setConfiguredFilters(id, currVal);
  };

  const isFullTextSearchMode = (searchTerm && searchTerm.length);
  return (
    <Form.Item
      name={name}
      label={label}
      fieldKey={name}
    >
      <Select
        allowClear
        disabled={isFullTextSearchMode}
        showSearch
        tagRender={RenderTag}
        mode="multiple"
        placeholder={placeholder}
        optionFilterProp="children"
        filterOption={(input, option) => option.children.toLowerCase().includes(input.toLowerCase())}
        onChange={(currVal) => { setRdxVal(name, currVal); }}
      >
        {(options || []).map((el) => <Option data-filterid={el.val} key={el.val} value={el.val}>{el.label}</Option>)}
      </Select>
    </Form.Item>
  );
};
FixedListSelect.propTypes = propTypes;
export default FixedListSelect;

A call of this component:

<FixedListSelect data={{
          name: 'companies',
          label: t('companies'),
          placeholder: t('companies-placeholder'),
          options: companies,
        }}
        />

Can someone help or at least give a hint?

Gutelaunetyp
  • 2,144
  • 4
  • 15
  • 40
  • 1
    A reducer wont trigger a new render of your component, it will just take care of updating the state. Can you show us the component that should re-render? – Florian May 26 '20 at 09:59
  • @Florian yes, I added the component. Please see my edited post above. – Gutelaunetyp May 26 '20 at 10:21
  • 1
    can you show appReducer too – Shubham Khatri Jun 02 '20 at 12:11
  • 1
    This may be totally unconnected to the actual problem - but your `rootReducer` is not a pure function (it mutates an external variable called `storage` - I guess this is local storage?), and this is something you should never do. A reducer should take an action and the current state and returns the new state, with no side effects. – Robin Zigmond Jun 02 '20 at 20:36
  • 1
    You seem to be selecting an object with a deep state from the store. the second argument to useSelector is deepEqual be default, have you tried not replacing it with shallowEqual? `state.configuredFilters` is shallow `state.configuredFilters.data.contries` for example is deep and therefore won't cause a rerender unless you use deepEqual. – Arye Eidelman Jun 05 '20 at 18:43
  • As much as I know there is no way to do this in this way. but what you can do is make another reducer function which will re-initialize the state. and call it whenever you need. – Adesh Kumar Jun 08 '20 at 04:06
  • 1
    You are most def mutating state in your reducer. That causes your app not to respond to state updates – user0101 Jun 08 '20 at 05:42
  • If possible please create a reproducible codesandbox of your issue. The scenario that you suggest shouldn't happen with the code that you show – Shubham Khatri Jun 09 '20 at 08:48
  • Why are you returning the `appReducer` call in the `rootReducer`? – k.s. Jun 09 '20 at 08:52
  • This is some real *what on earth are you doing* code. Can you include the code of `appReducer`?. Calling one reducer inside another is weird but not inherently a violation. Altering `storage` from a reducer is a side effect so that is not allowed. Deep cloning the `initialState.configuredFilters` is just weird. Why not return `{...state, configuredFilters: initialState.configuredFilters}`? Trying to override the behavior of redux persist from inside a reducer rather than through the redux persist setup feels wrong. – Linda Paiste Feb 10 '21 at 19:14

2 Answers2

2

In ReactJS you can not mutate the state directly. It is the same in Redux. You have to copy the existing info. Redux compares the current tree with new information if founds the difference then it updates the store and renders the new result. If mutating directly you will always refresh to have it working.

More info

Immutable Update Patterns

Niyongabo Eric
  • 1,333
  • 18
  • 21
0

Please clone last data. JSON.parse(JSON.stringify(data));

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 24 '22 at 07:35