I'm trying to send and see my data status in my console log, when I click on 'Cancel' button, the status will be change by status:cancel, if I click on 'finish' button then the status is status:finish and same idea for the last one with save. Here what I've try to do but the status is not working
export default function App() {
const [data, setData] = useState({
status: ""
});
const [status, setStatus] = useState("");
const saveState = () => {
setStatus("saved");
};
const finishState = () => {
setStatus("finish");
};
const pendingState = () => {
setStatus("pending");
};
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={saveState}>Save</button>
<button onClick={finishState}> Finish</button>
<button onClick={pendingState}> Cancel</button>
</form>
</>
);
}
you can use simple setsate
export default function App() {
const [data, setData] = useState({
status: "",
});
const [status, setStatus] = useState("");
useEffect(() => {
axios
.post("")
.then((res) => {
console.log(res);
setInvitations(res.data.invitations[0]);
})
.catch((err) => {
console.log(err);
});
}, []);
function submit(e) {
e.preventDefault();
axios
.post("", {
status: data.status,
})
.then((res) => {
console.log(res.data);
});
}
return (
<>
<form onSubmit={(e) => submit(e)}>
<button onClick={() => setStatus({ status: "saved" })}>Save</button>
<button onClick={() => setStatus({ status: "finish" })}> Finish</button>
<button onClick={() => setStatus({ status: "pending" })}>
{" "}
Cancel
</button>
</form>
</>
);
}
You are using setStatus to change the status, but you are using axios.post() on your data.status
You need to either setData in your 3 functions
const saveState = () => {
setData({status:"saved"});
};
const finishState = () => {
setData({status:"finish"});
};
const pendingState = () => {
setData({status:"pending"});
};
or you can change axios.post to:
function submit(e) {
e.preventDefault();
axios
.post("", {
status: status //This is the change
})
.then((res) => {
console.log(res.data);
});
}
Related
I have two components.
DropDownForRoomChangeCondo.js js that displays radio buttons. DiscoverCondoRoom.js displays DropDownForRoomChangeCondo.js.
What I want to achieve is In DropDownForRoomChangeCondo.js, When sending a request to the backend with handleChange (when switching the radio button) I want to change the screen display by calling getDevices(); in DiscoverCondoRoom.js. (Since the assignment of the room where the device is installed changes when the radio button is switched, I want to update the display)
Issue/error message
Currently, when sending a request to the backend with handleChange (when switching the radio button) Display update does not occur.
DropDownForRoomChangeCondo.js
import Dropdown from 'react-bootstrap/Dropdown';
const DropDownForRoomChangeCondo = (item) => {
const history = useHistory();
const [devices, setDevices] = useState([]);
const handleChange = e => {
setVal(e.target.name);
setDeviceRoomName(e.target.name);
}
const setDeviceRoomName = async(data) => {
console.log("Body sent to server", {
attributes:
[
{
entity_id : item.item.entity_id,
room_name: data
}
]
})
await axios.post('xxx.com',
{
attributes:
[
{
entity_id : item.item.entity_id,
room_name: data
}
]
},
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
console.log('Set Device Room Name!');
getDevices();
})
.catch(err => {
console.log(err);
console.log('Missed Set Device Room Name!');
});
}
const getDevices = async(data) => {
await axios.get('xxx.com',
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
console.log(result.data)
console.log("bbbbbbbbbbb")
setDevices(result.data.attributes);
})
.catch(err => {
console.log(err);
});
}
const keys = [
"camera",
"climate",
"cover",
"light",
"lock",
"sensor",
"switch",
];
const entities = keys
.map((key) => (devices[key] || []).map((e) => ({ ...e, key })))
.flat();
const roomNames = [...new Set(entities.map((entity) => entity.room_name))];
const [val, setVal] = useState(item.item.room_name);
console.log(val)
console.log(typeof(val))
const CustomToggle = React.forwardRef(({ children, onClick }, ref) => (
<a
href=""
ref={ref}
onClick={(e) => {
e.preventDefault();
onClick(e);
}}
>
{children}
<img className="ic_edit" src={ic_edit} />
</a>
));
useEffect(() => {
getDevices();
},[]);
return (
<>
<div className="">
<p>{item.item.room_name}</p>
<Dropdown className="room_change_dropdown_top">
<Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" />
<Dropdown.Menu className="room_change_dropdown">
<Dropdown.Item className="room_change_dropdown_item">
{roomNames.map((room_names, i) => (
<div className="flex_radio">
<input
className="room_change_radio"
type="radio"
value={room_names}
name={room_names}
onChange={handleChange}
checked={val === room_names}
/>
<p className="drop_down_p">{room_names}</p>
</div>
))}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
</>
);
}
export default DropDownForRoomChangeCondo;
DiscoverCondoRoom.js
const DiscoverCondoRoom = () => {
const history = useHistory();
const [devices, setDevices] = useState([]);
const getDevices = async(data) => {
await axios.get('xxx.com',
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${cookies.get('accesstoken')}`
},
})
.then(result => {
setDevices(result.data.attributes);
})
.catch(err => {
console.log(err);
});
}
useEffect(() => {
getDevices();
},[]);
const lll = Object.keys(devices);
const object_device_value = Object.values(devices).flat();
const keys = [
"camera",
"climate",
"cover",
"light",
"lock",
"sensor",
"switch",
];
const entities = keys
.map((key) => (devices[key] || []).map((e) => ({ ...e, key })))
.flat();
const roomNames = [...new Set(entities.map((entity) => entity.room_name))];
return (
<>
<div className="container condo_container">
{entities.map((entity, i) => (
<DropDownForRoomChangeCondo item={entity} />
))}
</div>
</>
);
}
};
export default DiscoverCondoRoom;
You need to pass your getDevices Method as a prop to your dropdown component.
<DropDownForRoomChangeCondo item={entity} getDevices={getDevices} />
Then inside your DropDown Component, call the getDevices Method at your desired place by calling props.getDevices().
Also, i would suggest to define props like so:
const DropDownForRoomChangeCondo = (props) => {
const history = useHistory();
const [devices, setDevices] = useState([]);
…
And then access item by pointing to props.item
My parent component use hook useEffect for get data from API and pass props to child component.
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
useEffect(() => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
}, []);
return <Child adsData={adsData} />;
};
My Child component has handleDeleteClick function for delete request from API
const Child = () => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
return (
<div>
// array.map Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Delete request works successfully, but my list not updated.
How update my items list after deleted item?
You would need to pass another function that is called when a delete is executed. Something like:
const ParentComoponent = () => {
const [adsData, setAdsData] = useState([]);
const fetchData = () => {
api
.get(`MyUrl`, { headers: authHeader() })
.then((res) => {
console.log(res);
setAdsData(res.data.data);
})
.catch((err) => {
console.log(err);
});
};
const onDelete = () => {
fetchData();
};
useEffect(() => {
fetchData();
}, []);
return <Child adsData={adsData} onDelete={fetchData} />;
};
const Child = (props) => {
const [deletedItem, setDeletedItem] = useState("");
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, { params: { id: id } })
.then((res) => {
console.log(res);
props.onDelete();
})
.catch((err) => console.log(err));
};
return (
<div>
// Items list
<a
href=""
onClick={(e) => handleDeleteClick(e, ads.id)}
className="tables__link"
>
Delete
</a>
</div>
);
};
Put your delete function in the parent and pass it to the child. Then after deleting, update your list in the parent.
<ParentComponent>
const [adsData, setAdsData] = useState([]);
const handleDeleteClick = (e, id) => {
e.preventDefault();
axios
.delete(`MyUrl`, {params: {id: id}})
.then(res => {
console.log(res)
//TODO:: Implement list.pop or similar
})
.catch(err => console.log(err));
};
useEffect(() => {
api.get(`MyUrl`, { headers: authHeader() })
.then(res => {
console.log(res);
setAdsData(res.data.data);
})
.catch(err => {
console.log(err);
})
}, []);
return (
<Child
adsData={adsData}
handleClick={handleDeleteClick}
/>
)
</ParentComponent>
return (
<div>
// array.map Items list
<a href="" onClick={(e) =>
handleDeleteClick(e, ads.id)}className="tables__link">Delete</a>
</div>
)```
Alternating between the 2 buttons will display first names or last names, but pressing them together really fast will chain requests and will combine the two. How can I make create a check, and only display the names from the button that was pressed last
export default function App() {
const [name, setName] = useState();
return (
<div className="App">
<button onClick={() => setName("first_name")}>1</button>
<button onClick={() => setName("last_name")}>2</button>
<Users name={name} />
</div>
);
}
export default function Users({ name }) {
const [users, setUsers] = useState([]);
useEffect(() => {
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
})
.catch((e) => {
console.log(e);
});
}, [name]);
return <div className="Users">{users}</div>;
}
Here is a great article by Dan Abramov about the useEffect hook in which he also talks about how to handle race cases- https://overreacted.io/a-complete-guide-to-useeffect/#speaking-of-race-conditions
To solve your issue, create a variable like let didCancel = false at the start of useEffect. Then, you have to return a function from useEffect, which automatically runs at the time when the name changes next time. In that function set didCancel to true. Now, you have to handle fetch response only if didCancel is false. This way, you are discarding all fetch responses received from second-last, third-last, etc. button presses, and handling fetch response only from the last button press.
Here is updated useEffect code:-
useEffect(() => {
let didCancel = false;
setUsers([]);
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
if (!didCancel) {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers((prev) => [...prev, ...allUsers]);
}
})
.catch((e) => {
console.log(e);
});
return () => {
didCancel = true;
};
}, [name]);
return <div className="Users">{users}</div>;
}
you have to create a loading state, and the user should not be able to send a new request until the data is received... you can create a hook for this or use SWR:
let me give you an example:
function Users(usersList) {
return (
<ul>
{usersList.map((user, key) => (
<li key={key}>{user}</li>
))}
</ul>
);
}
const useFetchUsers = (name) => {
const [isLoading, setIsLoading] = React.useState(true);
const [error, setError] = React.useState(null);
const [data, setData] = React.useState([]);
React.useEffect(() => {
setIsLoading(true);
setError(null);
fetch('https://blahblahblah.com/api/users')
.then((res) => res.json())
.then((response) => setData(response))
.catch((err) => setError(err))
.finally(() => setIsLoading(false));
}, [name]);
return {
isLoading,
error,
data,
};
};
function App() {
const [name, setName] = React.useState('Tom');
const { isLoading, error, data } = useFetchUsers(name);
const handleSubmitName = (name) => {
if (isLoading) alert('wait!');
else setName(name);
};
if (error) return <>an error occured</>;
if (data)
return (
<>
<button onClick={() => handleSubmitName('first_name')}>1</button>
<button onClick={() => handleSubmitName('last_name')}>2</button>
<Users name={name} />
</>
);
}
hint/note: it's just pseudocode and there are some tools to do data fetching + caching.
The problem is in this line setUsers((prev) => [...prev, ...allUsers]);. You are assuming that prev is [], but when the second request is resolve prev has data, that is why you see the request are combined:
I recommend to change your useEffect block to avoid the problem you are facing:
useEffect(() => {
axios({
method: "GET",
url: `https://reqres.in/api/users?delay=1`
})
.then((res) => {
const allUsers = res.data.data.map((user) => <p>{user[name]}</p>);
setUsers(...allUsers); //--> with the last name's value
})
.catch((e) => {
console.log(e);
});
}, [name]);
I'm using an axios call to a database to get "about me" data, for client to update. DB is connected properly, as I am able to log in just fine, I've isolated this issue pretty well to my GET request.
My context provider file:
import React, { useState } from 'react'
import axios from 'axios'
export const UserContext = React.createContext()
const userAxios = axios.create()
userAxios.interceptors.request.use((config) => {
const token = localStorage.getItem("token")
config.headers.Authorization = `Bearer ${token}`
return config
})
const UserProvider = (props) => {
const initState = {
user: JSON.parse(localStorage.getItem("user")) || {},
token: localStorage.getItem("token") || "",
authErrMsg: ""
}
const [userState, setUserState] = useState(initState)
const [dataState, setDataState] = useState({
bioData: []
})
const login = credentials => {
axios.post("/auth/login", credentials)
.then(res => {
const { user, token } = res.data
localStorage.setItem("user", JSON.stringify(user))
localStorage.setItem("token", token)
setUserState(res.data)
})
.catch(err => handleAuthErr(err.response.data.errMsg))
}
const handleAuthErr = errMsg => {
setUserState(prevUserState => ({
...prevUserState,
authErrMsg: errMsg
}))
}
const logout = () => {
localStorage.removeItem("token")
localStorage.removeItem("user")
setUserState({
user: {},
token: "",
authErrMsg: ""
})
}
const getData = () => {
axios.get('/info/bio')
.then(res => {
setDataState(prevData => ({
...prevData,
bioData: res.data
}))
})
.catch(err => {
console.log(err)
})
}
const deleteBio = (id) => {
userAxios.delete(`/api/bio/${id}`)
.then(res => {
setDataState(prevData => ({
...prevData,
bioData: dataState.bioData.filter(bio => bio._id !== id)
}))
})
.catch(err => console.log(err.response.data.errMsg))
}
const addBio = (newText) => {
const newBio = {
bioText: newText
}
userAxios.post('/api/bio', newBio)
.then(res => {
getData()
})
.catch(err => console.log(err))
}
const editBio = (update, id) => {
const updatedBio = {
bioText: update
}
userAxios.put(`/api/bio/${id}`, updatedBio)
.then(res => {
console.log(res.data, 'edited')
getData()
})
.catch(err => console.log(err))
}
return (
<UserContext.Provider
value={{
user: userState.user,
token: userState.token,
authErrMsg: userState.authErrMsg,
login: login,
logout: logout,
getData: getData,
dataState: dataState,
editBio: editBio,
deleteBio: deleteBio,
addBio: addBio
}}>
{props.children}
</UserContext.Provider>
)
}
export default UserProvider
Here's my Bio component. The loading effect never changes because for some reason, no "bioData" is saving, in the provider. Tested it with that little button/handleClick and coming up an empty array.
import React, {useContext, useState, useEffect} from 'react'
import { UserContext } from './context/userProvider'
const Bio = () => {
const { token, editBio, dataState: {bioData} } = useContext(UserContext)
const [loader, setLoader] = useState('Loading')
useEffect(() => {
if(bioData[0]?._id === undefined){
setLoader('Loading')
}else {
setLoader(bioData[0]?._id)
}
})
// let initText = bioData[0].bioText
const [bioText, setBioText] = useState("initText")
const handleChange = (e) => {
setBioText(e.target.value)
}
const handleUpdate = () => {
editBio(bioText, bioData[0]._id)
alert`Bio successfully updated. :)`
}
const handleClick = () => {
console.log(bioData)
}
return (
<div className='bio'>
<h1>About Me</h1>
<div className='bio-content'>
{loader === 'Loading' ?
<div>
<p>Loading...</p>
<button onClick={handleClick}>thing</button>
</div>
:
<>
{token ?
<div className="editBio">
<p>edit mee</p>
</div>
:
<h4>{bioData[0].bioText}</h4> }
</>
}
</div>
</div>
)
}
export default Bio
Thanks in advance guys! Let me know if I can post routes or anything that might be helpful.
Relatively new to using hooks, but I create this useFetch hook and I am trying to sign in a user after getting their information from text fields, I am able to create a successful request by hard coding {email:someemail, password: somepassword} but the issue arises when I am trying to grab the input from my state.
The error states I am missing a parameter
Here is my component:
const Login: FunctionComponent = (props) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
// #ts-ignore
const postData =useData({email:email, password:password})
const thisWorks = useData({email:"someEmail", password:"somePassword"})
return (
<>
{console.log(thisWorks)}
<TextFieldComponent
isRequired={true}
label={'Email'}
value={email}
// #ts-ignore
handleChange={(e)=> setEmail(e.target.value) }
/>
<TextFieldComponent
isRequired={true}
label={'Password'}
value={password}
// #ts-ignore
handleChange={(e)=> setPassword(e.target.value) }
/>
<Button
onClick={() => postData}
text="Login"
/>
</>
)
}
And here is my query:
const useData = (data: loginInfo): QueryType[] | string => {
const fetched = useFetch<Query>(
"example.com",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}
);
if (fetched.status === "loading") {
return fetched.status;
} else if (fetched.status === "loaded") {
const { payload } = fetched;
// #ts-ignore
return payload;
} else if (fetched.status === "error") {
return fetched.status;
}
return "Error";
};
my useFetch Hook:
const useFetch = <T>(url: string, headers?: Header | any) => {
const isCurrent = useRef(true);
const [result, setResult] = useState<Service<T>>({
status: "loading",
});
useEffect(
() => () => {
// called when the component is going to unmount
isCurrent.current = false;
},
[]
);
useEffect(() => {
fetch(url, headers)
.then((resp) => resp.json())
.then((response) => setResult({ status: "loaded", payload: response }))
.catch((error) => setResult({ status: "error", error }));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return result;
};
I'm curious to ask,
will you send many API requests while you typing email and password?
<Button
onClick={() => postData}
text="Login"
/>
this code doesn't seem to work, postData is not a function, just an object
may be you can modify your code like this (I'm not tested just sample code)
your component
const Login: FunctionComponent = (props) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const login = useFetch('http://path/to/login', { method: 'POST' })
React.useEffect(() => {
if (login.status === 'loaded') {
// do your stuff
}
}, [login.status])
const handleLogin = () => {
login.setPayload({ email, password })
}
if (login.status === 'loading') {
return <div>Loading...</div>
}
if (login.status === 'error') {
return <div>{login.error}</div>
}
return (
<>
<TextFieldComponent
isRequired={true}
label={'Email'}
value={email}
// #ts-ignore
handleChange={(e)=> setEmail(e.target.value) }
/>
<TextFieldComponent
isRequired={true}
label={'Password'}
value={password}
// #ts-ignore
handleChange={(e)=> setPassword(e.target.value) }
/>
<Button
onClick={handleLogin}
text="Login"
/>
</>
)
}
useFetch
const useFetch = <T>(url: string, options: RequestInit) => {
const [payload, setPayload] = React.useState({})
const [start, setStart] = React.useEffect(false)
useEffect(() => {
if (!start) {
return
}
fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options.headers
},
body: JSON.stringify(payload),
})
.then((resp) => resp.json())
.then((response) => setResult({ status: "loaded", payload: response }))
.catch((error) => setResult({ status: "error", error }))
.finally(() => setStart(false))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url, start, payload, options]);
return {
...result,
setPayload: (payload) => {
setPayload(payload)
setStart(true)
}
}
}
This can be written using the useEffect hook
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [status, setStatus] = React.useState("loading");
useEffect(async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
setStatus("success");
} catch (error) {
setStatus("error");
}
});
return [status, response];
};