Skip to content
Advertisement

Tracking redux state

I faced a problem when using redux. I have two arrays with objects in it, initially they are empty. One of these arrays contains objects that are marked as ‘favorite’ – by user.

const INITIAL_STATE = {
  favorites: [],
  recipes: [],
};

My reducer for adding item:

const reducer = (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case "TOGGLE_FAVORITES":
      if (!state.favorites.includes(action.payload)) {
        return { ...state, favorites: [...state.favorites, action.payload] };
      }
      return {
        ...state,
        favorites: state.favorites.filter(
          (recipe) => recipe.id !== action.payload.id
        ),
      };
    case "REMOVE_FROM_FAVORITES":
      return {
        ...state,
        favorites: state.favorites.filter(
          (recipe) => recipe.id !== action.payload
        ),
      };
    case "GET_RECIPES":
      return {
        ...state,
        recipes: action.payload,
      };
    default:
      return state;
  }
};

I have this ‘toggle’ type for the same button if user decides to add and then remove from favorites.

The problem is When i add item to array it doesn’t update right away. I mean, on first ‘add’ click my favorites array is empty, but when i add another item it shows that there is one item inside, and so on and so forth.

Is there a way to update state whenever i add something to show me really current state?

Checking if favorite like this:

const [isFavorite, setIsFavorite] = useState(false);
  // Checking if recipe is favorite when rendered
  useEffect(() => {
    const checkIfIsFavorite = (id) => {
      for (let i = 0; favorites.length > 1; i++) {
        if (id === favorites[i].id) {
          return true;
        }
        return false;
      }
    };
    setIsFavorite(checkIfIsFavorite(recipe.id));
  }, [favorites, recipe.id]);

Advertisement

Answer

The following is confusing:

const [isFavorite, setIsFavorite] = useState(false);
  // Checking if recipe is favorite when rendered
  useEffect(() => {
    const checkIfIsFavorite = (id) => {
      for (let i = 0; favorites.length > 1; i++) {
        if (id === favorites[i].id) {
          return true;
        }
        return false;
      }
    };
    setIsFavorite(checkIfIsFavorite(recipe.id));
  }, [favorites, recipe.id]);

It doesn’t show what favorites is and where it’s defined, I don’t see code anywhere that dispatches TOGGLE_FAVORITES but could assume your reducer is executed.

If you have an array of ids that are favorites and an id or an item and want to know if the item is a favorite item than you can calculate/derive this. Derived data is commonly calculated in selectors not in the component.

An example of using a selector to get the favorite information of an item is provided below.

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector } = Reselect;

const initialState = {
  favorites: [],
  recipes: [{ id: 1 }, { id: 2 }, { id: 3 }],
};
//action types
const TOGGLE_FAVORITES = 'TOGGLE_FAVORITES';
//action creators
const toggleFavorite = (id) => ({
  type: TOGGLE_FAVORITES,
  payload: id,
});
const reducer = (state, { type, payload }) => {
  if (type === 'TOGGLE_FAVORITES') {
    if (!state.favorites.includes(payload)) {
      return {
        ...state,
        favorites: [...state.favorites, payload],
      };
    }
    return {
      ...state,
      favorites: state.favorites.filter(
        (id) => id !== payload
      ),
    };
  }
  return state;
};
//selectors
const selectRecipes = (state) => state.recipes;
const selectFavorites = (state) => state.favorites;
const createSelectIsFavorite = (id) =>
  createSelector([selectFavorites], (favorites) =>
    favorites.includes(id)
  );
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(
      () => (next) => (action) => next(action)
    )
  )
);
const Recipe = React.memo(function Recipe({ recipe }) {
  const { id } = recipe;
  const selectIsFavorite = React.useMemo(
    () => createSelectIsFavorite(id),
    [id]
  );
  const isFavorite = useSelector(selectIsFavorite);
  const dispatch = useDispatch();
  const toggle = () => dispatch(toggleFavorite(recipe.id));
  return (
    <li>
      {recipe.id} {String(isFavorite)}
      <button onClick={toggle}>toggle favorite</button>
    </li>
  );
});
const App = () => {
  const recipes = useSelector(selectRecipes);
  return (
    <ul>
      {recipes.map((recipe) => (
        <Recipe key={recipe.id} recipe={recipe} />
      ))}
    </ul>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>

<div id="root"></div>

In future questions I would advise you to provide the actions that are dispatched and the changes they make to the state (redux devtools or log in reducer). Other information that is crucial is how you select the information from redux state. This way you can pinpoint to where it “doesn’t work”, is setting the state not working or getting the information? It would be easier to post relevant code this way.

If you’re really stuck then you could try to create a minimal project that demonstrates the problem and share a code sandbox, you can even create a sandbox from a github repo

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement