React formReducer in context causing rerender with each keystroke - reactjs

I am trying to architecture a React form which uses functions and data from context.
I have a formReducer which is used by one of the context state values.
export const FormContext = createContext({});
function formReducer(state, event) {
if (event.name.startsWith("address") && event.name.split(".").length > 1) {
var updateField = event.name.split(".")[1];
return {
...state,
address: { ...state.address, [updateField]: event.value },
};
} else if (
event.name.startsWith("hours") &&
event.name.split(".").length > 1
) {
var updateField = event.name.split(".")[1];
return {
...state,
hours: { ...state.hours, [updateField]: event.value },
};
}
return {
...state,
[event.name]: event.value,
};
}
export const FormProvider = (props) => {
const { children } = props;
const [isSubmitting, setIsSubmitting] = useState(false);
const [formData, setFormData] = useReducer(formReducer, {
name: "",
address: { line1: "", city: "", state: "", zip: "" },
phone: "",
contact: "",
hours: {
monday_start: "",
tuesday_start: "",
wednesday_start: "",
thursday_start: "",
friday_start: "",
saturday_start: "",
sunday_start: "",
monday_end: "",
tuesday_end: "",
wednesday_end: "",
thursday_end: "",
friday_end: "",
saturday_end: "",
sunday_end: "",
},
});
const [page, setPage] = useState(1);
const [showModal, setShowModal] = useState(false);
function fetchClientData(id) {
return fetch(
`api-call`
)
}
function handleSubmit(e) {
e.preventDefault();
fetch(
"api-call",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ...formData, page: page }),
}
)
}
const formContext = {
fetchClientData,
formData,
setFormData,
};
return (
<FormContext.Provider value={formContext}>{children}</FormContext.Provider>
);
};
In my form component I pull in these values from context
const {
fetchClientData,
formData,
handleSubmit,
isSubmitting,
page,
setFormData,
setIsSubmitting,
setPage,
setShowModal,
showModal,
} = useContext(FormContext);
And anytime an input field changes I update the context state.
function handleChange(e) {
const { name, value } = e.target;
setFormData({
name: name,
value: value,
});
}
If I understand context correctly components that consume context will rerender each time a context state changes.
I am wondering how I can better design my app to avoid this problem. Ideally I don't want to bog down my component with all of these functions and state variables, but I also don't want my component to constantly rerender.

Related

Somebody knows how to populate this field showing in the picture below with specific data object?

this is the image(click here!) where you can see our web page with certain fields, that one of the field with arrow is the one didn't populate object from the backend
this is written with react & typescript & our backend is java springboot & MySQL as database.
and here is the code that i'm suspected , having a issue:
const AddUsers: FC = (props) => {
const navigate = useNavigate();
// const { id } = useParams();
const dispatch = useDispatch();
const roleList = useGetRoleList()
const user = useCurrentUser();
const [rolesInput, setRolesInput] = useState<MultiValue<{ value: string; label: string; }>>([]);
const isFetching = useInviteUserLoading()
const permitted = useCheckPermission(ROLES.ADD_USER)
const { register, handleSubmit, formState: { errors }, clearErrors, reset } =
useForm<IUserForm>({ //'resetField'
mode: "onChange",
resolver: yupResolver(userFormSchema)
});
useEffect(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, [])
//fetching list from backend.. <--------
useUpdateEffect( () => {
dispatch( getRolesList({companyId: user.companyId}) );
},[user.companyId])
useUpdateEffect(() => {
clearErrors()
reset();
}, [isFetching])
const onSubmit: SubmitHandler<IUserForm> = (data)=>{
const roles = rolesInput.map(role => parseInt(role.value))
if(roles){
dispatch(inviteUser({
companyId: user.companyId,
roles: roles,
firstName: data.firstName,
lastName: data.lastName,
email: data.email
}))
}else{
alert("Please assign roles")
}
}

how i should use react hook useState with post axios

I try call request post with hooks. Firstly, a call request post using this.setState and it working correctly
but I want to use a hook (useState) instead of setState and it doesn't work
code below working correctly
export default class AddShoes extends Component {
constructor(props) {
super(props);
this.state = this.startValue;
this.state.show = false;
this.shoesChange = this.shoesChange.bind(this);
}
startValue = {
brand: "",
model: "",
date: "",
price: "",
img: "",
};
shoesChange = (event) => {
this.setState({
[event.target.name]: event.target.value,
});
};
submitShoes = (event) => {
event.preventDefault();
const shoes = {
brand: this.state.brand,
model: this.state.model,
date: this.state.date,
price: this.state.price,
img: this.state.img,
};
axios.post("http://localhost:8080/api", shoes).then((response) => {
if (response.data != null) {
this.setState(this.startValue);
alert("added")
}
});
};
the second code below doesn't work
export default function AddShoes() {
const [values, setValues] = useState({
brand: "",
model: "",
date: "",
price: "",
img: "",
});
// const [show, setShow] = useState(false);
const handleSetInputs = (e) => {
setValues({ ...values, [e.target.name]: e.target.value });
};
const submitShoes = (event) => {
event.preventDefault();
axios.post("http://localhost:8080/api", values)
.then((response) => {
if (response.data != null) {
setValues(response.data);
alert("added!");
}
});
};
what I should change?
To just change one property from an state-object in React Hooks you have to do this:
setValues(prevValues => ({ ...prevValues, [e.target.name]: e.target.value }));
In the first example that works, you are resetting the state by calling this.setState(this.startValue)
In the second example, you are passing the result of the network request inside setValue setValues(response.data)
Create initialValues outside of AddShoes function component.
const initialValues = {
brand: "",
model: "",
date: "",
price: "",
img: "",
}
Now pass that into setValues inside submitShoes
const submitShoes = (event) => {
event.preventDefault();
axios.post("http://localhost:8080/api", values)
.then((response) => {
if (response.data != null) {
setValues(initialValues);
alert("added!");
}
});
};

Unhandled Rejection (TypeError): Cannot read property 'temp' of undefined

We are making a weather website using React-hooks.
Api got it from https://openweathermap.org/current#parameter.
But I can't read the temperature properties.
According to the website description,
main: main.temp Temperature. Unit Default: Kelvin, Metric: Celsius, Imperial: Fahrenheit.
Can't get properties inside JSON
What is the reason?
This is my Weather.jsx file
import React, { useState, useEffect } from "react";
const Weather = () => {
const [weather, setWeather] = useState("");
const [error, setError] = useState(null);
const API_KEY = "1234567890"; // My Api key
const COORDS = "coords";
const baseUrl = "https://api.openweathermap.org/data/2.5/";
const getWeather = (lat, lon) => {
fetch(
`${baseUrl}weather?lat=${lat}&lon=${lon}&appid=${API_KEY}&units=metric&lang={kr}`
)
.then(function (response) {
return response.json();
})
.then(function (json) {
const temperature = json.main.temp; // Error part!!!
const place = json.name;
setWeather(`${temperature} \n ${place}`);
});
};
useEffect(() => {
getWeather();
}, []);
const saveCoords = (coordsObj) => {
localStorage.setItem(COORDS, JSON.stringify(coordsObj));
};
const weatherSuccess = (position) => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
const coordsObj = {
latitude: latitude,
longitude: longitude,
};
saveCoords(coordsObj);
getWeather(latitude, longitude);
};
const weatherError = () => {
setError("위치 정보를 얻을 수 없습니다.");
};
const askForCoords = () => {
navigator.geolocation.getCurrentPosition(weatherSuccess, weatherError);
};
const loadCoords = () => {
const loadedCoords = localStorage.getItem(COORDS);
if (loadedCoords === null) {
askForCoords();
} else {
const parsedCoords = JSON.parse(loadedCoords);
getWeather(parsedCoords.latitude, parsedCoords.longitude);
}
};
const init = () => {
loadCoords();
};
init();
return weather;
};
export default Weather;
This Weather.jsx file is shown in the Presenter.jsx file.
import React from "react";
import styled from "styled-components";
import Weather from "../../Components/Weather";
const DetailPresenter = () => {
return (
<>
<DetailContainer>
<Weather/>
</DetailContainer>
</>
);
};
export default DetailPresenter;
console.log result is 👇
{coord: {…}, weather: Array(1), base: "stations", main: {…}, visibility: 10000, …}
base: "stations"
clouds: {all: 0}
cod: 200
coord: {lon: 126.79, lat: 37.65}
dt: 1608189833
id: 1842485
main: {temp: 0.08, feels_like: -4.98, temp_min: -1, temp_max: 1, pressure: 1030, …}
name: "Goyang-si"
sys: {type: 1, id: 8105, country: "KR", sunrise: 1608158521, sunset: 1608192958}
timezone: 32400
visibility: 10000
weather: [{…}]
wind: {speed: 2.1, deg: 300}
__proto__: Object
-----
{cod: "400", message: "wrong latitude"}
cod: "400"
message: "wrong latitude"
__proto__: Object

Updating Multiple React State within the same event handler

These are my states using hooks:
const [adminProfile, setProfile] = useState({
locations: [],
});
const [location, setLocation] = useState({
locationName: "",
location: {},
locationPhone: "",
locationEmail: "",
staff: [],
multipleKitchens: false,
kitchens: [],
});
const [locationList, setLocationList] = useState([]);
const [locationAddress, setAddress] = useState({
streetAddress: "",
streetAddress2: "",
city: "",
state: "",
zip: "",
country: "USA"
});
I have a bunch of fields with onChange handlers and an onClick handler that needs to update 3 states in order. First, LocationAddress has to become the state of the location property within the location state. Second, the location state has to be updated with a unique ID, and then that unique ID is inserted into the array in the locationList state. Finally, the entire array from locationList state is added to the locations property of adminProfile state. These are all in one component.
const handleClickLocation = () => {
setLocation(prevValue => ({
...prevValue,
locationID: uuidv4(),
location: locationAddress
}));
setLocationList(prevValue => [...prevValue, location.locationID]);
setProfile(prevValue => ({
...prevValue,
locations: locationList
}))
The first time the click handler is triggered, it sets only the first state in the handler and sends "undefined" into the second state. When the click handler is clicked a second time, it then behaves normally. I want all the states to update simultaneously. I've tried forceUpdate(), but couldn't figure out the syntax. I've tried using ReactDOM.unstable_batchedUpdates but it still behaved the same.
How can I get this to work? I want to keep this within one component. Is that possible?
Here is the entire code updated with the useEffect hook:
import React, {useState, useEffect} from "react";
import axios from "axios";
const { v4: uuidv4 } = require('uuid');
const CompanyProfileInfo = (props) => {
const todayDate = () => {
let today = new Date();
let day = today.getDate();
let month = today.getMonth() + 1;
let year = today.getFullYear();
if (day < 10) day = '0' + day;
if(month < 10) month = '0' + month;
return (month + "/" + day + "/" + year);
}
const [adminProfile, setProfile] = useState({
companyID: props.companyInfo.companyID,
firstName: "",
lastName: "",
phonePrimary: "",
phoneSecondary: "",
emailSecondary: "",
streetAddress: "",
streetAddress2: "",
city: "",
state: "",
zip: "",
country: "USA",
multipleLocations: false,
locations: [],
membershipLevel: "Basic",
joinedDate: todayDate(),
});
const [location, setLocation] = useState({
locationName: "",
locationPhone: "",
locationEmail: "",
staff: [],
multipleKitchens: false,
kitchens: [],
});
const [locationAddress, setAddress] = useState({
streetAddress: "",
streetAddress2: "",
city: "",
state: "",
zip: "",
country: "USA"
});
const [locationList, setLocationList] = useState([]);
useEffect(() => {
setLocationList(prevValue => [...prevValue, location.locationID]);
}, [location.locationID]);
useEffect(() => {
if (locationList[0] === undefined) {
{locationList.shift()}
}
setProfile(prevValue => ({
...prevValue,
locations: locationList
})
)
}, [locationList])
const handleChange = (event) => {
const {name, value} = event.target;
setProfile(prevValue => ({
...prevValue,
[name]: value
}))
}
const handleChangeLocations = (event) => {
const {name, value} = event.target;
setLocation(prevValue => ({
...prevValue,
[name]: value
}));
};
const handleChangeLocations1 = (event) => {
const {name, value} = event.target;
setAddress(prevValue => ({
...prevValue,
[name]: value
}));
};
const handleClickLocation = () => {
setLocation(prevValue => ({
...prevValue,
locationID: uuidv4(),
location: locationAddress,
}));
};
const handleClick = () => {
axios.post('http://localhost:3001/profileinfo', adminProfile)
.then(res => {
props.supportFunctions.setUpLocations(res);
})
.catch(function (error) {
console.log(error);
})
}
return (
<div>
</div>
)
}
export default CompanyProfileInfo;
setState is asynchronous, it means that when it is called, its state won't update at the same time, it takes some time to perform its action.
You can make use of useEffect to do that.
useEffect will perform an action only when the specified state (inside brackets) changes
useEffect(() => {
setLocation({
...location,
location: locationAddress,
locationID: uuidv4()
})
}, [locationAddress]) //When locationAddress changes, setLocation
useEffect(() => {
setLocationList([
...locationList,
location.locationID
])
}, [location]) //When location changes, insert Id
Ps: You can have multiple useEffects in your code.
Updating of the state is asynchronous behavior, because of that you are getting locationID undefined for setLocationList.
Inside class component, we can use a callback to setState call like this -
this.setState({ data: newData }, () => { console.log("This will get printed after setState") })
But in your case, you are using function component so you have to use useEffect react hook to listen for changes in your data and then update other data in the state.
Take a look at this question - How to use `setState` callback on react hooks

How do I call API hooks one at a time?

I am completely new to React and trying to figure this out, while still keeping it as simple as possible.
I have a wrapper functional component called RestaurantMapWrapper that is supposed to:
Get geolocation data from a hook called usePosition.
pass the longitude and latitude data into a hook called useYelpHook, which retrieves data on restaurants using the passed in latitude and longitude data.
Render the yelp data (automatically, without user input).
The problem is that usePosition does not get the location in time, so useYelpHook has nothing to work with. If I set pos to a default value, then useYelpHook is never called again.
How can I ensure that useYelpHook waits for usePosition before rendering? Does this have something to do with one hook being asynchronous?
export function RestaurantMapWrapper(props) {
const { latitude, longitude, timestamp, accuracy, error, isLoadingMap } = usePosition();
const pos = { lat: latitude, lng: longitude }; //ends up being undefined since neither have been retreived yet
const [{ data, isLoading }, setLoc] = useYelpHook(pos); //is there somewhere I could call setLoc?
return <div>JSON.stringify({data})</div>;
export const useYelpHook = (initialLoc) => {
const API_KEY = 'my api key';
const config = {
headers: { Authorization: `Bearer ${API_KEY}` },
params: {
term: 'food',
latitude: '0',
longitude: '0',
radius: '',
sort_by: 'rating'
}
}
const [data, setData] = useState({ businesses: [] });
const [loc, setLoc] = useState(initialLoc);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
config.params.latitude = loc.lat;
config.params.longitude = loc.lng;
const fetchData = async () => {
setIsLoading(true);
const result = await axios(`${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3/businesses/search`, config);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [loc])
return [{ data, isLoading }, setLoc];
}
import { useState, useEffect } from 'react';
const defaultSettings = {
enableHighAccuracy: false,
timeout: Infinity,
maximumAge: 0,
};
export const usePosition = (settings = defaultSettings) => {
const [position, setPosition] = useState({});
const [error, setError] = useState(null);
const [isLoadingMap, setIsLoadingMap] = useState(true);
const onChange = ({ coords, timestamp }) => {
setPosition({
latitude: coords.latitude,
longitude: coords.longitude,
accuracy: coords.accuracy,
timestamp,
});
};
const onError = (error) => {
setError(error.message);
};
useEffect(() => {
setIsLoadingMap(true);
const geo = navigator.geolocation;
if (!geo) {
setError('Geolocation is not supported');
return;
}
geo.getCurrentPosition(onChange, onError, settings);
setIsLoadingMap(false);
}, [settings]);
return { ...position, error, isLoadingMap };
};
useYelpHook's useEffect runs every time the component renders and is triggered by the dependencies. You should pass loc directly into the useYelpHook useEffect instead of saving its initial state.
export function RestaurantMapWrapper(props) {
const { latitude, longitude, timestamp, accuracy, error, isLoadingMap } = usePosition();
const pos = useMemo(() => ({ lat: latitude, lng: longitude }), [latitude, longitude]); //ends up being undefined since neither have been retrieved yet
const [{ data, isLoading }] = useYelpHook(pos);
return <div>JSON.stringify({data})</div>;
const API_KEY = 'my api key';
const config = {
headers: { Authorization: `Bearer ${API_KEY}` },
params: {
term: 'food',
latitude: '0',
longitude: '0',
radius: '',
sort_by: 'rating'
}
}
export const useYelpHook = (loc) => {
const [data, setData] = useState({ businesses: [] });
//const [loc, setLoc] = useState(initialLoc);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
/* return if loc is invalid */
config.params.latitude = loc.lat;
config.params.longitude = loc.lng;
const fetchData = async () => {
setIsLoading(true);
const result = await axios(`${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3/businesses/search`, config);
setData(result.data);
setIsLoading(false);
};
fetchData();
}, [loc])
return [{ data, isLoading }];
}
Note the useMemo and removal of initialLoc

Resources