1

I have a reducer in my react native app which ideally should check the state and return if the recipe has been favourited or not. I've tried to create a reducer to return if isFav is true or false for a recipe object in the state. If the recipe is not found in the state it should return false.

I've hit a brick wall trying to figure out how to write the reducer. This is what I have so far.

This is my favids.js reducer

import { TOGGLE, IS_FAVOURITE } from '../actions'

export const favids = (state = [], action) => {
    const { type, recipeid } = action

    const newRecipe = {
        recipeid,
        is_fav: true,
    }

    switch (type) {
        case TOGGLE: {
          //   console.log(`Toggle State = ${JSON.stringify(state)}`)
           //  console.log(`Toggle recipe id : ${recipeid}`)
            return state.findIndex(recipe => recipe.recipeid === recipeid) === -1 ?
                [...state, newRecipe] :
                state.map(recipe =>
                    recipe.recipeid === recipeid ?
                        { ...recipe, is_fav: !recipe.is_fav } : recipe
                )
        }
        case IS_FAVOURITE: {
            //console.log(`Is Favourite state =${JSON.stringify(state)}`)
            return state.map(recipe =>
                recipe.recipeid === recipeid ?
                    recipe.is_fav : state
            )
        }
        default:
            return state
    }
}

This is my actions.js

export const TOGGLE = 'TOGGLE'
export const toggleFavId = id => ({
    type: 'TOGGLE',
    recipeid: id
})

export const IS_FAVOURITE = 'IS_FAVOURITE'
export const isFavourite = id => (
    {
        type: 'IS_FAVOURITE',
        recipeid: id
    }
)

I'm trying to modify the is_favourite reducer to return a true or false which is the value of the is_fav property of the recipe object saved down in the state.

I'm trying to call this in the useEffect of the visible component while rendering the component.

This is the favicon.js file

import React, {useState, useEffect} from 'react'
import { FontAwesome } from '@expo/vector-icons'
import { View, TouchableOpacity,StyleSheet, Text, Alert } from 'react-native'

const FavIcon = ({recipeid, onToggle, isFavourite, text}) => {

  const [isFavNew, setFavNew] = useState(false)

  const toggleFav = (recipeid) =>{
    setFavNew(!isFavNew)
    onToggle(recipeid)
  }

  useEffect(() => {
    console.log("Entered useEffect")
    console.log(`Recipeid = ${recipeid}`)

    console.log(`Favicon UseEffect IS Favourtie Recipe id:> ${JSON.stringify(isFavourite(recipeid))}`)
    **// if (recipeid > 0) 
    //   setFavNew((isFavourite(recipeid))
    // else {
    //   setFavNew(false)
    // }**
  },[])
  
 // console.log(`Is Favourite in Favicon is = ${isFavourite(recipeid)}`)

   return (
        <View>
            <TouchableOpacity style={styles.favItem} onPress={() => toggleFav(recipeid)}>
                <FontAwesome name={isFavNew === true ? 'heart' : 'heart-o'} size={40} color='red' />
               <Text> {text}</Text>
            </TouchableOpacity>
        </View>
    )
}

export default FavIcon
Ashley Vaz
  • 55
  • 6

1 Answers1

1

If I understand your issue correctly, it's due to a misunderstanding about Redux. You are looking for a selector, not an action handled by a reducer.

Redux is nothing but a big, fancy object. Reducers modify the object's properties. Actions tell the reducers how to do that. Selectors return those properties to you.

You can remove the TOGGLE action and the case in your reducer with the same name. Then you'd create a selector like the following:

const isRecipeFavorited = (recipeId) => (state) => {
    const recipe = state.find(recipe => recipe.id === recipeId;
    return recipe ? recipe.is_fav : false;
};

A selector is a function that takes your whole state as an argument and returns a subset. Redux passes in your whole state for you when you use useSelector. So a function like this is a "selector factory", where you would invoke it with the recipe ID and it would return the selector for that recipe. You would use it like this:

import { useSelector } from 'react-redux';
import { isRecipeFavorited } from '{selectors path}';

...

const recipeIsFavorited = useSelector(isRecipeFavorited(recipeId));

On a side note, there are a couple of optimizations you could make that will help if you end up with a large amount of information in state. The most important would be to store your recipes in an object instead of an array. I assume you get an array of recipes back from an API. Assuming you can count on unique recipe IDs, you can iterate through the array and store each one in an object:

const recipesState = {};

returnedRecipes.forEach(recipe => {
    recipesState[recipe.id] = recipe;
};

Then, when you're selecting recipes in your reducers, you don't have to iterate through the whole list to get the one you want, or to store the results. You can do these operations in O(1) time because you already know the recipe ID.

Then your IS_FAVOURITE case in your reducer could look something like:

        case TOGGLE: {
            const tempRecipes = { ...state };
            const currentRecipe = tempRecipes[recipeId];

            currentRecipe
                 ? currentRecipe.is_fav = !currentRecipe.is_fav
                 : tempRecipes[recipeId] = newRecipe;

            return tempRecipes;
        }

You could then design the selector I described above like this:

const isRecipeFavorited = (recipeId) => ({ recipe }) => recipe[recipeId]
    ? recipe[recipeId].is_fav
    : false;
Abe
  • 166
  • 1
  • 8
  • thank you very much i've been struggling with this for so long now. – Ashley Vaz Feb 02 '21 at 03:48
  • I tried updating the code as you mentioned but i keep getting an error saying _selectors.isRecipeFavourited is not a function. I also tried using useEffect to fix it but that did not fix the error. `import {isRecipeFavorited} from '../selectors/selectors' import {useSelector} from 'react-redux' const FavIcon = ({recipeid, onToggle, isFavourite, text}) => { const [isFavRecipe, setIsFavRecipe] = useState(false) useEffect((recipeid) => { setIsFavRecipe(useSelector(isRecipeFavorited(recipeid))) },[] ) const isFavNew = useSelector(isRecipeFavorited(recipeid))` – Ashley Vaz Feb 02 '21 at 06:39
  • I made a codepen to show how my recommendation for this. Don't store the isFavourite property in local state -- rely on Redux for state whenever possible. This eliminates the need for a useEffect https://codepen.io/256hz/pen/zYoGMva Also, don't call hooks (like useSelector) anywhere except in the root of a function component. – Abe Feb 02 '21 at 18:53
  • i've reposted this question with more information on the code here. I am still getting the error that _selectors.isRecipeFavourited is not a function. https://stackoverflow.com/questions/66037525/react-useselector-not-working-what-am-i-doing-wrong – Ashley Vaz Feb 04 '21 at 00:02