React not setting state from fetch data - javascript

I have a piece of react JS code that is supposed to fetch data from an endpoint and populate a form from the data.
The main issue I'm having with is that it only populates the first field.
It does not populate the rest.
The react component is as below
import React, { useState, useCallback, useEffect } from "react";
import { Page, Button, Stack, Card, Form, FormLayout, TextField, TextContainer, Modal, Toast, TextStyle, Loading } from "#shopify/polaris";
import axiosInstance from "../common/RequestHandler";
import { useParams } from 'react-router-dom';
import { useNavigate } from "react-router";
function EditPackages(){
const [errorToastActive, setErrorToastActive] = useState(false);
const [active, setActive] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [height, setHeight] = useState('');
const [width, setWidth] = useState('');
const [depth, setDepth] = useState('');
const [maxWeight, setMaxWeight] = useState('');
const [packageName, setPackageName] = useState('');
const [packageId, setPackageId] = useState(null);
const [btnLoadingState, setBtnLoadingState] = useState(false);
const [btnLoadingState, setBtnLoadingState] = useState(false);
const toggleErrorToastActive = useCallback(() => setErrorToastActive((errorToastActive) => !errorToastActive), []);
const errorToastMarkUp = errorToastActive ? (
<Toast content="Error in editing your package" error onDismiss={toggleErrorToastActive} />
) : null;
const params = useParams();
const editPackageId = params.editPackageId;
console.log("Edit Package ID -> ", editPackageId);
const navigate = useNavigate();
useEffect(async () => {
const data = await retrievePackage();
console.log(data);
setMaxWeight(data.maxWeight);
setDepth(data.depth);
setHeight(data.height);
setWidth(data.width);
setPackageName(data.packageName);
}, [editPackageId]);
const backToPackages = function (){
navigate('/app/packages');
}
const getPackage = useCallback(async () => {
setPackageInfo(await retrievePackage());
}, []);
async function retrievePackage(){
setIsLoading(true);
const resp1 = await axiosInstance.get('/packageInfo?packageId=' + editPackageId);
setIsLoading(false);
return await resp1.data;
}
return (
<Page title="Edit Package" fullWidth>
{errorToastMarkUp}
<Form>
<FormLayout>
<TextField label="Package Name" value={packageName} onChange={setPackageName} autoComplete="off" />
<TextField label="Height in CM" value={height} onChange={setHeight} autoComplete="off" />
<TextField label="Width in CM" value={width} onChange={setWidth} autoComplete="off" />
<TextField label="Depth in CM" value={depth} onChange={setDepth} autoComplete="off" />
<TextField label="Max Weight in Grams" value={maxWeight} onChange={setMaxWeight} autoComplete="off" />
<Button submit>Submit</Button>
</FormLayout>
</Form>
</Page>
);
}
export default EditPackages;
The getPackage method is to retrieve the data from the endpoint and I'm expecting the setPackageInfo to set the state/values for the object.
I can confirm the API data is retrieved and to confuse me even more, it populates the textbox with packageInfo.packageName. But the rest, none.
I'm sure the names match with the data retrieve as well.
For better understanding, below is my response from the endpoint.
{
"id": 25,
"mId": 1,
"height": 123,
"width": 35,
"depth": 3,
"maxWeight": 4566,
"created_at": "2022-02-18T21:13:47.000000Z",
"updated_at": "2022-02-18T21:13:47.000000Z",
"packageName": "Some random name"
}
Any help is greatly appreciate. I've been hitting my head on a brick wall for days with this problem. Thank you in advance.

It seems like the form has some internal state.
Shopify has a package for managing form state: #shopify/react-form-state.
For this example, if you want to keep it simple, make a state hook for every field. This is not slow since react groups setState calls in rerenders and if multiple requests are made, the page wont refresh depending on the field count but only once.
const [packageName, setPackageName] = useState("");
const [height, setHeight] = useState("");
async function retrievePackage(){
setIsLoading(true);
const response = await axiosInstance.get('/packageInfo?packageId=' + editPackageId);
setIsLoading(false);
return response.data;
}
useEffect(() => {
const data = await retrievePackage();
setPackageName(data.packageName);
setHeight(data.height);
...
}, []);
<TextField
value={packageName}
onChange={setPackageName}
/>
<TextField
value={height}
onChange={setHeight}
/>
Then submit the object composed from all the hooks
const formData = {
packageName,
height,
}
...

Your onChange method is incorrect, that's why you get an object in the response from axios, but once one of the onChange(s) runs, the state changes and you are left with the first field in the tree only and the other values become null/undefined.
Try changing the onChange method to - e=> setPackageInfo({...packageInfo, packageName: e.target.value}) for packageName, e=> setPackageInfo({...packageInfo, height: e.target.value}) for height, and so on.

Related

How to reset every input data which we've rendered on every input in react js?

import React, { useState } from "react";
import axios, { Axios } from "axios";
import {
ContainerDiv,
InnerDiv,
StyledButton,
StyledInput,
} from "./StyledComponents";
function WeatherCard() {
const [input, SetInput] = useState("");
const [apiData, setApiData] = useState([]);
const [temp, setTemp] = useState([]);
const [minTemp, setMinTemp] = useState([]);
const [maxTemp, setMaxTemp] = useState([]);
const [pressure, setPressure] = useState([]);
const [humidity, setHumidity] = useState([]);
const [error, setError] = useState([]);
const [wind, setWind] = useState([]);
const handleOnChange = (e) => {
SetInput(e.target.value);
console.log(input);
};
const handleButtonClick = () => {
axios
.get(
`https://api.openweathermap.org/data/2.5/weather?q=${input}&appid=0f5ee70dcdfa521f9fbd0cacb97d71b7`
)
.then((data) => {
setApiData(data.data.weather);
setTemp(Math.round(data.data.main.temp - 273));
setMinTemp(Math.round(data.data.main.temp_min - 273));
setMaxTemp(Math.round(data.data.main.temp_max - 273));
setPressure(data.data.main.pressure);
setHumidity(data.data.main.humidity);
setWind(data.data.wind.speed);
})
.catch((e) => {
setError(`${input} City not found.`);
});
};
return (
<>
<ContainerDiv>
<InnerDiv>
<label for="city">City: </label>
<StyledInput
type="text"
value={input}
onChange={handleOnChange}
name="city"
></StyledInput>
<StyledButton onClick={handleButtonClick}>
Get Weather Data
</StyledButton>
</InnerDiv>
</ContainerDiv>
<ContainerDiv>
<div>
{apiData.map((item) => {
return <p>Weather: {item.description}</p>;
})}
<p>Temperature: {temp}°C</p>
<p>Minimum Temperature: {minTemp}°C</p>
<p>Maximum Temperature: {maxTemp}°C</p>
<p>Pressure: {pressure}pa</p>
<p>Humidity:{humidity}%</p>
<p>Wind Speed: {wind}</p>
<p>Error: {error}</p>
</div>
</ContainerDiv>
</>
);
}
export default WeatherCard;
"in this code the last results are there if we get any error i.e. by typing wrong city name, the problem is that the last data is still there and there is error which we cant tell if its for new input or last input. please dont mind my english as it is my third language"
i want to reset rendered data on every input.
just set your states to an empty string after getting an error
.catch((e) => {
setError(`${input} City not found.`);
setApiData([]);
setTemp('');
setMinTemp('');
setMaxTemp('');
setPressure('');
setHumidity('');
setWind('');
}

How to write value to localStorage and display it in input on reload?

I have an input on the page, initially it is empty. I need to implement the following functionality: on page load, the component App fetches from localStorage a value of key appData and puts it in the input. That is, so that in the localStorage I write the value to the input and when reloading it is displayed in the input. How can i do this?
I need to use useEffect
import { useEffect, useState } from "react";
export default function App() {
const [userData, setUserData] = useState("");
useEffect(() => {
localStorage.setItem("Userdata", JSON.stringify(userData));
}, [userData]);
return (
<div>
<input value={userData} onChange={(e) => setUserData(e.target.value)}></input>
</div>
);
}
Use the change event to write to the localStorage, then use an init function in the useState hook.
import { useState } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const [ userData, setUserData ] = useState(loadUserData);
const handleUserDataUpdate = e => {
const userData = e.target.value;
setUserData(userData);
saveUserData(userData);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input id="testInput" value={ userData } onChange={ handleUserDataUpdate } />
</div>;
}
If you need an example using uncontrolled inputs, here is one using useEffect :
import { useEffect } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const inputRef = useRef();
useEffect(() => {
inputRef.current.value = loadUserData();
}, []); // initial load
const handleUpdateUserData = () => {
saveUserData(inputRef.current.value);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input ref={ inputRef } id="testInput" onChange={ handleUpdateUserData } />
</div>;
}
You can set a default value for the input inside state.
const [userData, setUserData] =
useState(JSON.parse(localStorage.getItem('Userdata')) || '');
So when the component mounts (after reload), the initial userData value is taken directly from the localStorage. If it's empty, the fallback value will be set ('').
Note: Make sure to add also the onChange handler to the input.

What would be the best solution to avoid this infinite loop (useEffect)

I developed an application where I get an api (pokeAPI) with pokemons, and basically I have a global array with "myPokemons", so I want to display all my pokemons except the ones in that array, so I created the function "filterMyPokemons" that I filter the pokemons that should be displayed, and then I call this function in useEffect so that it is updated along with the page, putting a dependency array from the API list. The problem is that I now have an infinite loop that hurts the performance of the application.
import * as C from './styles';
import logo from '../../assets/pokelogo.png';
import { useContext, useState } from 'react';
import { useApi } from '../../hooks/useApi';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import Pokelist from '../../components/PokeList';
import CatchingPokemonIcon from '#mui/icons-material/CatchingPokemon';
import CatchContext from '../../context/Context';
const Homepage = () => {
const api = useApi();
const { showMyPokemons } = useContext(CatchContext);
const navigate = useNavigate();
const [pokemonList, setPokemonList] = useState([]);
const [loading, setLoading] = useState(false);
const [text, setText] = useState('');
const [myPokemonsList, setMyPokemonsList] = useState([]);
const [pokemonListFiltered, setPokemonListFiltered] = useState([]);
useEffect (() => {
const getAllPokemons = async () => {
const myPokemons = await showMyPokemons();
const pokemon = await api.getAllPokemon();
setLoading(true);
setPokemonList(pokemon);
setMyPokemonsList(myPokemons);
setLoading(false);
}
filterMyPokemons();
getAllPokemons();
}, [myPokemonsList]);
const filterMyPokemons = async () => {
const filteredList = await pokemonList.filter(pokemons => !myPokemonsList.includes(pokemons.name))
return setPokemonListFiltered(filteredList);
};
const lowerSearch = text.toLocaleLowerCase();
const filteredPokemons = pokemonListFiltered.filter(pokemon => pokemon
.name.toLowerCase().includes(lowerSearch)
);
const handleHome = () => {
navigate('/')
}
const handleMyPokemons = () => {
navigate('/mypokemons')
}
return (
<C.Container>
<C.Logo>
<img src={logo} alt="" />
</C.Logo>
<C.Navbar>
<input
type="text"
placeholder='Busque um pokémon...'
onChange={(e) => setText(e.target.value)}
value={text}
/>
</C.Navbar>
<C.Pokedatabase onClick={handleMyPokemons}>
<button>Meus pokémons <i><CatchingPokemonIcon /></i></button>
</C.Pokedatabase>
<C.Pokelist>
{filteredPokemons.map(pokemon => {
return (
<Pokelist
name={pokemon.name}
/>
)
})}
</C.Pokelist>
</C.Container>
)
}
export default Homepage;
If I leave useEffect's dependency array empty, the items are not displayed, but if I leave any dependencies it causes an infinite loop. How to solve this problem?
The problem comes with updating the myPokemonsList array within the useEffect hook that depends on that array.
useEffect (() => {
const getAllPokemons = async () => {
const myPokemons = await showMyPokemons();
const pokemon = await api.getAllPokemon();
setLoading(true);
setPokemonList(pokemon);
setMyPokemonsList(myPokemons); // Here's the infinite loop
setLoading(false);
}
filterMyPokemons();
getAllPokemons();
}, [myPokemonsList]); // Here's the infinite loop
You should have another use effect for updates on the myPokemonList in order to avoid updating and depending on the same list.

How do I pass data from one sibling to another using React Router?

I have a search bar with a submit button that fetches data from an API, all located in a fixed Nav bar. Every time I click submit, I would like React Router to change to a results page, and display the results.
I can't seem to figure out how to pass the data, either as props or as state to this new component. Here is my search API code:
const Search = () => {
const apiKey = 'xxxxxx';
const [input, setInput] = useState('');
const [items, setItems] = useState([]);
const handleSubmit = (e) => {
searchAPI();
};
const searchAPI = async () => {
const res = await fetch(`http://www.omdbapi.com/?apikey=${apiKey}&s=${input}`);
const data = await res.json();
setItems(data.Search);
};
return (
<form>
<input onChange={(e) => setInput(e.target.value)}></input>
<Link to={{ pathname: '/results', state: items }}>
<button type="submit" onClick={handleSubmit}>
search
</button>
</Link>
</form>
);
};
And here is the code in my results component:
const SearchResults = () => {
const [results, setResults] = useState([]);
return (
<div>
<h1>RESULTS</h1>
{results.map((result) => {
return <li key={results.Search.imdbID}>{result.Search.Title}</li>;
})}
</div>
);
};
How would I go about getting the data from items in my search API component into results in my results component?
You are so close. You could use useLocation hook from "react-router-dom" in your SearchResults component like so
const { state } = useLocation();
Items that you pass from the Link would be there.
Use:
<Link to="results" myData={{ state: items }}>
In results components you can access params:
import {useRouteMatch} from 'react-router-dom'
const {params} = useRouteMatch();
console.log(params.myData);

How to pass state from one component to another that are joined by a stacknavigator in react native?

Let's say I have two components, A and B that are joined together by the stack navigator that are being rendered in App.js. Now in A I have declared some state variables and also in B I have declared some state variables. I want to use the state variables I declared in A in B. And then use all the state variables of A and B together to test an api endpoint.
For e.g: Componenet A:
...
const A = () => {
const [projectName, setProjectName] = useState("");
const [projectDescription, setProjectDescription] = useState("");
const [projectBudget, setProjectBudget] = useState(null);
const [projectDuration, setProjectDuration] = useState(null);
const [industry, setIndustry] = useState("");
const [companyName, setCompanyName] = useState("");
const [numberOfEmployees, setNumberOfEmployees] = useState(null);
return (
...
);
}
...
...
const B = () => {
var newDiamond = [
{ newCriteria: { novelty: "", technology: "", complexity: "", pace: "" } },
];
...
const [techVal, setTechVal] = useState("");
const [noveltyVal, setNoveltyVal] = useState("");
const [complexityVal, setComplexityVal] = useState("");
const [paceVal, setPaceVal] = useState("");
return (
...
newDiamond[0].newCriteria.technology = techVal;
...
newDiamond[0].newCriteria.novelty = noveltyVal;
...
newDiamond[0].newCriteria.complexity = complexityVal;
...
newDiamond[0].newCriteria.pace = paceVal;
...
//Need to initiate an HTTP request here by using state variables of both A and B components:
axios.post('/projects', {projectName, projectDescription, projectBudget, projectDuration, industry, companyName, numberOfEmployees, newDiamond});
//newDiamond that is initialized in B uses state variables of B, is used in the request.
...
);
}
...
How can I pass state from A to B? I don't think there is a parent-child relationship between them so I don't think I can pass props b/w them.
You should consider using React Context API https://uk.reactjs.org/docs/context.html. Its dedicated to sharing the common state (items in your case). Here is an example:
You should create a common context for items:
ItemsState.js
import React, { useState, useContext } from 'react';
const ItemsContext = React.createContext([]);
export const ItemsProvider = ({ children }) => {
return (
<ItemsContext.Provider value={useState([])}>
{children}
</ItemsContext.Provider>
);
}
export const useItems = () => useContext(ItemsContext);
Then share the context between screens with provider in App.js like this
import {ItemsProvider} from 'ItemsState';
function App() {
return (
<ItemsProvider> // share the items between both screens
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Add" component={AddScreen} />
</Stack.Navigator>
</NavigationContainer>
</ItemsProvider>
);
}
Then use items context in each screen like this AddScreen.js
import {useItems} from './ItemsState';
function AddScreen({ route, navigation }) {
const [items, setItems] = useItems(); // <- using items context as global useState
const [itemName, setItemName] = React.useState('');
const [itemPrice, setItemPrice] = React.useState('0');
const addItem = () => {
setItems([...items, { itemName, itemPrice }]);
setItemName('');
setItemPrice('0');
};
return (
<View>
<TextInput
multiline
placeholder="What's on your mind?"
value={itemName}
onChangeText={setItemName}
/>
<TextInput
multiline
placeholder="What's on your mind?"
value={itemPrice}
onChangeText={setItemPrice}
/>
<Button
title="Done"
onPress={() => {
addItem();
// Pass params back to home screen
navigation.navigate('Home', items);
}}
/>
</View>
);
}
You can also use useReducer hook and make more Redux-like. Check out this article
https://medium.com/simply/state-management-with-react-hooks-and-context-api-at-10-lines-of-code-baf6be8302c

Categories

Resources