Every time I emit a message from another component, I can't get the full list of messages. Here is the hook and view component:
export function useChat() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = openSocket("http://localhost:3003");
socket.on("chat message", msg => {
const newState = update(messages, { $push: [msg] });
setMessages(newState);
});
}, []);
return { messages };
}
Unfortunately the state doesn't persist and shows always the last message:
export const HookSockets = () => {
const { messages } = useChat();
return (
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
</div>
);
};
If I do this the regular way, everything works as intended:
export class ClassSockets extends Component {
state = {
socket: openSocket("http://localhost:3003"),
messages: [],
message: ""
};
componentDidMount() {
this.state.socket.on("chat message", msg => {
const newState = update(this.state, {
messages: { $push: [msg] }
});
this.setState(newState);
});
}
handleClick = () => {
this.state.socket.emit("chat message", this.state.message);
this.setState({ message: "" });
};
handleChange = event => {
this.setState({ message: event.target.value });
};
render() {
return (
<div>
<div>Sockets</div>
<div>{this.state.messages}</div>
<input
type="text"
value={this.state.message}
onChange={this.handleChange}
/>
<button onClick={this.handleClick}>Send Message</button>
</div>
);
}
}
Since you have written your useEffect to execute on initial mount of component, it creates a closure which references the initial value of messages and even if the messages update, it will still refer to the same value on subsequent calls
You should instead configure the useEffect to run on initial mount and messages change
export function useChat() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = openSocket("http://localhost:3003");
socket.on("chat message", msg => {
const newState = update(messages, { $push: [msg] });
setMessages(newState);
});
}, [messages]);
return { messages };
}
or else you could use the callback pattern to update state
export function useChat() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = openSocket("http://localhost:3003");
socket.on("chat message", msg => {
setMessages(prevMessages => update(prevMessages, { $push: [msg] }););
});
}, []);
return { messages };
}
As you are writing socket handler inside the useEffect() with an empty array, So this effect will run only once when your component will mount for the first time. The socket.on() function (or closure) will memorize the initial value of the messages and even if the messages gets change the socket.on() closure will still refer to its initial value. Solution for this problem will be to register our messages to the dependency array of effect.
export function useChat() {
const [messages, setMessages] =
useState([]);
useEffect(() => {
const socket = openSocket("http://localhost:3003");
socket.on("chat message", msg => {
const newState = update(messages, { $push: [msg] });
setMessages(newState);
}); }, [messages]);
return { messages };
}
Here a new problem you will encounter that each time messages get changed a new socket with "chat message" handler is created which may result unexpected and addition code to run multiple times. To solve that issue you will have to de-register the earlier handler. And I'll recommend you to create socket only once (e.g. inside App.js) and pass it as a props.
export function useChat(socket) {
const [messages, setMessages] = useState([]);
useEffect(() => {
socket.on("chat message", msg => {
const newState = update(messages, { $push: [msg] });
setMessages(newState);
});
//De-register old handler
return function(){
socket.off("chat message") } }, [messages]);
return { messages }; }
Related
I'm trying to implement "scroll to bottom" function. Here is my code:
const messagesEndRef = useRef();
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
console.log("scroll");
}
};
And I use "useEffect" to trigger it:
useEffect(() => {
scrollToBottom();
}, [messages]);
Here is the place where I implement the view:
<div className="scroll">
<ListMessages listMessages={messages} />
<div ref={messagesEndRef} />
</div>
And here is the result on browser:
You guys can see that the word "scroll" is printed duplicated (or more) after every message. It makes the app very slow
Could you guys have any idea to help me to solve this case?
Thank you in advance!
PS: Here is my full component
const ws = new WebSocket("ws://localhost:8080/chat");
const Chat = () => {
const createWebSocket = () => {
ws.addEventListener("open", () => {
console.log("We are connected!");
});
ws.addEventListener("message", (e) => {
receiveMessage(e.data);
});
};
createWebSocket();
const [messages, setMessages] = useState([]);
const messagesEndRef = useRef();
const pushMessageToList = async (message, sentBy) => {
let inputMessage = {
id: Date.now(),
message: message,
sentBy: sentBy,
};
let listMessages = [...messages];
listMessages.push(inputMessage);
setMessages(listMessages);
};
const receiveMessage = (message) => {
pushMessageToList(message, "chatbot");
};
const sendInputMessage = (message) => {
pushMessageToList(message, "user");
ws.send(message);
};
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
console.log("scroll");
}
};
useEffect(() => {
scrollToBottom();
}, [messages]);
useEffect(() => {
// component did mount
}, []);
return (
<div className="bg-white">
<div className="scroll">
<ListMessages listMessages={messages} />
<div ref={messagesEndRef} />
</div>
<Input sendInputMessage={sendInputMessage} />
</div>
);
};
export default Chat;
Your code is adding new listeners to the websocket on every render, that's why you are getting incremental logs.
You have to setup handlers inside a useEffect hook, and remove them in the cleanup function of the hook itself (see docs), like:
useEffect(() => {
function onOpen() {
console.log("We are connected!");
}
function onMessage({ data }) {
setMessages([
...messages,
{
id: Date.now(),
message: data.message,
sentBy: 'chatbot'
}
]);
}
ws.addEventListener("open", onOpen);
ws.addEventListener("message",onMessage);
return () => {
ws.removeEventListener("open", onOpen);
ws.removeEventListener("message", onMessage);
}
}, [messages]);
(This hook needs the messages dependency, because you want to update the messages based on the previous state - I think you can do with the callback as well, without any dependency:
setMessages(prevMessages => ([
...prevMessages,
{
id: Date.now(),
message: data.message,
sentBy: 'chatbot'
}
]));
Now you can use the scroll hook in the same hook if you are listing messages as dependency, or in separate one like you have now in the other case.
Your full component will look something like:
const ws = new WebSocket("ws://localhost:8080/chat");
const Chat = () => {
const [messages, setMessages] = useState([]);
const messagesEndRef = useRef();
useEffect(() => {
function onOpen() {
console.log("We are connected!");
}
function onMessage({ data }) {
setMessages((prevMessages) => [
...prevMessages,
{
id: Date.now(),
message: data.message,
sentBy: "chatbot",
},
]);
}
ws.addEventListener("open", onOpen);
ws.addEventListener("message",onMessage);
return () => {
ws.removeEventListener("open", onOpen);
ws.removeEventListener("message", onMessage);
}
}, []);
useEffect(() => {
messagesEndRef?.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const sendInputMessage = (message) => {
ws.send(message);
setMessages([
...messages,
{
id: Date.now(),
message: data.message,
sentBy: 'user'
}
]);
};
return (
<div className="bg-white">
<div className="scroll">
<ListMessages listMessages={messages} />
<div ref={messagesEndRef} />
</div>
<Input sendInputMessage={sendInputMessage} />
</div>
);
};
export default Chat;
I'm calling a function (getEmployees(url)), inside a useEffect without a second argument.
I want to call the getEmployees(url) every time an employee is added.
As soon as I add an employee or error as a second argument, the useEffect re-renders infinitely.
Is this how its supposed to work?
import React, { useEffect, useState, useCallback } from 'react'
//
import EmployeeRecord from './EmployeeRecord'
const Employees = () => {
const [loading, setLoading] = useState(false);
const [employees, setEmployees] = useState([]);
const [error, setError] = useState({show: false, msg: ''});
//
const url = 'http://localhost:3001/';
//
// const fetchDrinks = useCallback( async () => {
const getEmployees = useCallback( async (url) => {
setLoading(true)
try {
const response = await fetch(url)
const data = await response.json()
if (data) {
setEmployees(data)
setError({ show: false, msg: '' })
} else {
setError({ show: true, msg: data.Error })
}
setLoading(false)
} catch (error) {
console.log(error)
}
}, []);
useEffect(() => {
getEmployees(url)
}, [])
console.log("11111111 from employee.js ")
if (loading){
return (
<div>
.....is loading
</div>
)
}
return (
<div>
<div className="addinfo-infomations">
<EmployeeRecord employees={employees}/>
</div>
</div>
)
}
export default Employees
The second argument of useEffect is a dependency list telling React to call your useEffect function again any time one of the dependencies changes.
If there are no dependencies ([]), it will just get run when the component mounts.
Right now, if you put employees as a dependency, it will change every time your employees array gets set. And, you can see that in your getEmployees function, you call setEmployees. That's how you end up in your loop:
useEffect -> getEmployees -> setEmployees -> (employees changes, triggering useEffect again)
In order to avoid this, you have to not form a loop, or short-circuit it somehow.
You say that you want to run getEmployees every time an employee is added, but there's no code from what you've shown dealing with adding an employee. So, I'm assuming maybe this happens on the server that you're polling? If so, you'll need to find some way to get a notification from the server -- it won't be a matter of just calling useEffect, because React will have no way to know that there's been an employee added. Or, your example is missing some relevant code.
call getEmployees cause employees and error change. If you add an employee or error as a second argument, it means useEffect will execute its callback when employees and error changed. As a result, useEffect will execute its callback when you call getEmployees. Then the callback of useEffect will call getEmployees. infinity happened.
I think if you want reload the empolyees, you can add a refresh button
const [refresh, setRefresh] = React.useState(true);
const doRefresh = React.useCallback(() => {
setRefresh(pre => !pre);
}, []);
useEffect(() => {
...
}, [refresh]);
return (
<div>
...
<button onClick={doRefresh}>reload</button>
</div>
)
or automattically reload
useEffect(() => {
let handle;
const task = () => {
getEmpolyees().then(() => {
handle = setTimeout(task, 1000)
});
}
task();
return () => {
handle && clearTimeout(handle);
}
}, [])
I feel like the employees that are added vs the data you get back from the fetch should be different and if they are different you need to separate them, in order for that to work the way you have it now. Adding another state for it would be enough, lets say we call it setData since you have it named data. You would now setData instead of employees that way the effect doesn't start an infinite loop and then update the effect with all of its dependencies and you'll have no prob.
const Employees = () => {
const [loading, setLoading] = useState(false);
const [employees, setEmployees] = useState([]);
const [data, setData] = useState();
const [error, setError] = useState({ show: false, msg: '' });
//
const url = 'http://localhost:3001/';
//
// const fetchDrinks = useCallback( async () => {
const getEmployees = useCallback(async url => {
setLoading(true);
try {
const response = await fetch(url);
const data = await response.json();
if (data) {
setData(data)
setError({ show: false, msg: '' })
} else {
setError({ show: true, msg: data.Error });
}
setLoading(false);
} catch (error) {
console.log(error);
}
}, []);
useEffect(() => {
url && getEmployees(url);
}, [url, employees, getEmployees]);
if (loading) {
return <div>.....is loading</div>;
}
return (
<div>
<div className="addinfo-infomations">
<EmployeeRecord employees={employees} data={data} />
</div>
</div>
);
};
I've a problem, I use the react hooks to keep track of the user token and I use the token to identify that user in a socket connection.
The problem is that when I mount the component and set the socket the token works as expected, after when the app goes in background I close the socket but when the app come in foreground and I executed the setToken function to re-set the connection the token in the function have his starter value (false). I print the token on screen and also when in the function appear to be false in the screen is printed correctly.
Here my code:
let socket;
const Chat = (props) => {
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState({});
const [token, setToken] = useState(false);
useEffect(() => {
init();
return ()=> {
socket.close();
AppState.removeEventListener('change', appStateChange);
}
}, []);
const init = async () => {
// [...] get the token
};
const appStateChange = async (newState) => {
if (newState === "active") {
setSocket(); //--------- EXECUTING FROM HERE THE TOKEN IS FALSE ---------//
}
if (newState !== "active") {
socket.close();
}
}
useEffect(() => {
if (token) {
setSocket(); //--------- EXECUTING FROM HERE THE TOKEN IS CORRECT ---------//
}
}, [token]);
const setSocket = async () => {
socket = io("http://192.168.1.172:3000/", {
query: {
token: token,
userTo: props.userTo
},
});
socket.on("init", (data) => {
setUsers(data.users);
setMessages(data.messages);
});
socket.on("newMessage", (data) => {
onReceive({
_id: data._id,
text: data.text,
createdAt: new Date(),
user: {
_id: data.user._id,
name: data.user.name,
avatar: data.user.avatar,
},
});
});
};
const onSend = useCallback((messages = []) => {
setMessages((previousMessages) =>
GiftedChat.append(previousMessages, messages)
);
socket.emit("newMessage", messages);
console.log(messages)
}, []);
const onReceive = useCallback((received) => {
setMessages((previousMessages) =>
GiftedChat.append(previousMessages, received)
);
}, []);
return (
<View style={{flex:1}}>
<Text>{token}</Text>
{/*--------- HERE THE TOKEN IS CORRECT ---------*/}
</View>
);
};
export default Chat;
Some State setting you are missing out!
When the app goes to background, you didn't cleared the token.
For appStateChange function parameter newState, from where you are intializing the value
For newState when the app comes to front again, you didn't changed the value so the setSocket() will not be called
I'm not understanding why the following code, the callback onSocketMessage is not using the new acquisition state. inside the useEffect the state is correctly updated, but the function is not evaluated again...i've also tryed using useCallback with acquisition as dependency but nothing changed.
const Ac = () => {
const [acquisition, setAcquisition] = useState({ data: {} })
const [loading, setLoading] = useState(true)
const socket = useRef(null);
const onSocketMessage = (message) => {
console.log(acquisition) // this is always initial state
let { data } = acquisition
data.input[message.index] = message.input
setAcquisition(prevState => ({ ...prevState, data }));
}
useEffect(() => {
fetchCurrentAcquisition(acquisition => {
setAcquisition(acquisition)
setLoading(false)
socket.current = newSocket('/acquisition', () => console.log('connected'), onSocketMessage);
})
return () => socket.current.disconnect()
}, [])
console.log(acquisition)
You are logging a stale closure you should try the following instead:
const onSocketMessage = useCallback((message) => {
setAcquisition((acquisition) => {
//use acquisition in the callback
console.log(acquisition);
//you were mutating state here before
return {
...acquisition,
data: {
...acquisition.data,
input: {
//not sure if this is an array or not
//assimung it is an object
...acquisition.data.input,
[message.index]: message.input,
},
},
};
});
}, []); //only created on mount
useEffect(() => {
fetchCurrentAcquisition((acquisition) => {
setAcquisition(acquisition);
setLoading(false);
socket.current = newSocket(
'/acquisition',
() => console.log('connected'),
onSocketMessage
);
});
return () => socket.current.disconnect();
//onSocketMessage is a dependency of the effect
}, [onSocketMessage]);
I have this code
import ReactDOM from "react-dom";
import React, { useState, useEffect } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function ParamsExample() {
return (
<Router>
<div>
<h2>Accounts</h2>
<Link to="/">Netflix</Link>
<Route path="/" component={Miliko} />
</div>
</Router>
);
}
const Miliko = ({ match }) => {
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
(async function() {
setIsError(false);
setIsLoading(true);
try {
const Res = await fetch("https://foo0022.firebaseio.com/New.json");
const ResObj = await Res.json();
const ResArr = await Object.values(ResObj).flat();
setData(ResArr);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
})();
console.log(data);
}, [match]);
return <div>{`${isLoading}${isError}`}</div>;
};
function App() {
return (
<div className="App">
<ParamsExample />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I created three links that open the Miliko component. but when I quickly click on the links I get this error:
To fix, cancel all subscriptions and asynchronous tasks in a useEffect
cleanup function.
I think the problem is caused by dismount before async call finished.
const useAsync = () => {
const [data, setData] = useState(null)
const mountedRef = useRef(true)
const execute = useCallback(() => {
setLoading(true)
return asyncFunc()
.then(res => {
if (!mountedRef.current) return null
setData(res)
return res
})
}, [])
useEffect(() => {
return () => {
mountedRef.current = false
}
}, [])
}
mountedRef is used here to indicate if the component is still mounted. And if so, continue the async call to update component state, otherwise, skip them.
This should be the main reason to not end up with a memory leak (access cleanedup memory) issue.
Demo
https://codepen.io/windmaomao/pen/jOLaOxO , fetch with useAsync
https://codepen.io/windmaomao/pen/GRvOgoa , manual fetch with useAsync
Update
The above answer leads to the following component that we use inside our team.
/**
* A hook to fetch async data.
* #class useAsync
* #borrows useAsyncObject
* #param {object} _ props
* #param {async} _.asyncFunc Promise like async function
* #param {bool} _.immediate=false Invoke the function immediately
* #param {object} _.funcParams Function initial parameters
* #param {object} _.initialData Initial data
* #returns {useAsyncObject} Async object
* #example
* const { execute, loading, data, error } = useAync({
* asyncFunc: async () => { return 'data' },
* immediate: false,
* funcParams: { data: '1' },
* initialData: 'Hello'
* })
*/
const useAsync = (props = initialProps) => {
const {
asyncFunc, immediate, funcParams, initialData
} = {
...initialProps,
...props
}
const [loading, setLoading] = useState(immediate)
const [data, setData] = useState(initialData)
const [error, setError] = useState(null)
const mountedRef = useRef(true)
const execute = useCallback(params => {
setLoading(true)
return asyncFunc({ ...funcParams, ...params })
.then(res => {
if (!mountedRef.current) return null
setData(res)
setError(null)
setLoading(false)
return res
})
.catch(err => {
if (!mountedRef.current) return null
setError(err)
setLoading(false)
throw err
})
}, [asyncFunc, funcParams])
useEffect(() => {
if (immediate) {
execute(funcParams)
}
return () => {
mountedRef.current = false
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return {
execute,
loading,
data,
error
}
}
Update 2022
This approach has been adopted in the book https://www.amazon.com/Designing-React-Hooks-Right-Way/dp/1803235950 where this topic has been mentioned in useRef and custom hooks chapters, and more examples are provided there.
useEffect will try to keep communications with your data-fetching procedure even while the component has unmounted. Since this is an anti-pattern and exposes your application to memory leakage, cancelling the subscription to useEffect optimizes your app.
In the simple implementation example below, you'd use a flag (isSubscribed) to determine when to cancel your subscription. At the end of the effect, you'd make a call to clean up.
export const useUserData = () => {
const initialState = {
user: {},
error: null
}
const [state, setState] = useState(initialState);
useEffect(() => {
// clean up controller
let isSubscribed = true;
// Try to communicate with sever API
fetch(SERVER_URI)
.then(response => response.json())
.then(data => isSubscribed ? setState(prevState => ({
...prevState, user: data
})) : null)
.catch(error => {
if (isSubscribed) {
setState(prevState => ({
...prevState,
error
}));
}
})
// cancel subscription to useEffect
return () => (isSubscribed = false)
}, []);
return state
}
You can read up more from this blog juliangaramendy
Without #windmaomao answer, I could spend other hours trying to figure out how to cancel the subscription.
In short, I used two hooks respectively useCallback to memoize function and useEffect to fetch data.
const fetchSpecificItem = useCallback(async ({ itemId }) => {
try {
... fetch data
/*
Before you setState ensure the component is mounted
otherwise, return null and don't allow to unmounted component.
*/
if (!mountedRef.current) return null;
/*
if the component is mounted feel free to setState
*/
} catch (error) {
... handle errors
}
}, [mountedRef]) // add variable as dependency
I used useEffect to fetch data.
I could not call the function inside effect simply because hooks can not be called inside a function.
useEffect(() => {
fetchSpecificItem(input);
return () => {
mountedRef.current = false; // clean up function
};
}, [input, fetchSpecificItem]); // add function as dependency
Thanks, everyone your contribution helped me to learn more about the usage of hooks.
fetchData is an async function which will return a promise. But you have invoked it without resolving it. If you need to do any cleanup at component unmount, return a function inside the effect that has your cleanup code. Try this :
const Miliko = () => {
const [data, setData] = useState({ hits: [] });
const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux');
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
(async function() {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(url);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
})();
return function() {
/**
* Add cleanup code here
*/
};
}, [url]);
return [{ data, isLoading, isError }, setUrl];
};
I would suggest reading the official docs where it is clearly explained along with some more configurable parameters.
Folowing #Niyongabo solution, the way I ended up that fixed it was:
const mountedRef = useRef(true);
const fetchSpecificItem = useCallback(async () => {
try {
const ref = await db
.collection('redeems')
.where('rewardItem.id', '==', reward.id)
.get();
const data = ref.docs.map(doc => ({ id: doc.id, ...doc.data() }));
if (!mountedRef.current) return null;
setRedeems(data);
setIsFetching(false);
} catch (error) {
console.log(error);
}
}, [mountedRef]);
useEffect(() => {
fetchSpecificItem();
return () => {
mountedRef.current = false;
};
}, [fetchSpecificItem]);
Create a mutable ref object and set it to true, and during clean-up toggle its value, to ensure that the component has been unmouted.
const mountedRef = useRef(true)
useEffect(() => {
// CALL YOUR API OR ASYNC FUNCTION HERE
return () => { mountedRef.current = false }
}, [])
const [getAllJobs, setgetAlljobs] = useState();
useEffect(() => {
let mounted = true;
axios.get('apiUrl')
.then(function (response) {
const jobData = response.data;
if (mounted) {
setgetAlljobs(jobData)
}
})
.catch(function (error) {
console.log(error.message)
})
return () => mounted = false;
}, [])
set a variable mounted to true->
then if it is true, mount the function->
in the bottom you return it to unmount it
My case was pretty different from what this questions wants. Still I got the same error.
My case was because I had a 'list', which was rendered by using .map from array. And I needed to use .shift. (to remove first item in array)
If array had just one item, it was ok, but since it had 2 of them -> the first one got 'deleted/shifted' and because I used key={index} (while index was from .map), it assumed, that the second item, which later was first, was the same component as the shifted item..
React kept info from the first item (they were all nodes) and so, if that second node used useEffect(), React threw error, that the component is already dismounted, because the former node with index 0 and key 0 had the same key 0 as the second component.
The second component correctly used useEffect, but React assumed, that it is called by that former node, which was no longer on the scene -> resulting in error.
I fixed this by adding different key prop value (not index), but some unique string.
you can wrap any action as a callback inside checkUnmount
const useUnmounted = () => {
const mountedRef = useRef(true);
useEffect(
() => () => {
mountedRef.current = false;
},
[],
);
const checkUnmount = useCallback(
(cb = () => {}) => {
try {
if (!mountedRef.current) throw new Error('Component is unmounted');
cb();
} catch (error) {
console.log({ error });
}
},
[mountedRef.current],
);
return [checkUnmount, mountedRef.current];
};
import React, { useCallback, useEffect, useRef, useState } from "react";
import { userLoginSuccessAction } from "../../../redux/user-redux/actionCreator";
import { IUser } from "../../../models/user";
import { Navigate } from "react-router";
import XTextField from "../../../x-lib/x-components/x-form-controls/XTextField";
import { useDispatch } from "react-redux";
interface Props {
onViewChange?: (n: number) => void;
userInit?: (user: IUser) => void;
}
interface State {
email: string;
password: string;
hasError?: boolean;
errorMessage?: string;
}
const initialValue = {
email: "eve.holt#reqres.in",
password: "cityslicka",
errorMessage: "",
};
const LoginView: React.FC<Props> = (props) => {
const { onViewChange } = props;
const [state, setState] = useState(initialValue);
const mountedRef = useRef(true);
const dispatch = useDispatch();
const handleEmailChange = useCallback(
(val: string) => {
setState((state) => ({
...state,
email: val,
}));
},
[state.email]
);
const handlePasswordChange = useCallback(
(val: string) => {
setState((state) => ({
...state,
password: val,
}));
},
[state.password]
);
const onUserClick = useCallback( async () => {
// HTTP Call
const data = {email: state.email , password: state.password}
try{
await dispatch(userLoginSuccessAction(data));
<Navigate to = '/' />
setState( (state)=>({
...state,
email: "",
password: ""
}))
}
catch(err){
setState( (state)=>({
...state,
errorMessage: err as string
}))
}
},[mountedRef] )
useEffect(()=>{
onUserClick();
return ()=> {
mountedRef.current = false;
};
},[onUserClick]);
const Error = (): JSX.Element => {
return (
<div
className="alert alert-danger"
role="alert"
style={{ width: "516px", margin: "20px auto 0 auto" }}
>
{state.errorMessage}
</div>
);
};
return (
<div>
<div>
email: "eve.holt#reqres.in"
<span style={{ paddingRight: "20px" }}></span> password: "cityslicka"{" "}
</div>
{state.errorMessage && <Error />}
<form className="form-inline">
<div className="form-group">
<XTextField
label="email"
placeholder="E-Posta"
value={state.email}
onChange={handleEmailChange}
/>
</div>
<div className="form-group my-sm-3">
<XTextField
type="password"
label="password"
placeholder="Şifre"
value={state.password}
onChange={handlePasswordChange}
/>
</div>
<button type="button" className="btn btn-primary" onClick = {onUserClick} >
Giriş Et
</button>
<a
href="#"
onClick={(e) => {
e.preventDefault();
onViewChange && onViewChange(3);
}}
>
Şifremi Unuttum!
</a>
</form>
<p>
Hələdə üye deyilsiniz? <br />
pulsuz registir olmak üçün
<b>
<u>
<a
style={{ fontSize: "18px" }}
href="#"
onClick={(e) => {
e.preventDefault();
onViewChange && onViewChange(2);
}}
>
kilik edin.
</a>
</u>
</b>
</p>
</div>
);
};
export default LoginView;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
For this problem I used a tricky way
first I deploy a state like this
const [routing,setRouting] = useState(false)
then when my works finished I changed it to true
and change my useEffect like this
useEffect(()=>{
if(routing)
navigation.navigate('AnotherPage')
),[routing]}