How to loop through array after data fetching is completed? - reactjs

I am a little new to react, and I am trying to loop through a list of menu items after it has been loaded. I set the sate to empty and put a condition to only start to use the .map() when the state is not empty. But i keep getting TypeError: menu_items.map is not a function.
I know that it gets loaded because when i do a console.log(menu_items) it shows the menu item.
I am little but confused on this one. Any help would be really appreciated.
import {useEffect} from 'react';
import {connect} from 'react-redux'
import {getMenu} from '../service/thunks'
const StoreMenu = ({loadMenu,match,menu_items}) => {
useEffect(()=> loadMenu(match.params.id),[]);
if(menu_items !== "") {
{
menu_items.map(menu_item => console.log(menu_item.item_name))
}
}
return (<div> tesr</div>);
}
const mapStateToProps = state => (
{
menu_items : state.menuItems
}
)
const mapDispatchToProps = dispatch => (
{
loadMenu: (store_id) => dispatch(getMenu(store_id))
}
)
export default connect(mapStateToProps,mapDispatchToProps)(StoreMenu);

Fix the code to:
if (menu_items && menu_items.length > 0) {
menu_items.map(menu_item => console.log(menu_item.item_name))
}
return (
// Create JSX here.
);

Related

Accessing state or props from functional component

i'm trying to accessing variable/state in functional component from other component. i found out i can save the in some global state like redux or local storage. but i don't want to use any of them. i also found out about lifting state up but can't understand about it.
below is an example for my case. let's say i want to generate a lucky number from functional component GenerateLuckyNumber. and i call the component from showLuckyNumber. but i need to save all the list of lucky number that have been generated. i create a hook state to store all lucky number. but i dont know how to retrieve them from GenereateLuckyNumber
any idea how to to it?
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { useState, useEffect } from 'react';
export const GenerateLuckyNumber = () => {
const [luckNo, setluckNo] = useState(1)
const changeCatName = () => {
setluckNo(Math.floor(Math.random() * 100))
}
return (
<>
<TouchableOpacity onPress={() => changeCatName()}>
<Text>Hello, Your Lucky Number is {luckNo}!</Text>
</TouchableOpacity>
</>
);
}
const showLuckyNumber = () => {
const [listLuckyNumber, setlistLuckyNumber] = useState()
//how to save all generated number from functional component to state in this component
return (
<>
<GenerateLuckyNumber/>
<GenerateLuckyNumber/>
<GenerateLuckyNumber/>
</>
);
}
export default showLuckyNumber;
Your component can provide an optional callback function as a prop, which it would invoke whenever it "generates a lucky number". For example:
export const GenerateLuckyNumber = ({onGenerate}) => {
And within the component:
const changeCatName = () => {
const luckyNumber = Math.floor(Math.random() * 100);
setluckNo(luckyNumber);
onGenerate && onGenerate(luckyNumber); // invoke the callback
}
Then any component which renders a <GenerateLuckyNumber/> can optionally supply it with a callback:
const showLuckyNumber = () => {
const [listLuckyNumber, setlistLuckyNumber] = useState()
const handleNewLuckyNumber = (num) => {
// do whatever you like with the newly generated number here
};
return (
<>
<GenerateLuckyNumber onGenerate={handleNewLuckyNumber}/>
<GenerateLuckyNumber onGenerate={handleNewLuckyNumber}/>
<GenerateLuckyNumber onGenerate={handleNewLuckyNumber}/>
</>
);
}

Passing fetched data to context-Provider in react

I am fetching data from a English language dictionary API and creating a front end in react JS. The code works if I render out all components in the Form.js but I am trying to pass returned data to the wordDataContext to create components separately.
import React, {useEffect, useState , useContext } from 'react';
import {UserContext} from '../context/UserContext';
import {WordContext,WordProvider} from '../context/WordContext';
const getWord = async() => {
if (word !== '') {
setWord(word)
try {
const response = await fetch(`https://mydictionaryapi.appspot.com/?define=${word}`)
const data = await response.json()
iterateObject(data)
setWordData(data) // pass to context
console.log('word data', wordData)
} catch(error) {
let message = "Word NOT Found - Check spellings or connection"
setMeaning(message)
console.log(error)
}
}
}
I am then iterating over the returned dictionary object to extract definitions(meanings), synonyms and example sentence etc.
function iterateObject(data) {
for (var item in data) {
if (typeof(data[item]) == "object"){
if (item === "synonyms"){
var synonyms = data[item]
setSynonyms(synonyms)
}
iterateObject(data[item])
}
else
{
if (item === "definition") {
var meaning = data[item]
setMeaning(meaning)
}
if (item === "example"){
var example = data[item]
setExample(example)
}
}
}
}
I can render out components from iteration but I want to pass the dictionary object ( or iterated items ) to a global wordDataContext to make separate components. Here is the code for my wordDataContext and Provider,
import React, {useState, createContext } from 'react';
export const WordContext = createContext([ [{}], () => {}])
export const WordProvider= (props) => {
const [wordData , setWordData] = useState({ word:'' , meaning:'', synonyms:{} , example:'' })
return(
<WordContext.Provider value={[wordData, setWordData]}>
{props.children}
</WordContext.Provider>
)
}
Unfortunately, the returned object is not getting passed to the WordDataContext I have tried doing so after fetching the data in API call function and also when iterating but only get an empty array

How do I update an array using the useContext hook?

I've set a Context, using createContext, and I want it to update an array that will be used in different components. This array will receive the data fetched from an API (via Axios).
Here is the code:
Context.js
import React, { useState } from "react";
const HeroContext = React.createContext({});
const HeroProvider = props => {
const heroInformation = {
heroesContext: [],
feedHeroes: arrayFromAPI => {
setHeroesContext(...arrayFromAPI);
console.log();
}
};
const [heroesContext, setHeroesContext] = useState(heroInformation);
return (
<HeroContext.Provider value={heroesContext}>
{props.children}
</HeroContext.Provider>
);
};
export { HeroContext, HeroProvider };
See above that I created the context, but set nothing? Is it right? I've tried setting the same name for the array and function too (heroesContex and feedHeroes, respectively).
Component.js
import React, { useContext, useEffect } from "react";
import { HeroContext } from "../../context/HeroContext";
import defaultSearch from "../../services/api";
const HeroesList = () => {
const context = useContext(HeroContext);
console.log("Just the context", context);
useEffect(() => {
defaultSearch
.get()
.then(response => context.feedHeroes(response.data.data.results))
.then(console.log("Updated heroesContext: ", context.heroesContext));
}, []);
return (
//will return something
)
In the Component.js, I'm importing the defaultSearch, that is a call to the API that fetches the data I want to push to the array.
If you run the code right now, you'll see that it will console the context of one register in the Just the context. I didn't want it... My intention here was the fetch more registers. I have no idea why it is bringing just one register.
Anyway, doing all of this things I did above, it's not populating the array, and hence I can't use the array data in another component.
Does anyone know how to solve this? Where are my errors?
The issue is that you are declaring a piece of state to store an entire context object, but you are then setting that state equal to a single destructured array.
So you're initializing heroesContext to
const heroInformation = {
heroesContext: [],
feedHeroes: arrayFromAPI => {
setHeroesContext(...arrayFromAPI);
console.log();
}
};
But then replacing it with ...arrayFromAPI.
Also, you are not spreading the array properly. You need to spread it into a new array or else it will return the values separately: setHeroesContext([...arrayFromAPI]);
I would do something like this:
const HeroContext = React.createContext({});
const HeroProvider = props => {
const [heroes, setHeroes] = useState([]);
const heroContext = {
heroesContext: heroes,
feedHeroes: arrayFromAPI => {
setHeroes([...arrayFromAPI]);
}
};
return (
<HeroContext.Provider value={heroContext}>
{props.children}
</HeroContext.Provider>
);
};
export { HeroContext, HeroProvider };

Make localStorage retain its content on page refresh in React

I am trying to add a favorite page to my application, which basically will list some of the previously inserted data. I want the data to be fetched from localStorage. It essentially works, but when I navigate to another page and come back, the localStorage is empty again. I want the data in localStorage to persist when the application is refreshed.
The data is set to localStorage from here
import React, { useState, createContext, useEffect } from 'react'
export const CombinationContext = createContext();
const CombinationContextProvider = (props) => {
let [combination, setCombination] = useState({
baseLayer: '',
condiment: '',
mixing: '',
seasoning: '',
shell: ''
});
const saveCombination = (baseLayer, condiment, mixing, seasoning, shell) => {
setCombination(combination = { baseLayer: baseLayer, condiment: condiment, mixing: mixing, seasoning: seasoning, shell: shell });
}
let [combinationArray, setCombinationArray] = useState([]);
useEffect(() => {
combinationArray.push(combination);
localStorage.setItem('combinations', JSON.stringify(combinationArray));
}, [combination]);
return (
<CombinationContext.Provider value={{combination, saveCombination}}>
{ props.children }
</CombinationContext.Provider>
);
}
export default CombinationContextProvider;
And fetched from here
import React, { useContext, useState } from 'react'
import { NavContext } from '../contexts/NavContext';
const Favorites = () => {
let { toggleNav } = useContext(NavContext);
let [favorites, setFavorites] = useState(localStorage.getItem('combinations'));
console.log(favorites);
return (
<div className="favorites" >
<img className="menu" src={require("../img/tacomenu.png")} onClick={toggleNav} />
<div className="favorites-title">YOUR FAVORITES</div>
<div>{ favorites }</div>
</div>
);
}
export default Favorites;
There are a few issues with your code. This code block:
useEffect(() => {
combinationArray.push(combination);
localStorage.setItem('combinations', JSON.stringify(combinationArray));
}, [combination]);
Will run any time the dependency array [combination] changes, which includes the first render. The problem with this is combination has all empty values on the first render so it is overwriting your local storage item.
Also, combinationArray.push(combination); is not going to cause a rerender because you are just changing a javascript value so react doesn't know state changed. You should use the updater function react gives you, like this:
setCombinationArray(prevArr => [...prevArr, combination])
You should push to your combinationArray and set the result as the new state value and be careful not to overwrite your old local storage values

UseEffect causes infinite loop with swipeable routes

I am using the https://www.npmjs.com/package/react-swipeable-routes library to set up some swipeable views in my React app.
I have a custom context that contains a dynamic list of views that need to be rendered as children of the swipeable router, and I have added two buttons for a 'next' and 'previous' view for desktop users.
Now I am stuck on how to get the next and previous item from the array of modules.
I thought to fix it with a custom context and custom hook, but when using that I am getting stuck in an infinite loop.
My custom hook:
import { useContext } from 'react';
import { RootContext } from '../context/root-context';
const useShow = () => {
const [state, setState] = useContext(RootContext);
const setModules = (modules) => {
setState((currentState) => ({
...currentState,
modules,
}));
};
const setActiveModule = (currentModule) => {
// here is the magic. we get the currentModule, so we know which module is visible on the screen
// with this info, we can determine what the previous and next modules are
const index = state.modules.findIndex((module) => module.id === currentModule.id);
// if we are on first item, then there is no previous
let previous = index - 1;
if (previous < 0) {
previous = 0;
}
// if we are on last item, then there is no next
let next = index + 1;
if (next > state.modules.length - 1) {
next = state.modules.length - 1;
}
// update the state. this will trigger every component listening to the previous and next values
setState((currentState) => ({
...currentState,
previous: state.modules[previous].id,
next: state.modules[next].id,
}));
};
return {
modules: state.modules,
setActiveModule,
setModules,
previous: state.previous,
next: state.next,
};
};
export default useShow;
My custom context:
import React, { useState } from 'react';
export const RootContext = React.createContext([{}, () => {}]);
export default (props) => {
const [state, setState] = useState({});
return (
<RootContext.Provider value={[state, setState]}>
{props.children}
</RootContext.Provider>
);
};
and here the part where it goes wrong, in my Content.js
import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
import SwipeableRoutes from 'react-swipeable-routes';
import useShow from '../../hooks/useShow';
import NavButton from '../NavButton';
// for this demo we just have one single module component
// when we have real data, there will be a VoteModule and CommentModule at least
// there are 2 important object given to the props; module and match
// module comes from us, match comes from swipeable views library
const ModuleComponent = ({ module, match }) => {
// we need this function from the custom hook
const { setActiveModule } = useShow();
// if this view is active (match.type === 'full') then we tell the show hook that
useEffect(() => {
if (match.type === 'full') {
setActiveModule(module);
}
},[match]);
return (
<div style={{ height: 300, backgroundColor: module.title }}>{module.title}</div>
);
};
const Content = () => {
const { modules, previousModule, nextModule } = useShow();
// this is a safety measure, to make sure we don't start rendering stuff when there are no modules yet
if (!modules) {
return <div>Loading...</div>;
}
// this determines which component needs to be rendered for each module
// when we have real data we will switch on module.type or something similar
const getComponentForModule = (module) => {
// this is needed to get both the module and match objects inside the component
// the module object is provided by us and the match object comes from swipeable routes
const ModuleComponentWithProps = (props) => (
<ModuleComponent module={module} {...props} />
);
return ModuleComponentWithProps;
};
// this renders all the modules
// because we return early if there are no modules, we can be sure that here the modules array is always existing
const renderModules = () => (
modules.map((module) => (
<Route
path={`/${module.id}`}
key={module.id}
component={getComponentForModule(module)}
defaultParams={module}
/>
))
);
return (
<div className="content">
<div>
<SwipeableRoutes>
{renderModules()}
</SwipeableRoutes>
<NavButton type="previous" to={previousModule} />
<NavButton type="next" to={nextModule} />
</div>
</div>
);
};
export default Content;
For sake of completion, also my NavButton.js :
import React from 'react';
import { NavLink } from 'react-router-dom';
const NavButton = ({ type, to }) => {
const iconClassName = ['fa'];
if (type === 'next') {
iconClassName.push('fa-arrow-right');
} else {
iconClassName.push('fa-arrow-left');
}
return (
<div className="">
<NavLink className="nav-link-button" to={`/${to}`}>
<i className={iconClassName.join(' ')} />
</NavLink>
</div>
);
};
export default NavButton;
In Content.js there is this part:
// if this view is active (match.type === 'full') then we tell the show hook that
useEffect(() => {
if (match.type === 'full') {
setActiveModule(module);
}
},[match]);
which is causing the infinite loop. If I comment out the setActiveModule call, then the infinite loop is gone, but of course then I also won't have the desired outcome.
I am sure I am doing something wrong in either the usage of useEffect and/or the custom hook I have created, but I just can't figure out what it is.
Any help is much appreciated
I think it's the problem with the way you are using the component in the Route.
Try using:
<Route
path={`/${module.id}`}
key={module.id}
component={() => getComponentForModule(module)}
defaultParams={module}
/>
EDIT:
I have a feeling that it's because of your HOC.
Can you try
component={ModuleComponent}
defaultParams={module}
And get the module from the match object.
const ModuleComponent = ({ match }) => {
const {type, module} = match;
const { setActiveModule } = useShow();
useEffect(() => {
if (type === 'full') {
setActiveModule(module);
}
},[module, setActiveModule]);
match is an object and evaluated in the useEffect will always cause the code to be executed. Track match.type instead. Also you need to track the module there. If that's an object, you'll need to wrap it in a deep compare hook: https://github.com/kentcdodds/use-deep-compare-effect
useEffect(() => {
if (match.type === 'full') {
setActiveModule(module);
}
},[match.type, module]);

Resources