action is not dispatching in react-redux - reactjs

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) => {

Related

I want to send text entered in modal in React.js to Backend

I am using React.js to create the front-end side of a web application that can control home appliances.
What I want to achieve is
I want to send the text entered in the modal to Backend.
At that time, I want to prevent the screen reload.
Issue is
When I put the text in the modal and press the Submit button, the page reload happens and I can't see what was sent in the console.
Please see the video below for details.
https://youtu.be/_ppCNBTBIvc
AddRoomModal.js
import Modal from 'react-bootstrap/Modal';
const cookies = new Cookies();
const AddRoomModal = (props) => {
const [room_name, setRoomName] = useState("");
const addRoom = (e) => {
setRoomName(e.target.value);
}
const clickSubmit = (e) => {
AddRoom(e.target.value);
}
const building_state = useSelector(state => state.building_state.building_state);
console.log(building_state);
const url_forgetroomname = {
"condo": "aaa.com",
"office": "bbb.com",
"house": "ccc.com"
}[building_state]
const AddRoom = async(data) => {
console.log("Body sent to server", {
home_rooms: room_name,
})
await axios.post(url_forgetroomname,
{
home_rooms: room_name,
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
alert('Succeded add room!');
console.log('Succeded add room!');
})
.catch(err => {
alert('Missed add room!');
console.log(err);
console.log('Missed add room!');
});
}
const getRoomName = async(data) => {
await axios.get(url_forgetroomname,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
console.log(result.data)
setRoomName(result.data.home_rooms);
})
.catch(err => {
console.log(err);
});
}
return (
<>
<Modal show={props.show}>
<Modal.Body className="light_modal_body" >
<div className="light_modal_body">
<div className="range-container col-12">
</div>
<h3>Add Room</h3>
<form>
<input type="text" onChange={addRoom} value={room_name}/>
<div className="done_button">
<button className='btn btn-block btn-primary' type="submit" onClick={clickSubmit}>
OK
</button>
</div>
</form>
<div className="done_button">
<button onClick={props.handleHide} className='btn btn-block btn-danger'>Cancel</button>
</div>
</div>
</Modal.Body>
</Modal>
</>
);
}
export default AddRoomModal;
HeaderForSettingGetRoomName.js
import AddRoomModal from '../AddRoomModal';
const cookies = new Cookies();
const HeaderForSettingGetRoomName = (props) => {
const isLoggedInOn = useSelector(state => state.user.isLoggedIn);
// modal
const [show, setShow] = useState();
// Show Modal
const handleShow = () => {
setShow(true);
}
// Hide modal
const handleHide = () => {
setShow(false);
}
const building_state = useSelector(state => state.building_state.building_state);
console.log(building_state);
return (
<div>
<AddRoomModal
show={show} handleHide={handleHide}
/>
<div className="header">
<Link to={`/setting`} className=""> <img className="header_menu_back_leftside" src={ic_back} alt="" />
</Link>
<img className="header_logo" src={logo_image} />
<img className="ic_add_white" src={ic_add_white} onClick={handleShow}/>
</div>
</div>
);
}
export default HeaderForSettingGetRoomName;
You can disable the default form behaviour (submitting the form) by calling preventDefault on the submit event
const clickSubmit = (e) => {
e.preventDefault();
AddRoom(e.target.value);
}

New to react...state not updating on login

Looking for a gentle push in the right direction. Working on a react project and using hooks. Yes, have read documents, but not fully understanding yes.
The ask is about a login routine. Login form works, but does not reflect failed login state until repeat submission; so I am getting previous state, not current.
Tried useEffect...no change. Code follows, and appreciated any constructive feedback:
From the Login form
import React, { useState, useEffect, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Redirect } from 'react-router-dom'
import getAuthStatus from 'common/cyclone/auth/authenticated.status'
import {
authenticateByLogin,
authenticationSelector,
} from '../services/auth.service'
import Form from 'react-validation/build/form'
import Input from 'react-validation/build/input'
import CheckButton from 'react-validation/build/button'
const required = (value) => {
if (!value) {
return (
<div className="alert alert-danger" role="alert">
This field is required!
</div>
)
}
}
const Login = (props) => {
const form = useRef()
const checkBtn = useRef()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [errorMessage, setErrorMessage] = useState(null)
const dispatch = useDispatch()
const { session, hasErrors } = useSelector(authenticationSelector)
useEffect(() => {}, [session, hasErrors])
const onChangeUsername = (e) => {
const username = e.target.value
setUsername(username)
}
const onChangePassword = (e) => {
const password = e.target.value
setPassword(password)
}
const handleLogin = (e) => {
e.preventDefault()
setLoading(true)
form.current.validateAll()
if (checkBtn.current.context._errors.length === 0) {
dispatch(authenticateByLogin(username, password))
.then(() => {
setLoading(false)
if (hasErrors) {
setErrorMessage(session.error.message)
} else {
//props.history.push('/profile')
// window.location.reload()
}
})
.catch(() => {
setLoading(false)
})
} else {
setLoading(false)
}
}
if (session.success) {
//console.log(session.success)
return <Redirect to="/profile" />
}
if (getAuthStatus()) {
return <Redirect to="/profile" />
}
return (
<div className="col-md-12">
<div className="card card-container">
<img
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
alt="profile-img"
className="profile-img-card"
/>
<Form onSubmit={handleLogin} ref={form}>
<div className="form-group">
<label htmlFor="username">Username</label>
<Input
type="text"
className="form-control"
name="username"
value={username}
onChange={onChangeUsername}
validations={[required]}
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<Input
type="password"
className="form-control"
name="password"
value={password}
onChange={onChangePassword}
validations={[required]}
/>
</div>
<div className="form-group">
<button className="btn btn-primary btn-block" disabled={loading}>
{loading && (
<span className="spinner-border spinner-border-sm"></span>
)}
<span>Login</span>
</button>
</div>
{hasErrors && (
<div className="form-group">
<div className="alert alert-danger" role="alert">
{errorMessage}
</div>
</div>
)}
<CheckButton style={{ display: 'none' }} ref={checkBtn} />
</Form>
</div>
</div>
)
}
export default Login
From the auth slice:
/** Third Party Libraries */
import { createSlice } from '#reduxjs/toolkit'
import qs from 'qs'
/**Axios Wrapper...nothing fancy here*/
import CycloneAPIInstance from 'common/cyclone/api/api.client'
import CycloneConfig from 'config/base'
/** Main API Server URL */
const API_URL = CycloneConfig.API_URL
const session = JSON.parse(localStorage.getItem('authentication'))
/** Define Initial State */
export const initialState = session
? {
hasErrors: false,
session: session,
}
: {
hasErrors: false,
session: [],
}
/** Define Slice */
const authenticationSlice = createSlice({
name: 'authentication',
initialState,
reducers: {
authenticateUser: (state) => {
state.hasErrors = false
},
authenticateUserSuccess: (state, { payload }) => {
state.hasErrors = false
state.session = payload
console.log(state.session)
},
authenticateUserFailure: (state, { payload }) => {
state.hasErrors = true
state.session = payload
},
deauthenticateUser: (state) => {
state.session = []
},
},
})
export const {
authenticateUser,
authenticateUserSuccess,
authenticateUserFailure,
deauthenticateUser,
} = authenticationSlice.actions
export const authenticationSelector = (state) => state.authentication
export default authenticationSlice.reducer
export function authenticateByLogin(user_name, user_password) {
let requestBody = {
user_name: user_name,
user_password: user_password,
}
let config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
return async (dispatch) => {
dispatch(authenticateUser())
try {
const response = await CycloneAPIInstance.post(
API_URL + 'auth/login',
qs.stringify(requestBody),
config
)
//console.log(response.data.content)
localStorage.setItem('session', JSON.stringify(response.data.content))
dispatch(authenticateUserSuccess(response.data.content))
} catch (error) {
//console.log(JSON.stringify(error.response.data))
dispatch(authenticateUserFailure(error.response.data))
}
}
}
export function deauthenticateByLogout() {
return async (dispatch) => {
dispatch(deauthenticateUser())
localStorage.removeItem('session')
}
}
Try to set the message when hasError change
useEffect(()=> {
if(hasErrors) {
setErrorMessage(session.error.message)
}
}, [hasErrors]);
This is quite some code so I just skipped through to fix the problem and not pick everything apart. My best guess is this part:
dispatch(authenticateByLogin(username, password))
.then(() => {
setLoading(false)
if (hasErrors) {
setErrorMessage(session.error.message)
} else {
//props.history.push('/profile')
// window.location.reload()
}
})
.catch(() => {
setLoading(false)
})
Here you execute the async authentication and then do thing based on "hasError". This "hasError" comes from a hook. We (or at least I) have no clear idea how this is managed. Thing is you cant be 100% sure that hasError is really trustworthy at the point you check it in the then-block. The hook might run just after the next render, which explains why you see the previous state, not the actual one.
Best guess would be to use the response from that async call, because there should be one => authenticate.then((response) => if(response.hasError) ...)
With this check you can set your own error state and your component should be up-to-date
Let me know if this fixes your error.

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.

I can't update array on MongoDB with React and Redux

My it's super simple but I get stuck.
I need to update an array on MongoDB with fetch PUT
I tested it with postman and works perfectly but my app React + Redux doesn't work
import React, { Fragment, useEffect, useState } from "react";
import PropTypes from "prop-types";
import "materialize-css/dist/css/materialize.min.css";
import M from "materialize-css/dist/js/materialize.min.js";
import config from "react-global-configuration";
import Preloader from "../layout/Preloader";
import { connect } from "react-redux";
import { getColors, updateColors } from "../../redux/actions/settingsActions";
const Settings = ({
setting: { settings, loading },
getColors,
updateColors
}) => {
const [HighPColor, setHighPColor] = useState("");
const [NormalPColor, setNormalPColor] = useState("");
const [LowPColor, setLowPColor] = useState("");
useEffect(() => {
M.AutoInit();
getColors();
//eslint-disable-next-line
}, []);
const onSubmit = () => {
const updColors = {
id: settings[0]._id,
colors: [
{
_id: colorsArray.colors[0]._id,
HighPColor,
NormalPColor,
LowPColor
}
]
};
updateColors(updColors);
M.toast({ html: "Settings updated" });
};
if (loading || settings === null) {
return <Preloader />;
}
const colorsArray = settings[0];
return (
<Fragment>
<div id="color-settings" className="container">
<div className="">
<h4>Set Priorities Colors </h4>
<div className="row">
<div>High Priority</div>
<div className="input-field">
<input
type="text"
name="highPColor"
defaultValue={colorsArray.colors[0].HighPColor}
onChange={e => setHighPColor(e.target.value)}
/>
</div>
</div>
<div className="row">
<div>Normal Priority</div>
<div className="input-field">
<input
type="text"
name="normalPColor"
defaultValue={colorsArray.colors[0].NormalPColor}
onChange={e => setNormalPColor(e.target.value)}
/>
</div>
</div>
<div className="row">
<div>Low Priority</div>
<div className="input-field">
<input
type="text"
name="lowPColor"
defaultValue={colorsArray.colors[0].LowPColor}
onChange={e => setLowPColor(e.target.value)}
/>
</div>
</div>
</div>
<div className="">
<a
href="#!"
onClick={onSubmit}
className="modal-close waves-effect blue btn"
>
Enter
</a>
</div>
</div>
</Fragment>
);
};
Settings.propTypes = {
setting: PropTypes.object.isRequired,
getColors: PropTypes.func.isRequired,
updateColors: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
setting: state.settings
});
export default connect(mapStateToProps, { getColors, updateColors })(Settings);
I take everything from some inputs values that work perfectly
Redux action:
export const updateColors = colors => async dispatch => {
try {
setLoading();
const res = await fetch(`/api/settings/${colors.id} `, {
method: "PUT",
body: JSON.stringify(colors),
headers: {
"Content-Type": "application/json"
}
});
const data = await res.json();
dispatch({
type: UPDATE_COLORS,
payload: data
});
} catch ...
Redux reducer:
case UPDATE_COLORS:
return {
...state,
settings: state.settings.map(setting =>
setting._id === action.payload._id ? action.payload : setting
),
loading: false
};
it gives me back:
UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property `NormalPColor` of 'undefined' or 'null'.
[0] at router.put (C:\Users\Marco\Desktop\React-Course\to-do-list\routes\settings.js:81:7)
This happens despite I commented the line 81
Any Idea of my mistakes?
thanks!
It sounds odd but now works I don't know what I have done but now updates

Loading state does not change to true in fetch method

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]
)
...

Resources