import{createStore,combineReducers}from"redux";import{Provider}from"react-redux";importRecipesNavigatorfrom"./navigation/RecipesNavigator";importrecipesReducerfrom"./store/reducers/recipes";// Combined all reducers into the `rootReducer`constrootReducer=combineReducers({recipes: recipesReducer,});conststore=createStore(rootReducer);exportdefaultfunctionApp(){// more codereturn(// wrapping the `Provider` component around our root component<Providerstore={store}><RecipesNavigator/></Provider>);}
149. Selecting State Slices by Using useSelector()
useSelector allows you to extract data from the Redux store state, using a selector function.
Project Code:
screens/CategoryRecipesScreen.js
import{useSelector}from"react-redux";constCategoryRecipesScreen=(props)=>{constavailableRecipes=useSelector((state)=>state.recipes.filteredRecipes);constdisplayedRecipes=availableRecipes.filter((recipe)=>recipe.categoryIds.indexOf(catId)>=0);// more code ..};
Add or remove recipe from the favoriteRecipes list
import{RECIPES}from"../../data/dummy-data";import{TOGGLE_FAVORITE}from"../actions/recipes";constinitialState={recipes: RECIPES,filteredRecipes: RECIPES,favoriteRecipes: [],};constrecipesReducer=(state=initialState,action)=>{switch(action.type){caseTOGGLE_FAVORITE:
constexistingIndex=state.favoriteRecipes.findIndex((recipe)=>recipe.id===action.recipeId);if(existingIndex>=0){// remove if found recipe in the favorite list// copied the favoriteRecipesconstupdatedFavRecipes=[...state.favoriteRecipes];// removed the recipeupdatedFavRecipes.splice(existingIndex,1);return{
...state,favoriteRecipes: updatedFavRecipes,};}else{// add if not found in the favorite list// search for the `recipe` objectconstrecipe=state.recipes.find((recipe)=>recipe.id===action.recipeId);return{
...state,// add the `recipe` object to the listfavoriteRecipes: state.favoriteRecipes.concat(recipe),};}break;default:
// use case: first reloaded the appreturnstate;}};exportdefaultrecipesReducer;
screens/RecipeDetailScreen.js
Dispatching the toggleFavorite action
import{useSelector,useDispatch}from"react-redux";import{toggleFavorite}from"../store/actions/recipes";constRecipeDetailScreen=(props)=>{constrecipeId=props.navigation.getParam("recipeId");constavailabeRecipes=useSelector((state)=>state.recipes.recipes);constselectedRecipe=availabeRecipes.find((recipe)=>recipe.id===recipeId);constdispatch=useDispatch();// using `useCallback()` function to avoid a infinite loopconsttoggleFavoriteHandler=useCallback(()=>{dispatch(toggleFavorite(recipeId));},[dispatch,recipeId]);// Passing the `toggleFavoriteHandler()` function to the navigation header through the `setParams()`useEffect(()=>{props.navigation.setParams({toggleFav: toggleFavoriteHandler});},[toggleFavoriteHandler]);return(<ScrollView>
// more code to display recipe detail
</ScrollView>);};RecipeDetailScreen.navigationOptions=(navigationData)=>{constrecipeTitle=navigationData.navigation.getParam("recipeTitle");consttoggleFavorite=navigationData.navigation.getParam("toggleFav");return{headerTitle: recipeTitle,headerRight: ()=>(<HeaderButtonsHeaderButtonComponent={HeaderButton}><Itemtitle="Favorite"iconName="ios-star"onPress={toggleFavorite}/></HeaderButtons>),};};
152. Switching the Favorite Icon (solid / outline)
Project Code:
screen/RecipeDetailScreen.js
constRecipeDetailScreen=(props)=>{constrecipeId=props.navigation.getParam("recipeId");// Check if the displayed recipe item is in the favorite recipe listconstcurrentRecipeIsFavorite=useSelector((state)=>state.recipes.favoriteRecipes.some((recipe)=>recipe.id===recipeId));// Update navigation params when the `isFav` property changesuseEffect(()=>{props.navigation.setParams({isFav: currentRecipeIsFavorite});},[currentRecipeIsFavorite]);};RecipeDetailScreen.navigationOptions=(navigationData)=>{// loaded the `isFav` prop from the navigation paramconstisFavorite=navigationData.navigation.getParam("isFav");return{headerTitle: recipeTitle,headerRight: ()=>(<HeaderButtonsHeaderButtonComponent={HeaderButton}><Itemtitle="Favorite"// update icon from solid <=> ouline, depending on the `isFavorite` propertyiconName={isFavorite ? "ios-star" : "ios-star-outline"}onPress={toggleFavorite}/></HeaderButtons>),};};
components/RecipeList.js
import{useSelector}from"react-redux";constRecipeList=(props)=>{// pulling `favoriteRecipes` list from the central store.constfavoriteRecipes=useSelector((state)=>state.recipes.favoriteRecipes);constrenderRecipeItem=(itemData)=>{// check if the curerent recipe item is in the `favoriteRecipes` listconstisFavorite=favoriteRecipes.find((recipe)=>recipe.id===itemData.item.id);return(<RecipeItem// more code...onSelectRecipe={()=>{props.navigation.navigate({routeName: "RecipeDetail",params: {recipeId: itemData.item.id,recipeTitle: itemData.item.title,// passing this prop through the navigation param to make sure that// the star gets painted right away when the RecipeDeatilScreen is loaded.isFav: isFavorite,},});}}/>);};return(<Viewstyle={styles.recipeList}><FlatListkeyExtractor={(item,index)=>item.id}data={props.recipeListData}renderItem={renderRecipeItem}style={{width: "100%"}}/></View>);};
153. Rendering a fallback text (for an empty favorite list)
Project Code:
screen/FavoritesScreen.js
import{View,StyleSheet}from"react-native";importDefaultTextfrom"../components/DefaultText";constFavoritesScreen=(props)=>{constfavRecipe=useSelector((state)=>state.recipes.favoriteRecipes);// rendering a fallback text when the `favRecipe` list is emptyif(favRecipe.length===0||!favRecipe){return(<Viewstyle={styles.emptyFav}><DefaultText>No favorite recipes found. Start adding some!</DefaultText></View>);}return(<RecipeListrecipeListData={favRecipe}navigation={props.navigation}/>);};
import{useDispatch}from"react-redux";import{setFilters}from"../store/actions/recipes";constFiltersScreen=(props)=>{const{ navigation }=props;constdispatch=useDispatch();constsaveFilters=useCallback(()=>{constappliedFilters={glutenFree: isGluetenFree,lactoseFree: isLactoseFree,vegan: isVegan,vegetarian: isVegetarian,};// dispatching the setFilters action from the central storedispatch(setFilters(appliedFilters));// kty: redirect to the category screennavigation.navigate("Categories");},[dispatch,isGluetenFree,isLactoseFree,isVegan,isVegetarian]);useEffect(()=>{navigation.setParams({save: saveFilters});},[saveFilters]);};
screen/CategoryRecipesScreen.js
constCategoryRecipesScreen=(props)=>{// rendering a fallback text when displayedRecipes list is emptyif(displayedRecipes.length===0){return(<Viewstyle={styles.emptyRecipeList}><DefaultText>No recipes found. Maybe, check your filters?</DefaultText></View>);}};