Loading state does not change to true in fetch method - reactjs

I've been trying to implement a loading functionality to my app. I'm simply changing the state of loading from false as the initial value and then true as it starts the fetch and then false as it ends the data fetching. So this should show the loading element I've set conditionally to render when loading is true. But it shows in my console.log that the value is always false.
I've tried putting the setState(true) in different places, in the onClick function but it doesn't seem to toggle to true.
import React, { useState } from "react";
import { LANGUAGES } from '../config/languages'
import { BASEURL, APIKEY } from '../config/gavagai'
export function Input(props) {
const [word, setWord] = useState("");
const [language, setLanguage] = useState("");
const [data, setData] = useState([])
const [loading, setLoading] = useState(false);
const url = BASEURL + '/' + language + '/' + word + '?additionalFields=SEMANTICALLY_SIMILAR_WORDS&apiKey=' + APIKEY;
const fetchData = () => {
giveWarning();
setLoading(true);
if (word && language) {
fetch(url)
.then(response => response.json())
.then(response => setData({ status: 'loaded', payload: response }), setLoading(false))
.catch(error => setData({ status: 'error', error }))
return data;
};
}
return (
<div>
<h1>Gavagai Lexicon</h1>
<div className="row">
<label>
Type your word here
</label>
</div>
<div className="input-field col s5">
<input
type="text"
value={word}
onChange={e => setWord(e.target.value)}
/>
</div>
<div className="input-field col s3">
<select className="browser-default" value={language} onChange={e => setLanguage(e.target.value)}>
<option value="" disabled selected>Choose your language</option>
{ LANGUAGES.map((lang) => {
return(
<option value={lang.alpha2}>{lang.English}</option>
)
})}
</select>
</div>
<div className="button-space">
<button className="btn waves-effect waves-light" onClick={() => fetchData()}>Search</button>
</div>
{
loading ? <p>loading</p> : null
}
</div>
);
}
Console.log reveals that it doesn't toggle to true. What am I missing here?

Because of closure, fetchData has only access to the initial value of word and language variables.
You need to useCallback( your function, [word, language] ) to use the current values of those.
https://reactjs.org/docs/hooks-reference.html#usecallback
export function Input(props) {
...
const fetchData = useCallback(
() => {
giveWarning();
setLoading(true);
if (word && language) {
fetch(url)
.then(response => response.json())
.then(response => setData({
status: 'loaded',
payload: response
}), setLoading(false))
.catch(error => setData({ status: 'error', error }))
return data;
};
},
[word, language]
)
...

Related

Fetch React search data

I have a problem that when I search a Data in my firebase database with react dom js , i cann't get my expected data. Basically, I dont get any data at this time.
export default function SearchBar(){
const [data, setData] = useState([]);
const [search, setSearch] = useState("");
useEffect(() => {
fetch('<URL ...>')
.then(response => response.json())
.then(data => {
// console.log(data);
if(data>0){
setData(data)
}
// console.log(search);
// console.log(data);
})
.catch(error => console.error(error));
}, [search]);
return <Form>
<Input type="text" value={search} placeholder="Searching ..." onChange={event => setSearch(event.target.value)} />
<div className="" style={{height:"18px", color:'red'}}>
{data.filter(item => item.toLowerCase().includes(search.toLowerCase())).map(item => (
<div key={item}>
{console.log(item)}
</div>
))
}
</div>
<Button type="submit"><span class="material-symbols-outlined ">
search
</span>
</Button>
</Form>
}
I expect Get Data from my Firebase which i serach in my serachBox.

action is not dispatching in react-redux

I have a form that collect data about today's expenditure and total users(as attendances) and then submit it using redux dispatch via action addExpenses(). But it douse not run. It seem that it is not counting if it is present or not.
function TodayExpenses() {
const dispatch = useDispatch()
const navigate = useNavigate()
useEffect(() => {
dispatch(getAttendance());
}, [date, getAttendanceObj, dispatch, addExpenses])
const [todayExpenses, setTodayExpenses] = useState(0)
const { attendance: getAttendanceObj, error: getAttendanceError, loading: getAttendanceLoading } = useSelector(state => state.getAttendance)
const { success } = useSelector(state => state.addExpenses)
const submitHandler = (e) => {
e.preventDefault();
let expenses = {
date: date,
total_attendances: count,
expenses_per_day: todayExpenses,
expenses_per_attendance: expensePerAttendance,
}
dispatch(addExpenses(expenses)) // Here be the dragons
console.log(todayExpenses)
}
const today = new Date().toISOString().substr(0, 10);
const [date, setDate] = useState(today)
const count = counter(getAttendanceObj, date)
const expensePerAttendance = (todayExpenses / count).toFixed(2);
return (
<div className="container">
<div class="h1 text-center text-dark" id="pageHeaderTitle">
Enter <input type="date" id="date" value={date} onChange={(e) => setDate(e.target.value)} max={today} />'s Expenses
</div>
<div className="row">
<div className="col-md-6 mx-auto">
<div className="card card-body">
<form onSubmit={submitHandler}>
<label htmlFor="name">Today's Expenses:</label>
<input
type="number"
className="form-group"
id="name"
placeholder="Enter value"
value={todayExpenses}
onChange={(e) => setTodayExpenses(e.target.value)}
/>
<ul class="list-group list-group-flush">
<label class="list-group-item card-header">Total Attendances</label>
<li class="list-group-item">{count}</li>
<label class="list-group-item card-header">Expense Per Attendance</label>
<li class="list-group-item">{expensePerAttendance}</li>
</ul>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
</div>
</div>
</div>
);
}
export default TodayExpenses;
What I have tried so far
What not? I tried console.log()even inside action but it working just above the required script ( I mean where the action have submit the data) .
if wanna ask here is action
export const addExpenses = (expenses) => async (getState, dispatch) => {
try {
dispatch({
type: ADD_EXPENSES_REQUEST
})
console.log("data:", dispatch({
type: ADD_EXPENSES_SUCCESS
}))
const { userLogin: { userInfo } } = getState();
const config = {
headers: {
'Content-type': 'application/json',
// 'Authorization': `JWT ${userInfo.token}`
}
}
const { data } = await axios.post(
'/api/expenses/post/',
expenses,
config
)
dispatch({
type: ADD_EXPENSES_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: ADD_EXPENSES_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.response,
})
}
}
The dilemma is that I have copied it from other actions where it worked . I have also tried posting date using data manually using ThunderClient extention.(it is like insomnia or postman ) which mean there is no problem on the backend side.
Your thunk arguments are backwards. It should be (dispatch, getState)
export const addExpenses = (expenses) => async (dispatch, getState) => {

react TypeError: Cannot read property 'id' of undefined

From "orders" component, sending a order id to "update" component. Then trying to update "the status" of the order containing the id.
Logging the id value in the console works, but not setting a state with it.
"Update" component:
const UpdateStatus = (props) => {
const location = useLocation();
const [orderId, setOrderId] = useState(null);
const [status, setStatus] = useState("pending");
useEffect(() => {
setOrderId(location.state.id); // errors here
console.log(location.state.id) // but gives a valid id
}, [location]);
const handleChange = e => {
setStatus(e.target.value);
console.log(e.target.value)
}
const history = useHistory();
const handleClick = () => {
if (orderId){
axios.patch(`http://localhost:5000/orders/change-status/${orderId}`, {status: status}, {withCredentials: true})
.then((res) => {
console.log(res);
history.push("/get-orders");
})
}
}
return (
<div>
<h2> Update Order Status </h2>
<form>
<label>Change status to: </label>
<select name="status" id="order-status" onChange={handleChange}>
<option value="pending">Pending</option>
<option value="accepted">Accepted</option>
<option value="delivered">Delivered</option>
</select>
<br/><br/>
<input type="submit" value="Submit" onClick={handleClick}/>
</form>
</div>
);
}
"Orders" component:
const handleClick = orderId => {
history.push({
pathname: '/update-status',
state: { id: orderId }
});
}
Try something like:
useEffect(() => {
if(location?.state?.id)
setOrderId(location.state.id);
}, [location?.state?.id]);
Try this:
useEffect(() => {
setOrderId(location.state?.id);
..........
}, [location]);

TypeError: formData.set is not a function

Hii i am getting an error when i am trying to filling a value in the form then getting error like "form data is not a function" dont know whats going on wrong please help as soon as possible
error img https://ibb.co/xMy002L
addmovie.js
here is my addmovie form where i wrote my whole logic
import React,{useState} from 'react';
import Navbar from '../pages/Navbar';
import Footer from '../pages/Footer';
import {Link} from 'react-router-dom';
import {isAuthenticated} from '../Auth/index';
import {addMovie} from '../Admin/adminapicall';
const AddMovie = () => {
const {user,token} = isAuthenticated();
const [values,setValues] = useState({
movie_name:'',
actor:'',
country_of_origin:'',
duration:'',
director:'',
photo:'',
loading:false,
error:'',
addedMovie:'',
getRedirect:false,
formData:''
})
const {movie_name,actor,country_of_origin,duration,director,photo,loading,error,addedMovie,getRedirect,formData} = values;
const handleChange = name => event => {
const value = name === "photo" ? event.target.files[0] : event.target.value
formData.set(name,value);
setValues({...values,[name]:value})
};
const onSubmit = (e) => {
e.preventDefault();
setValues({...values,error:'',loading:true})
addMovie(user._id,token,formData).then(data =>{
if(data.error){
setValues({...values,error:data.error})
}else{
setValues({
...values,
movie_name:'',
actor:'',
country_of_origin:'',
duration:'',
director:'',
photo:'',
loading:false,
addedMovie: data.movie_name
})
}
})
}
const successMessage = () => (
<div className='alert alert-success mt-3'
style={{display : addedMovie ? '' : 'none'}}>
<h4>{addedMovie} added successfully</h4>
</div>
)
// const successMessage = () => {
// }
const addMovieForm = () => (
<form >
<span>Post photo</span>
<div className="form-group">
<label className="btn btn-block btn-success">
<input
onChange={handleChange("photo")}
type="file"
name="photo"
accept="image"
placeholder="choose a file"
/>
</label>
</div>
<div className="form-group">
<input
onChange={handleChange("movie_name")}
name="photo"
className="form-control"
placeholder="movie_name"
value={movie_name}
/>
</div>
<div className="form-group">
<input
onChange={handleChange("actor")}
name="photo"
className="form-control"
placeholder="actor"
value={actor}
/>
</div>
<div className="form-group">
<input
onChange={handleChange("duration")}
type="number"
className="form-control"
placeholder="duration"
value={duration}
/>
</div>
<div className="form-group">
<input
onChange={handleChange("country_of_origin")}
type="text"
className="form-control"
placeholder="country_of_origin"
value={country_of_origin}
/>
</div>
<div className="form-group">
<input
onChange={handleChange("director")}
type="text"
className="form-control"
placeholder="director"
value={director}
/>
</div>
<button type="submit" onClick={onSubmit} className="btn btn-success mb-2">
Add Movie
</button>
</form>
);
return (
<div>
<Navbar/>
<div className='container'style={{height:'0px'}}>
<Link to='/admin/dashboard'> <h1 className=' bg-info text-white p-4 text-decoration-none'>Admin Home</h1> </Link>
<div className='row bg-dark text-white rounded'>
<div className='col-md-8 offset-md-2'>
{successMessage()}
{addMovieForm()}
</div>
</div>
</div>
<Footer/>
</div>
)
}
export default AddMovie;
adminapicall.js
this is code where my frontend talk with backend
import {API} from '../backend';
//products calls
//add movie
export const addMovie = (userId,token,movie)=>{
return fetch(`${API}/movie/addMovie/${userId}`,{
method : "POST",
headers:{
Accept:'Application/json',
Authorization: `Bearer ${token}`
},
body:movie
}).then(response => {
return response.json()
})
.catch(err => console.log(err))
}
//get all movie
export const getAllMovies = () => {
return fetch(`${API}/movies`,{
method : "GET"
})
.then(response => {
return response.json();
})
.catch(err => console.log(err))
}
//get a movie
export const getMovie = movieId =>{
return fetch(`${API}/movie/${movieId}`,{
method : "GET"
})
.then(response => {
return response.json();
})
.catch(err => console.log(err))
}
//update movie
export const updateMovie = (movieId,userId,token,movie)=>{
return fetch(`${API}/movie/${movieId}/${userId}`,{
method : "PUT",
headers:{
Accept:'Application/json',
Authorization: `Bearer ${token}`
},
body:movie
}).then(response => {
return response.json()
})
.catch(err => console.log(err))
}
//delete movie
export const deleteMovie = (movieId,userId,token)=>{
return fetch(`${API}/movie/${movieId}/${userId}`,{
method : "DELETE",
headers:{
Accept:'Application/json',
Authorization: `Bearer ${token}`
}
}).then(response => {
return response.json()
})
.catch(err => console.log(err))
}
i think ur mistaken here,
const [values,setValues] = useState({
movie_name:'',
actor:'',
country_of_origin:'',
duration:'',
director:'',
photo:'',
loading:false,
error:'',
addedMovie:'',
getRedirect:false,
formData:'' // <-
})
const {movie_name,actor,country_of_origin,duration,director,photo,loading,error,addedMovie,getRedirect,formData} = values;
const handleChange = name => event => {
const value = name === "photo" ? event.target.files[0] : event.target.value
formData.set(name,value); // <-
setValues({...values,[name]:value})
};
const onSubmit = (e) => {
e.preventDefault();
setValues({...values,error:'',loading:true})
addMovie(user._id,token,formData).then(data =>{
// ^^^^^^^^ <-
if(data.error){
setValues({...values,error:data.error})
}else{
setValues({
...values,
movie_name:'',
actor:'',
country_of_origin:'',
duration:'',
director:'',
photo:'',
loading:false,
addedMovie: data.movie_name
})
}
})
You might wanted to do somethig like this,
const [values,setValues] = useState({
movie_name:'',
actor:'',
country_of_origin:'',
duration:'',
director:'',
photo:'',
loading:false,
error:'',
addedMovie:'',
getRedirect:false,
})
const {movie_name,actor,country_of_origin,duration,director,photo,loading,error,addedMovie,getRedirect} = values;
const handleChange = name => event => {
const value = name === "photo" ? event.target.files[0] : event.target.value
setValues({...values,[name]:value})
};
const onSubmit = (e) => {
e.preventDefault();
setValues({...values,error:'',loading:true})
addMovie(user._id,token,JSON.stringify(values)).then(data =>{
if(data.error){
setValues({...values,error:data.error})
}else{
setValues({
...values,
movie_name:'',
actor:'',
country_of_origin:'',
duration:'',
director:'',
photo:'',
loading:false,
addedMovie: data.movie_name
})
}
})
const [values,setValues] = useState({
movie_name:'',
actor:'',
country_of_origin:'',
duration:'',
director:'',
photo:'',
loading:false,
error:'',
addedMovie:'',
getRedirect:false,
formData:new FormData() <---- declare form, data like this
})
I know it's late but according to my study,
we need to check if we are on a server-side environment or client environment (browser).
we can check(for client-side), (process.browser == true) but since now it is deprecated we can use
**(typeof window !== 'undefined')**
const [values, setValues] = useState({
formData: typeof window !== 'undefined' && new FormData(),
// other values
});
Refer to https://github.com/zeit/next.js/issues/5354#issuecomment-520305040
Also,
If you're using Next.js newer versions, you can use getStaticProps or getServerSideProps instead of getInitialProps.

Conflicts between useEffect in react

I have to create component which fetch data with pagination and filters.
Filters are passed by props and if they changed, component should reset data and fetch it from page 0.
I have this:
const PaginationComponent = ({minPrice, maxPrice}) => {
const[page, setPage] = useState(null);
const[items, setItems] = useState([]);
const fetchMore = useCallback(() => {
setPage(prevState => prevState + 1);
}, []);
useEffect(() => {
if (page === null) {
setPage(0);
setItems([]);
} else {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
}, [page, minPrice, maxPrice]);
useEffect(() => {
setPage(null);
},[minPrice, maxPrice]);
};
.. and there is a problem, because first useEffect depends on props, because I use them to filtering data and in second one I want to reset component. And as a result after changing props both useEffects run.
I don't have more ideas how to do it correctly.
In general the key here is to move page state up to the parent component and change the page to 0 whenever you change your filters. You can do it either with useState, or with useReducer.
The reason why it works with useState (i.e. there's only one rerender) is because React batches state changes in event handlers, if it didn't, you'd still end up with two API calls. CodeSandbox
const PaginationComponent = ({ page, minPrice, maxPrice, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [page, minPrice, maxPrice]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const App = () => {
const [page, setPage] = useState(0);
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
setPage(0);
}}
/>
</div>
<PaginationComponent minPrice={minPrice} maxPrice={maxPrice} page={page} setPage={setPage} />
</div>
);
};
export default App;
The other solution is to use useReducer, which is more transparent, but also, as usual with reducers, a bit heavy on the boilerplate. This example behaves a bit differently, because there is a "set filters" button that makes the change to the state that is passed to the pagination component, a bit more "real life" scenario IMO. CodeSandbox
const PaginationComponent = ({ tableConfig, setPage }) => {
const [items, setItems] = useState([]);
useEffect(() => {
const { page, minPrice, maxPrice } = tableConfig;
get(page, minPrice, maxPrice).then(response => setItems(response));
}, [tableConfig]);
return (
<div>
{items.map(item => (
<div key={item.id}>
{item.id}, {item.name}, ${item.price}
</div>
))}
<div>Page: {tableConfig.page}</div>
<button onClick={() => setPage(v => v - 1)}>back</button>
<button onClick={() => setPage(v => v + 1)}>next</button>
</div>
);
};
const tableStateReducer = (state, action) => {
if (action.type === "setPage") {
return { ...state, page: action.page };
}
if (action.type === "setFilters") {
return { page: 0, minPrice: action.minPrice, maxPrice: action.maxPrice };
}
return state;
};
const App = () => {
const [tableState, dispatch] = useReducer(tableStateReducer, {
page: 0,
minPrice: 25,
maxPrice: 50
});
const [minPrice, setMinPrice] = useState(25);
const [maxPrice, setMaxPrice] = useState(50);
const setPage = useCallback(
page => {
if (typeof page === "function") {
dispatch({ type: "setPage", page: page(tableState.page) });
} else {
dispatch({ type: "setPage", page });
}
},
[tableState]
);
return (
<div>
<div>
<label>Min price:</label>
<input
value={minPrice}
onChange={event => {
const { value } = event.target;
setMinPrice(parseInt(value, 10));
}}
/>
</div>
<div>
<label>Max price:</label>
<input
value={maxPrice}
onChange={event => {
const { value } = event.target;
setMaxPrice(parseInt(value, 10));
}}
/>
</div>
<button
onClick={() => {
dispatch({ type: "setFilters", minPrice, maxPrice });
}}
>
Set filters
</button>
<PaginationComponent tableConfig={tableState} setPage={setPage} />
</div>
);
};
export default App;
You can use following
const fetchData = () => {
get(page, minPrice, maxPrice)
.then(response => setItems(response));
}
// Whenever page updated fetch new data
useEffect(() => {
fetchData();
}, [page]);
// Whenever filter updated reseting page
useEffect(() => {
const prevPage = page;
setPage(0);
if(prevPage === 0 ) {
fetchData();
}
},[minPrice, maxPrice]);

Resources