We are making the necessary consent for membership registration with React. However, it is up to agreeing to the checkbox, but it does not work properly because it is re-rendered when the button is clicked. Any help would be appreciated.
enter code here
const [disabled, setDisabled] = useState('disabled');
const [agree1, setAgree1] = useState(false); //회원정보 동의
const [agree2, setAgree2] = useState(false); //개인정보 수집 및 이용동의
const [agree3, setAgree3] = useState(false); //위치정보 동의
const [total, settotal] = useState(false); //전체 동의
const [email, setemail] = useState("");
const buttonState = useCallback(() => {
if((agree1===true)&&(agree2===true)&&(agree3===true)){
settotal(true);
setDisabled('');
}
else {
setDisabled('disabled');
}
},[agree1,agree2,agree3,total]);
useEffect(() => {
buttonState();
}, [buttonState])
const totalchange = () => {
if(total ===true) { //전체동의가 true라면 다시 클릭 했을때 전부 unchecked
settotal(!total);
setAgree1(!agree1);
setAgree2(!agree2);
setAgree3(!agree3);
} else{ //그외(하나만 체크되 있거나 아무것도 없다면) 전부 checked로 만듬
settotal(true);
setAgree1(true);
setAgree2(true);
setAgree3(true);
setDisabled('');
}
}
const clickFunction = () => {
axios.get(`/signup/authNum?email=${email}`)
.then(res => {
if(window.confirm("인증번호 전송이 완료되었습니다")){
history.push({
pathname : '/registerauth',
state : {state : res.data}
})
}
})
.catch(err => {console.log(err)})
}enter code here
Sorry for being awkward because this is my first question on stack-overflow
Why are you using the callback for the button click and why are you calling that function in useEffect?
Related
Local storage is getting cleared after refresh tried many StackOverflow solutions but none are solving it. Please help:
Code:
const [data, setData] = useState("");
const [tasks, setTasks] = useState([]);
useEffect(() => {
retrivedTasks();
}, []);
useEffect(() => {
saveToLocal();
}, [tasks]);
const saveToLocal = () => {
localStorage.setItem("tasks", JSON.stringify(tasks));
};
const retrivedTasks = () => {
if (localStorage.getItem("tasks") === null) {
localStorage.setItem("tasks", JSON.stringify([]));
} else {
let dataFromLS = JSON.parse(localStorage.getItem("tasks"));
setTasks(dataFromLS);
}
};
So I have built app which takes value from input -> set it to the state-> state change triggers functions in useEffect (this part is in custom hook) -> functions fetch data from api -> which triggers functions in useEffect in component to store data in array. The thing is that there are two problems that I am trying to solve :
When user is putting the same value in input and setting it in state it's not triggering useEffect functions (I solved it by wrapping value in object but I am looking for better solution).
When user uses the same value in short period of time api will send the same data which again makes problem with triggering function with useEffect (I tried to solved with refresh state that you will see in code below, but it looks awful)
The question is how can I actually do it properly? Or maybe the solutions I found aren't as bad as I think they are. Thanks for your help.
component
const [nextLink, setNextLink] = useState({ value: "" });
const isMounted = useRef(false);
const inputRef = useRef(null);
const { shortLink, loading, error, refresh } = useFetchLink(nextLink);
const handleClick = () => {
setNextLink({ value: inputRef.current.value });
};
useEffect(() => {
setLinkArr((prev) => [
...prev,
{
id: prev.length === 0 ? 1 : prev[prev.length - 1].id + 1,
long: nextLink.value,
short: shortLink,
},
]);
if (isMounted.current) {
scrollToLink();
} else {
isMounted.current = true;
}
inputRef.current.value = "";
}, [refresh]);
custom hook
const useFetchLink = (linkToShorten) => {
const [shortLink, setShortLink] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [refresh, setRefresh] = useState(false);
const isMounted = useRef(false);
const fetchLink = async (link) => {
setLoading(true);
try {
const response = await fetch(
`https://api.shrtco.de/v2/shorten?url=${link}`
);
if (response.ok) {
const data = await response.json();
setShortLink(data.result.short_link);
setRefresh((prev) => !prev);
} else {
throw response.status;
}
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (isMounted.current) {
if (checkLink(linkToShorten.value)) {
setError(checkLink(linkToShorten.value));
} else {
fetchLink(linkToShorten.value);
}
} else {
isMounted.current = true;
}
}, [linkToShorten]);
const value = { shortLink, loading, error, refresh };
return value;
};
export default useFetchLink;
Here is the code of the snippet I want to change to a Functional component, I write almost my code here now please check.
import _ from 'lodash';
import { ListItem, SearchBar, Avatar } from 'react-native-elements';
import { getUsers, contains } from './api/index';
function App(props) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [fullData, setFullData] = useState([]);
const [query, setQuery] = useState();
useEffect(() => {
makeRemoteRequest();
},[query]);
const makeRemoteRequest = _.debounce(() => {
setLoading(true);
getUsers(20, query)
.then((users) => {
setLoading(false);
setData(users);
setFullData(users);
})
.catch((error) => {
setLoading(false);
});
}, 250);
const handleSearch = (text) => {
const formattedQuery = text.toLowerCase();
const data = _.filter(fullData, (user) => {
return contains(user, formattedQuery);
});
// I want to change the below code to work on Functioanl component
// this.setState({ data, query: text }, () => //this.makeRemoteRequest());
// New code here.....
};
I implemented it in a different way but not work.
You can have something like the following.
const [query, setQuery] = useState();
const [data, setData] = useState();
useEffect(() => {
makeRemoteRequest();
}, [query])
Read more about useEffect here
You're trying to make a set of data and text, then call a callback after the set.
There are several ways to obtain this behaviour.
What I would suggest you is to have a state (useState) which include data and text and then listen for the changes of this stage through a useEffect.
export default function App() {
const [request, setRequest] = useState({data: {}, text: ''});
const makeRemoteRequest = useCallback(() => console.log({request}),[request]);
useEffect(() => {
//on mount
setRequest({data: {obj:'with data'}, text: 'text'})
},[])
useEffect(() => {
makeRemoteRequest()
},[request,makeRemoteRequest])
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
What you can see here, it's a functional component which is:
setting a state on mount (read comment)
define a function makeRemoteRequest every time the state request changes through the useCallback hook
call the function makeRemoteRequest every time the state request or the callback makeRemoteRequest changes through the useEffect hook
EDIT:
import _ from 'lodash';
import { ListItem, SearchBar, Avatar } from 'react-native-elements';
import { getUsers, contains } from './api/index';
function App(props) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [fullData, setFullData] = useState([]);
const [query, setQuery] = useState();
useEffect(() => {
makeRemoteRequest();
},[query]);
const makeRemoteRequest = _.debounce(() => {
setLoading(true);
getUsers(20, query)
.then((users) => {
setLoading(false);
setData(users);
setFullData(users);
})
.catch((error) => {
setLoading(false);
});
}, 250);
const handleSearch = (text) => {
const formattedQuery = text.toLowerCase();
const data = _.filter(fullData, (user) => {
return contains(user, formattedQuery);
});
setData(data);
setQuery(text);
}
};
Actually what you want is to trigger the function makeRemoteRequest, right now that you have to do in order to get it is to make the proper set (which means setQuery), which is going to trigger the useEffect
I'm trying to implement a refresh button but can't get it done.
This is how my code looks like:
// ParentComponent.js
const ParentComponent = () => {
const { loading, error, data } = useItems();
return (
<ChildComponent items={data} />
);
... rest of my code that shows the data
};
// ChildComponent.js
const ChildComponent = ({ items }) => {
return (
// Logic that renders the items in <li>s
<button onClick={() => console.log('Clicking this button should refresh parent component')}
)
};
// services/useItems.js
const useItems = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
axios
.get(API_URL + '/counter')
.then((response) => {
setItems(response.data);
setLoading(false);
})
.catch((error) => {
setLoading(false);
setError(error.message);
});
}, []);
return { loading, error, data: counters };
}
I've tried several ways but none did the work. any helps would be truly appreciated :)
I don't think useEffect is the right mechanism here. Since it's an imperative call, nothing reactive about it, useState does the job just fine:
// ParentComponent.js
const ParentComponent = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const refresh = () => {
axios.get(API_URL + '/counter').then((response) => {
setItems(response.data);
setLoading(false);
}).catch((error) => {
setLoading(false);
setError(error.message);
});
};
useEffect(refresh, []);
return (
<ChildComponent items={items} refresh={refresh} />
);
// ... rest of my code that shows the data
};
// ChildComponent.js
const ChildComponent = ({ items, refresh }) => {
return (
// Logic that renders the items in <li>s
<button onClick={refresh}>
Refresh
</button>
)
};
A very simple trick is to increase an integer state, let's just call it version, which would trigger a re-render of <ParentComponent /> and if useEffect depends on version, it'll re-execute the callback, so you get the "refresh" effect.
// ParentComponent.js
const ParentComponent = () => {
const [version, setVersion] = useState(0)
// when called, add 1 to "version"
const refresh = useCallback(() => {
setVersion(s => s + 1)
}, [])
const { loading, error, data } = useItems(version);
return (
<ChildComponent items={data} refresh={refresh} />
);
};
// ChildComponent.js
const ChildComponent = ({ items, refresh }) => {
return (
// Logic that renders the items in <li>s
<button onClick={refresh} />
)
};
// services/useItems.js
const useItems = (version) => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
axios
.get(API_URL + '/counter')
.then((response) => {
setItems(response.data);
setLoading(false);
})
.catch((error) => {
setLoading(false);
setError(error.message);
});
}, [version]); // <-- depend on "version"
return { loading, error, data: counters };
}
There are couple fo small parts where you need to make changes to resolve issue.
You need to create a communication for refresh
Create a function to process any processing for refresh.
Pass this as a prop to child component
In child component, call it on necessary event, in this case click
Now since you are using hooks, you need to get it invoked.
You can add a function refreshData in your useItem hook and expose it
Call this function on click of button.
You will also have to add a flag in hooks and update useEffect to be triggered on its change
This function is necessary as setItems is only available inside hook.
Following is a working sample:
const { useState, useEffect } = React;
// ParentComponent.js
const ParentComponent = () => {
const { loading, error, data, refreshData } = useItems();
const refreshFn = () => {
refreshData()
}
return (
<ChildComponent
items={data}
onClick={refreshFn}/>
);
// ... rest of my code that shows the data
};
// ChildComponent.js
const ChildComponent = ({ items, onClick }) => {
const onClickFn = () => {
console.log('Clicking this button should refresh parent component')
if(!!onClick) {
onClick();
}
}
return (
// Logic that renders the items in <li>s
<div>
<button
onClick={ () => onClickFn() }
>Refresh</button>
<ul>
{
items.map((item) => <li key={item}>{item}</li>)
}
</ul>
</div>
)
};
// services/useItems.js
const useItems = () => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [refresh, setRefresh] = useState(false)
useEffect(() => {
if (refresh) {
setItems(Array.from({ length: 5 }, () => Math.random()));
setRefresh(false)
}
}, [ refresh ]);
return {
loading,
error,
data: items,
refreshData: () => setRefresh(true)
};
}
ReactDOM.render(<ParentComponent/>, document.querySelector('.content'))
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='content'></div>
As correctly commented by hackape, we need to add a check for refresh and fetch data only if its true
As the title said, what is the correct way of using custom hook to handle onClick Event?
This codesandbox application will display a new quote on the screen when user clicks the search button.
function App() {
const [{ data, isLoading, isError }, doFetch] = useDataApi(
"https://api.quotable.io/random"
);
return (
<Fragment>
<button disabled={isLoading} onClick={doFetch}>
Search
</button>
{isError && <div>Something went wrong ...</div>}
{isLoading ? <div>Loading ...</div> : <div>{data.content}</div>}
</Fragment>
);
}
I created a custom hook called useDataApi() which would fetch a new quote from an API. In order to update the quote when the user clicks the button, inside the useDataApi(), I created a handleClick() which will change the value of a click value to trigger re-render. And this handleClick() function will be return back to App()
const useDataApi = initialUrl => {
const [data, setData] = useState("");
const [click, setClick] = useState(true);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const handleClick = () => {
setClick(!click);
};
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(initialUrl);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [initialUrl, click]);
return [{ data, isLoading, isError }, handleClick];
};
This is working, however, I don't feel this is the correct solution.
I also tried moving the fetchData() out of useEffect and return the fetchData(), and it works too. But according to the React Doc, it says it is recommended to move functions inside the useEffect.
const useDataApi = initialUrl => {
const [data, setData] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(initialUrl);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
useEffect(() => {
fetchData();
}, []);
return [{ data, isLoading, isError }, fetchData];
};
In addition, for creating these kinds of application, is the way that I am using is fine or there is another correct solution such as not using any useEffects or not create any custom Hook?
Thanks
Not sure if this is correct, but here is my solution.
const useDataApi = initialUrl => {
const [data, setData] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const doFetch = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await axios(initialUrl);
setData(result.data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
return [{ data, isLoading, isError }, doFetch];
};
Btw, don't mutate state directly.
const handleClick = () => {
setClick(!click); // don't do this
setClick(prev => !prev); // use this
};
Your implementation is fine. We are also using something similar. Hope you find it useful.
function useApi(promiseFunction, deps, shouldRun=true){
// promisFunction returns promise
const [loading, setLoading] = useState(false)
const [data, setData] = useState(false)
const [error, setError] = useState(false)
const dependencies: any[] = useMemo(()=>{
return [...dependencyArray, shouldRun]
},[...dependencyArray, shouldRun])
const reload = () => {
async function call() {
try {
setError(null)
setLoading(true)
const res = await promiseFunction();
}
catch (error) {
setError(error)
}
finally {
setLoading(false)
}
}
call();
}
useEffect(() => {
if(!shouldRun) return
setResult(null) //no stale data
reload()
}, dependencies)
return {loading, error, data, reload, setState: setData}
}
Below code will provide some idea about how to use it.
function getUsersList(){
return fetch('/users')
}
function getUserDetail(id){
return fetch(`/user/${id}`)
}
const {loading, error, data } = useApi(getUsersList, [], true)
const {loading: userLoading, error: userError, data: userData}
= useApi(()=>getUserDetail(id), [id], true)