Export / import a useState altering utility function? - javascript

I have a little upload handler like this:
const handleUploadPhoto = filename => {
setHasImage('has-image');
setPostButtonState('');
onAddedPhoto(filename);
setPostImageFilename(filename);
};
I use it all over the place and I'd love to export it from a helpers.js file and import it wherever needed, but I'm not sure how to do that considering when the useState variables affected by it need to stay in the parent, not the imported helper.
const [postImageId, setPostImageId] = useState(null);
const [postImageFilename, setPostImageFilename] = useState(null);
const [postImageUrl, setPostImageUrl] = useState(null);
Is this kind of function just not a good candidate for export / import?

One option is to make your own hook that defines all of the state setters and takes the onAddedPhoto as a parameter:
const useImageStuff = (onAddedPhoto) => {
const [hasImage, setHasImage] = useState('');
const [postButtonState, setPostButtonState] = useState('');
const [postImageFilename, setPostImageFilename] = useState('');
const handleUploadPhoto = () => {
setHasImage('has-image');
setPostButtonState('');
onAddedPhoto(filename);
setPostImageFilename(filename);
};
return {
hasImage,
setHasImage,
postButtonState,
setPostButtonState,
postImageFilename,
setPostImageFilename,
handleUploadPhoto,
};
Then use that all over the place:
const SomeComponent = () => {
const onAddedPhoto = () => {
// ...
};
const {
hasImage,
setHasImage,
postButtonState,
setPostButtonState,
postImageFilename,
setPostImageFilename,
handleUploadPhoto,
} = useImageStuff(onAddedPhoto);
// ...

Related

Why is my function not dispatching in react component?

why is fetchReviews not fetching?
Originally didn't use fetchData in use effect.
Ive tried using useDispatch.
BusinessId is being passed into the star component.
no errors in console.
please let me know if theres other files you need to see.
thank you!
star component:
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import {AiFillStar } from "react-icons/ai";
import { fetchReviews } from '../../actions/review_actions';
function Star(props) {
const [rating, setRating] = useState(null);
// const [reviews, setReview] = useState(props.reviews)
// const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
await fetchReviews(props.businessId)
};
fetchData();
console.log(props);
// getAverageRating();
});
const getAverageRating = () => {
let totalStars = 0;
props.reviews.forEach(review => {totalStars += review.rating});
let averageStars = Math.ceil(totalStars / props.reviews.length);
setRating(averageStars);
}
return (
<div className='star-rating-container'>
{Array(5).fill().map((_, i) => {
const ratingValue = i + 1;
return (
<div className='each-star' key={ratingValue}>
<AiFillStar
className='star'
color={ratingValue <= rating ? '#D32322' : '#E4E5E9'}
size={24} />
</div>
)
})}
</div>
);
};
export default Star;
star_container:
import { connect } from "react-redux";
import { withRouter } from "react-router-dom";
import Star from "./star";
import { fetchReviews } from "../../actions/review_actions";
const mSTP = state => {
return {
reviews: Object.values(state.entities.reviews)
};
}
const mDTP = dispatch => {
return {
fetchReviews: businessId => dispatch(fetchReviews(businessId))
};
};
export default connect(mSTP, mDTP)(Star);
console image
why is fetchReviews not fetching? Originally didn't use fetchData in use effect. Ive tried using useDispatch. BusinessId is being passed into the star component. no errors in console.
edit!***
made some changes and added useDispatch. now it wont stop running. its constantly fetching.
function Star(props) {
const [rating, setRating] = useState(null);
const [reviews, setReview] = useState(null)
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
ended up just calling using the ajax call in the useEffect.
useEffect(() => {
const fetchReviews = (businessId) =>
$.ajax({
method: "GET",
url: `/api/businesses/${businessId}/reviews`,
});
fetchReviews(props.businessId).then((reviews) => getAverageRating(reviews));
}), [];
if anyone knows how i can clean up and use the dispatch lmk.
ty all.
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
}), [];
dependency array is outside the useEffect. Since useEffect has no dependency option passed, function inside useEffect will run in every render and in each render you keep dispatching action which changes the store which rerenders the component since it rerenders code inside useEffect runs
// pass the dependency array in correct place
useEffect(() => {
const fetchData = async () => {
const data = await dispatch(fetchReviews(props.businessId))
setReview(data);
};
fetchData();
// console.log(props);
// getAverageRating();
},[]), ;
Passing empty array [] means, code inside useEffect will run only once before your component mounted

Function after async Function [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
Im new at React, I was trying to make weather website. I want to get the visitor’s IP first, then get the city location, and then get the weather conditions directly through Openweather. Here is my code, I hope someone can help me answer how to complete this website, Thank you
import { useState, useEffect } from "react";
import axios from "axios";
require("dotenv").config();
function IpGet() {
const [ip, setIP] = useState("");
const [countryName, setcountryName] = useState("");
const [cityName, setcityName] = useState("");
const [countryCode, setcountryCode] = useState("");
const [countryStateName, setcountryStateName] = useState("");
const WeatherKey = process.env.REACT_APP_WEATHERKEY;
const getData = async () => {
const res = await axios.get("https://geolocation-db.com/json/");
setIP(res.data.IPv4);
setcountryName(res.data.country_name);
setcityName(res.data.city);
setcountryCode(res.data.country_code);
setcountryStateName(res.data.state);
};
// const getWeather = async () => {
// const WeatherUrl = await axios.get(
// `https://api.openweathermap.org/data/2.5/weather?q=${cityName},${countryStateName}&appid=${WeatherKey}`
// );
// };
useEffect(() => {
getData();
}, []);
return (
<div className="IpGet">
<h4>{ip}</h4>
<h4>{countryName}</h4>
<h4>{countryCode}</h4>
<h4>{countryStateName}</h4>
<h4>{cityName}</h4>
</div>
);
}
export default IpGet;
The question is vague but here is a bit of a guess.
A few tips to start with:
You probably don't need axios for most front-end solutions. It is just an extra dependency. Use the fetch API instead.
Keep your variable names consistent - setCountryName instead of setcountryName.
The useMemo hook will prevent a function from being created on every render. You can pass the second argument of a dependency array that contains variables. If any of those variables change, useMemo will recalculate that function.
Now to the code. You can give useEffect the second argument of an array of variables. If any of these variables change, the effect will run the callback function provided as the first arg. useEffect will also always run once when the component mounts.
Create a second effect that runs when you get the data needed to make the weather API call.
All things above considered, your code might now look like this (untested):
import { useState, useEffect } from 'react';
require('dotenv').config();
function IpGet() {
const [ip, setIP] = useState('');
const [countryName, setCountryName] = useState('');
const [cityName, setCityName] = useState('');
const [countryCode, setCountryCode] = useState('');
const [countryStateName, setCountryStateName] = useState('');
const weatherKey = process.env.REACT_APP_WEATHERKEY;
// useMemo to avoid recreating this function on every render
const getData = React.useMemo(() => async () => {
const res = await fetch('https://geolocation-db.com/json/');
setIP(res.data.IPv4);
setCountryName(res.data.country_name);
setCityName(res.data.city);
setCountryCode(res.data.country_code);
setCountryStateName(res.data.state);
});
const getWeather = React.useMemo(() => async () => {
if (!cityName || !countryStateName || !weatherKey) return;
const weatherUrl = `https://api.openweathermap.org/data/2.5/weather?q=${cityName},${countryStateName}&appid=${weatherKey}`;
const weatherData = await fetch(weatherUrl);
// Do something with weatherData here... set to some state or something.
});
useEffect(() => {
getData();
}); // No dependency array, so this will only run once when the component mounts
useEffect(() => {
getWeather();
}, [cityName, countryStateName]); // This will trigger the callback when any of these variables change.
return (
<div className='IpGet'>
<h4>{ip}</h4>
<h4>{countryName}</h4>
<h4>{countryCode}</h4>
<h4>{countryStateName}</h4>
<h4>{cityName}</h4>
</div>
);
}
export default IpGet;

Arrays and Objects are being fetched and then reset to undefined. Using React/JavaScript/Json

I am new to JavaScript and React and am building a weather app that allows the user to save locations to their profile and retrieve the forecast data relevant to that specific location.
I need to display a list of buttons that display the only the names of the locations saved by the user logged in.
Currently, I'm fetching locations, current profile, and then mapping through the locations that was returned by the fetch to match up the location id's to the foreign keys saved to the profile. Right now, all of my fetches return empty arrays and objects. However, if I edit the code and save it, my React app re-renders correctly and the console prints the correct data. When I refresh the page I'm back to empty arrays and objects.
Here's my ProfilesProvider:
import React, { useState, createContext } from "react";
export const ProfileContext = createContext();
export const ProfileProvider = (props) => {
const [profiles, setProfiles] = useState([]);
const [currentProfile, setCurrentProfile] = useState({});
const getProfiles = () => {
return fetch("http://localhost:8088/profiles")
.then((res) => res.json())
.then((theProfiles) => setProfiles(theProfiles))
.then(console.log(profiles));
};
const getCurrentProfile = () => {
let id = localStorage.getItem("weathernet_user");
return fetch(`http://localhost:8088/profiles/${id}`)
.then((res) => res.json())
.then((theProfile) => {
return setCurrentProfile(theProfile);
})
.then(console.log(currentProfile));
};
Heres my LocationsProvider:
import React, { useState, createContext } from "react";
export const LocationContext = createContext();
export const LocationProvider = (props) => {
const apiURL = "http://localhost:8088";
const locationsURL = apiURL + "/locations";
const [locations, setLocations] = useState([]);
let id = localStorage.getItem("weathernet_user");
const getLocations = () => {
return fetch("http://localhost:8088/locations")
.then((res) => res.json())
.then(setLocations);
};
const addLocation = (locationObj) => {
return fetch("http://localhost:8088/locations", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(locationObj),
}).then(getLocations());
};
And here's the module where I'm implementing the code:
import React, { useContext, useEffect, useState } from "react";
import { LocationContext } from "./LocationsProvider";
import { ProfileContext } from "../profiles/ProfilesProvider";
import { useHistory } from "react-router-dom";
import "./Locations.css";
export const LocationList = () => {
const { locations, getLocations, deleteLocation } =
useContext(LocationContext);
const { profiles, getProfiles, currentProfile, getCurrentProfile } =
useContext(ProfileContext);
const [profile, setCurrentProfile] = useState({});
const [city, setCity] = useState("");
const [result, setResult] = useState({});
const [isHidden, setIsHidden] = useState(true);
const [buttonList, setButtonList] = useState([]);
const history = useHistory();
useEffect(() => {
Promise.all([
getLocations(),
getProfiles(),
getCurrentProfile(),
setCurrentProfile(),
]).then(() => {
console.log(currentProfile);
setButtonList(locationResults);
console.log(buttonList);
console.log(locationResults);
});
}, []);
const locationResults = (currentProfile.savedCityId || []).map((cityId) => {
return locations.find((location) => location.id === cityId);
});
I didn't include the lower half of the module because it's where im returning a form and I didn't want to clutter up the question too much

Custom React hook, infinite loop only if I add the second dependency. Bug or something I can't understand?

I've made a really simple React hook. That's something seen on many guides and websites:
import { useEffect, useState } from 'react';
import axios from 'axios';
export const useFetchRemote = (remote, options, initialDataState) => {
const [data, setData] = useState(initialDataState);
useEffect(() => {
const fetchData = async () => {
const result = await axios.get(remote, options);
setData(result.data);
};
fetchData();
}, [remote]);
return data;
};
Example usage:
import { useFetchRemote } from '../utils';
export const UserList = () => {
const users = useFetchRemote('/api/users', {}, []);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>}
</ul>
);
}
This is working. If I understand correctly:
With no dependencies like useEffect(() => { /*...*/ }), setting the state into the function would trigger a re-render, calling useEffect again, in an infinite loop.
With empty dependencies like useEffect(() => { /*...*/ }, []), my function will be called only the "very first time" component is mounted.
So, in my case, remote is a dependency. My function should be called again if remote changes. This is true also for options. If I add also options, the infinite loop starts. I can't understand... why this is happening?
export const useFetchRemote = (remote, options, initialDataState) => {
// ...
useEffect(() => {
// ...
}, [remote, options]);
// ...
};
The infinite loop is caused by the fact that your options parameter is an object literal, which creates a new reference on every render of UserList. Either create a constant reference by defining a constant outside the scope of UserList like this:
const options = {};
const initialDataState = [];
export const UserList = () => {
// or for variable options instead...
// const [options, setOptions] = useState({});
const users = useFetchRemote('/api/users', options, initialDataState);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>}
</ul>
);
}
or if you intend the options parameter to be effectively constant for each usage of the userFetchRemote() hook, you can do the equivalent of initializing props into state and prevent the reference from updating on every render:
export const useFetchRemote = (remote, options, initialDataState) => {
const [optionsState] = useState(options);
const [data, setData] = useState(initialDataState);
useEffect(() => {
const fetchData = async () => {
const result = await axios.get(remote, optionsState);
setData(result.data);
};
fetchData();
}, [remote, optionsState]);
// ---------^
return data;
};
This second approach will prevent a new fetch from occuring though, if the options are dynamically changed on a particular call site of useFetchRemote().

Can not set a state in a react component

I have a react component. I want to set the state within this component that will be passed down to child components. I am getting a reference error to this and I am not sure why.
export const WidgetToolbar: React.FC<{}> = () => {
this.state = {
targetBox:null,
}
const isOpen = useBehavior(mainStore.isWidgetToolbarOpen);
const themeClass = useBehavior(mainStore.themeClass);
const userDashboards = useBehavior(dashboardStore.userDashboards);
const [filter, setFilter] = useState("");
const [sortOrder, setSortOrder] = useState<SortOrder>("asc");
const userWidgets = useMemo(() => {
let _userWidgets = values(userDashboards.widgets).filter((w) => w.widget.isVisible);
if (sortOrder === "asc") {
_userWidgets.sort((a, b) => a.widget.title.localeCompare(b.widget.title));
} else {
_userWidgets.sort((a, b) => b.widget.title.localeCompare(a.widget.title));
}
if (!isBlank(filter)) {
_userWidgets = _userWidgets.filter((row) => {
return row.widget.title.toLowerCase().includes(filter.toLocaleLowerCase());
});
}
return _userWidgets;
}, [userDashboards, sortOrder, filter]);
...
This is the error I am getting:
TypeError: Cannot set property 'state' of undefined
at WidgetToolbar (WidgetToolbar.tsx?ba4c:25)
at ProxyFacade (react-hot-loader.development.js?439b:757)
There's no this or this.state in a functional component. Use the useState hook, similar to what you're doing a few lines below.
export const WidgetToolbar: React.FC<{}> = () => {
const [targetBox, setTargetBox] = useState<null | whateverTheTypeIs>(null);
//...
}
Functional React Components can't have state. You'd have to use a class-based component in order to have state.
https://guide.freecodecamp.org/react-native/functional-vs-class-components/
You used the hook to "use state" in this function: const [filter, setFilter] = useState("");
You could do the same for targetBox, instead of trying to set a property on a non-existent 'this'

Categories