I have the following React component:
const AuthInit: FC<WithChildren> = ({children}) => {
const {auth, logout, setCurrentUser} = useAuth()
const didRequest = useRef(false)
const [showSplashScreen, setShowSplashScreen] = useState(true)
// We should request user by authToken (IN OUR EXAMPLE IT'S API_TOKEN) before rendering the application
useEffect(() => {
const requestUser = async (apiToken: string) => {
try {
if (!didRequest.current) {
const {data} = await getUserByToken(apiToken)
if (data) {
setCurrentUser(data)
}
}
} catch (error) {
console.error(error)
if (!didRequest.current) {
logout()
}
} finally {
setShowSplashScreen(false)
}
return () => (didRequest.current = true)
}
if (auth && auth.api_token) {
requestUser(auth.api_token)
} else {
logout()
setShowSplashScreen(false)
}
// eslint-disable-next-line
}, [])
return showSplashScreen ? <LayoutSplashScreen /> : <>{children}</>
}
The component contains this line of code:
return () => (didRequest.current = true)
How can I deconstruct this line into multiple lines so that I can put a breakpoint on didRequest.current = true?
function updateDidRequest = () => {
didRequest.current = true;
}
return updateDidRequest;
Related
I am making a request to my API from the client. I set the information in a React state. The weird thing is the following:
When I print the result of the fetch by console I get this:
But when I display to see in detail, specifically the "explore" array, I get that the result is empty:
The explore array is passed to two components by props to show a list, the super weird thing is that one of the components shows the information but the other does not
Fetch
const baseUrl = process.env.BASE_API_URL;
import { marketPlace } from "./mock";
import { requestOptions } from "utils/auxiliaryFunctions";
const getInitialMarketPlaceData = async (username: string) => {
try {
return await fetch(
`${baseUrl}marketplace/home/${username}`,
requestOptions()
)
.then((data) => data.json())
.then((res) => {
console.log(res.data)
if (res.success) {
return res.data;
}
});
} catch (err) {
throw err;
}
};
Components
export default function Marketplace() {
const [marketPlace, setMarketPlace] = useState<any>();
const [coachData, setCoachData] = useState<false | any>();
const [coach, setCoach] = useState<string>();
const { user } = useContext(AuthContext);
useEffect(() => {
if (!marketPlace) {
getInitialMarketPlaceData(user.username).then((data) => {
console.log("data", data);
setMarketPlace(data);
});
}
}, []);
useEffect(() => {
console.log("marketplace", marketPlace);
}, [marketPlace]);
useEffect(() => {
console.log("coachData", coachData);
}, [coachData]);
const changeCoachData = async (coach: string) => {
setCoach(coach);
let res = await getCoachProfile(coach);
setCoachData(res);
};
if (!marketPlace) {
return <AppLoader />;
}
return (
<main className={styles.marketplace}>
{marketPlace && (
// Highlited viene vacĂo de la API
<Highlited
highlitedCoaches={/*marketPlace.highlighted*/ marketPlace.explore}
></Highlited>
)}
{marketPlace.favorites.length !== 0 && (
<Favorites
changeCoachData={changeCoachData}
favCoaches={marketPlace.favorites}
></Favorites>
)}
{
<Explore
changeCoachData={changeCoachData}
exploreCoaches={marketPlace.explore}
></Explore>
}
{/*<Opinions
changeCoachData={changeCoachData}
opinions={marketPlace.opinions}
></Opinions>*/}
{coachData && (
<CoachProfileRookieView
coachData={coachData}
coachUsername={coach}
isCoach={false}
isPreview={false}
onClose={() => setCoachData(false)}
/>
)}
</main>
);
}
I created a class object that stores the postlist.
When the post was updated, the Pub-Sub pattern subscription function was registered.
class PostLoad extends PubSub {
store = [];
constructor (){
super();
this.connectSocket = connectSocket(this.projectUid, async (type, post) => {
console.log('Socket HOOK', type, post);
if (type === 'insert') await this.newPostPush(post);
if (type === 'update') {
const {deleted} = post;
if (deleted) await this.deletePostInStore(post.id);
}
});
}
async postLoad () {
// ...API LOAD
const value = axios.get('');
this.store = value;
}
private async newPostPush(post: HashsnapPostItem) {
if (post.type === 'video') return;
const storeItem = {...post, img: await loadImage(post.url)} as HashsnapPost;
this.store.unshift(storeItem);
if (this.store.length > this.limit) this.store.splice(this.limit);
this.emit('insert', {post: storeItem, store: this.store});
}
private deletePostInStore(targetId: number) {
this.store.push(...this.store.splice(0).filter(({id}) => id !== targetId));
this.emit('delete', {postId: targetId, store: this.store});
}
}
React component executes events registered in Pub-Sub,
When the post was updated, the status value was changed, but there was no change.
const PostList = () => {
const [postList, setPostList] = useState([]);
const postLoad = store.get('postLoad'); // PostLoad Class Object
useEffect(() => {
setPostList(postLoad.store);
}, []);
postLoad.on('delete', (payload) => {
setPostList(postLoad.store);
})
postLoad.on('insert', (payload) => {
setPostList(postLoad.store);
})
return <div>
{postList.map((post, i) => {
return <div key={`post-${i}`}></div>
})}
</div>
}
What I want is that when the Pus-Sub event is executed, the status value changes and re-rends.
async postLoad () {
// ...API LOAD- axios is a async func, need await this
const value = await axios.get('');
this.store = value;
}
postLoad.on('delete', (payload) => {
setPostList(postLoad.store);
})
postLoad.on('insert', (payload) => {
setPostList(postLoad.store);
})
// thest two register on postLoad will repeat many times, just use hook useEffect, if postLoad always change, useRef, so the right code is below
const PostList = () => {
const [postList, setPostList] = useState([]);
const {current: postLoad} = useRef(store.get('postLoad'));
useEffect(() => {
setPostList(postLoad.store);
postLoad.on('delete', (payload) => {
setPostList(postLoad.store);
})
postLoad.on('insert', (payload) => {
setPostList(postLoad.store);
})
return () => {
// please unregister delete and insert here
}
}, [postLoad]);
return <div>
{postList.map((post, i) => {
return <div key={`post-${i}`}></div>
})}
</div>
}
I have a task to
Implement the functionality of sending a user's email to the server when they click on the "Subscribe" button. To do this, make a POST Ajax request using the /subscribe endpoint.
Implement the functionality of unsubscribing the user's email from
the community list when they click on the "Unsubscribe" button. To do
this, make a POST Ajax request using the /unsubscribe endpoint.
Prevent additional requests if the "Subscribe" and "Unsubscribe"
buttons are pressed while requests to the /subscribe and /unsubscribe
endpoints are in progress. Also, disable them (using the disabled
attribute) and style them using opacity: 0.5. using React.Redux library is prohibited.
So,i have did this task,but the problem is that my form is not doing unsubscribe state,it leaves the email unchanged and the button doesnt change its state from unsubscribe to subscribe,but as a result,when an email is valid,it should give the possibility of sending subscribe fetch request to the server and the button text to change in Unsubscribe and the display of the form to be none,but if the email is not valid,then the button should have the Subscribe text,no fetch request possibilities and the input form to have the display block.Besides this,the value to be stored in local storage and when the page refreshes it leaves the form in the state it was,if was subscribed then all the changes for subscribe are actual and if not than vice-versa.
I have did this task using only javascript and it works as follows:
non-valid email:
valid email:
So,the question is,what i am doing wrong? Why only subscribe fetch works and how to fix the form to make the requested result,i'm exhausted,three days i try to answer this question and i dont understand anything.. please help,thanks in advance!
JoinUsSection component with the form :
import { useState, useEffect } from "react"
import { sendSubscribe } from "../scripts/subscribeFetch"
import { validateEmail } from "../scripts/email-validator"
import { unsubscribeUser } from "../scripts/unsubscribeFetch"
const JoinUsSection = props => {
const { placeholder } = props
//Input div functions
let isSubscribedd = localStorage.getItem('Email') //Function for setting display to none if the form is subscribed and display to block
//console.log(validateEmail(isSubscribedd)) //if form is unsubscribed
let validatedEmail = validateEmail(isSubscribedd)
let setDisplay = ''
if (validatedEmail === true) {
setDisplay = 'none'
localStorage.setItem('isSubscribed', 'true')
} else if (validatedEmail === false) {
setDisplay = 'block'
localStorage.setItem('isSubscribed', 'false')
}
//-------------------------------------
//Input type text Functions
const [email, setEmail] = useState(() => {
//reading data from localStorage
const localEmail = localStorage.getItem('Email')
const initialValue = localEmail
if (localStorage.getItem('Email') !== null) {
return initialValue
} else {
return placeholder
}
})
useEffect(() => {
//storing input email in localStorage
const introducedEmail = email
//console.log(introducedEmail)
localStorage.setItem('Email', introducedEmail)
}, [email])
//------------------------------------------------------
//Input type button Functions
const [isDisabled, setDisabled] = useState(false)
const [isSubscribed, setSubscribe] = useState('Subscribe')
let isFetching = false
let isUsed = false
let introducedEmail = email
const submitClickButton = async () => {
subscribeEmail(introducedEmail) //change the button style and set in local storage isSubscribed to true
sendSubscribe(introducedEmail) //send subscribe fetch to the server
// prevent additional requests upon clicking on "Subscribe" and "Unsubscribe".
if (isFetching) return // do nothing if request already made
isFetching = true
disableBtn()
const response = await fetchMock() //eslint-disable-line
isFetching = false
enableBtn()
isUsed = true
if (validateEmail(introducedEmail) == false) {
isUsed = false
}
}
const fetchMock = () => {
return new Promise(resolve => setTimeout(() => resolve('hello'), 2000))
}
const disableBtn = () => {
// button.disabled = true
// button.style.opacity = '0.5'
setDisabled(true);
}
const enableBtn = () => {
// button.disabled= false
// button.style.opacity = '1'
setDisabled(false);
}
const opacityValue = props.disabled ? 0.5 : 1;
const undoClickButton = () => {
unsubscribeEmail()
isUsed = false
}
const changeButtonState = () => {
isUsed ? undoClickButton() : submitClickButton()
}
const subscribe = () => {
// const subscribeBtn = document.getElementById('subscribeButton')
setSubscribe('Unsubscribe')
// document.getElementById('emailForm').style.display = 'none'
localStorage.setItem('isSubscribed', 'true')
console.log(isUsed)
//document.getElementById('submit-info').value = ''
introducedEmail = ''
}
const unsubscribe = () => {
//const subscribeBtn = document.getElementById('subscribeButton')
setSubscribe('Subscribe')
//document.getElementById('emailForm').style.display = 'block'
localStorage.setItem('isSubscribed', 'false')
console.log(isUsed)
}
const subscribeEmail = (email) => {
const isValidEmail = validateEmail(email)
if (isValidEmail === true) {
subscribe()
} else if (isValidEmail === false) {
unsubscribe()
}
}
const unsubscribeEmail = () => {
unsubscribe()
unsubscribeUser()
localStorage.removeItem('Email')
}
//--------------------------------------
return (
<>
<section className='app-section app-section--image-program' id='programContainer'>
<h2 className='program-title'>Join Our Program</h2>
<h3 className='program-subtitle'>Sed do eiusmod tempor incididunt<br />ut labore et dolore magna aliqua</h3>
<form className='submitFieldWrapper' id='form'>
<div
style={{
display: setDisplay
}}>
<input
className='form-input'
id='submit-info'
type='text'
placeholder='Email'
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<input
id='subscribeButton'
className='app-section__button submit-btn'
type='button'
value={isSubscribed}
style={{
opacity: opacityValue
}}
onClick={() => changeButtonState()}
disabled={isDisabled} />
</form>
</section>
</>
)
}
export default JoinUsSection
subscribe fetch request,in different folder and file:
import { validateEmail } from './email-validator.js'
export const sendSubscribe = (emailInput) => {
const isValidEmail = validateEmail(emailInput)
if (isValidEmail === true) {
sendData(emailInput)
}
}
export const sendHttpRequest = (method, url, data) => {
return fetch(url, {
method: method,
body: JSON.stringify(data),
headers: data
? {
'Content-Type': 'application/json'
}
: {}
}).then(response => {
if (response.status >= 400) {
return response.json().then(errResData => {
const error = new Error('Something went wrong!')
error.data = errResData
throw error
})
}
return response.json()
})
}
const sendData = (emailInput) => {
sendHttpRequest('POST', '/subscribe', {
email: emailInput
}).then(responseData => {
return responseData
}).catch(err => {
console.log(err, err.data)
window.alert(err.data.error)
})
}
email validator ,in different folder and file:
const VALID_EMAIL_ENDINGS = ['gmail.com', 'outlook.com', 'yandex.ru']
export const validateEmail = (email) => !!VALID_EMAIL_ENDINGS.some(v => email.includes(v))
export { VALID_EMAIL_ENDINGS as validEnding }
unsubscribe fetch request,in different folder and file:
export const unsubscribeUser = () => {
fetch('/unsubscribe', { method: 'POST' }).then(response => { console.log(response.status) })
}
App.js:
import './App.css';
import JoinUsSection from './components/JoinUsSection';
function App() {
return (
<JoinUsSection/>
);
}
export default App;
So,i managed how to do this task,it wasnt easy but i got to the end. I used react hooks and local storage to store the states of the buttons to mentain the values on page refresh and the problem with buttons i solved with hooks too.
The whole component is below:
import { useState, useEffect } from "react"
import { sendSubscribe } from "../scripts/subscribeFetch"
import { validateEmail } from "../scripts/email-validator"
import { unsubscribeUser } from "../scripts/unsubscribeFetch"
const JoinUsSection = props => {
const { placeholder } = props
//Input div functions
//let isSubscribedd = localStorage.getItem('Email') //Function for setting display to none if the form is subscribed and display to block
//console.log(validateEmail(isSubscribedd)) //if form is unsubscribed
// let validatedEmail = validateEmail(isSubscribedd)
// let setDisplay = ''
// if (isSubscribed = 'Unsubscribe') {
// setDisplay = 'none'
// //localStorage.setItem('isSubscribed', 'true')
// } else if (isSubscribed ='Subscribe') {
// setDisplay = 'block'
// //localStorage.setItem('isSubscribed', 'false')
// }
//-------------------------------------
//Input type text Functions
const [email, setEmail] = useState(() => {
//reading data from localStorage
const localEmail = localStorage.getItem('Email')
const initialValue = localEmail
if (localStorage.getItem('Email') !== null) {
return initialValue
} else {
return placeholder
}
})
useEffect(() => {
//storing input email in localStorage
const introducedEmail = email
//console.log(introducedEmail)
localStorage.setItem('Email', introducedEmail)
}, [email])
//------------------------------------------------------
//Input type button Functions
let [isDisabled, setDisabled] = useState(false)
let [isSubscribed, setSubscribe] = useState('Subscribe')
let [status, setStatus] = useState(false);
let [displayMode, setDisplay] = useState('block')
const [opacity, setOpacity] = useState(1);
useEffect(() => {
const buttonState = localStorage.getItem('Button state')
if (buttonState) {
setSubscribe(JSON.parse(buttonState))
}
}, [])
useEffect(() => {
const statusState = localStorage.getItem('isSubmited')
if (statusState) {
setStatus(JSON.parse(statusState))
}
}, [])
useEffect(() => {
const displayMode = localStorage.getItem('displayMode')
if (displayMode) {
setDisplay(JSON.parse(displayMode))
}
}, [])
useEffect(() => {
localStorage.setItem('Button state', JSON.stringify(isSubscribed))
localStorage.setItem('isSubmited', JSON.stringify(status))
localStorage.setItem('displayMode', JSON.stringify(displayMode))
})
// if (isSubscribed === 'Unsubscribe') {
// setDisplay('none')
// //localStorage.setItem('isSubscribed', 'true')
// } else if (isSubscribed === 'Subscribe') {
// setDisplay('block')
// //localStorage.setItem('isSubscribed', 'false')
// }
// let isFetching = false
//let isUsed = false
let introducedEmail = email
const submitClickButton = async () => {
subscribeEmail(introducedEmail) //change the button style and set in local storage isSubscribed to true
sendSubscribe(introducedEmail) //send subscribe fetch to the server
// prevent additional requests upon clicking on "Subscribe" and "Unsubscribe".
if (isDisabled) return // do nothing if request already made
//setDisabled(true)
disableBtn()
const response = await fetchMock() //eslint-disable-line
// isFetching = false
//setDisabled(false)
enableBtn()
setStatus(true)
if (validateEmail(introducedEmail) == false) {
setStatus(false)
}
}
const fetchMock = () => {
return new Promise(resolve => setTimeout(() => resolve('hello'), 2000))
}
const disableBtn = () => {
// button.disabled = true
// button.style.opacity = '0.5'
setOpacity(0.5)
setDisabled(true);
}
const enableBtn = () => {
// button.disabled= false
// button.style.opacity = '1'
setOpacity(1)
setDisabled(false);
}
//let opacityValue =1
//props.disabled ? 0.5 : 1;
const undoClickButton = () => {
unsubscribeEmail()
setStatus(false)
}
const changeButtonState = () => {
status ? undoClickButton() : submitClickButton()
}
const subscribe = () => {
// const subscribeBtn = document.getElementById('subscribeButton')
setSubscribe('Unsubscribe')
// document.getElementById('emailForm').style.display = 'none'
localStorage.setItem('isSubscribed', 'true')
setDisplay('none')
console.log(status)
// setEmail('')
//document.getElementById('submit-info').value = ''
//localStorage.setItem('Email', '')
}
const unsubscribe = () => {
//const subscribeBtn = document.getElementById('subscribeButton')
setSubscribe('Subscribe')
//document.getElementById('emailForm').style.display = 'block'
localStorage.setItem('isSubscribed', 'false')
setDisplay('block')
console.log(status)
}
const subscribeEmail = (email) => {
const isValidEmail = validateEmail(email)
if (isValidEmail === true) {
subscribe()
} else if (isValidEmail === false) {
unsubscribe()
}
}
const unsubscribeEmail = () => {
unsubscribe()
unsubscribeUser()
setEmail('')
localStorage.removeItem('Email')
}
//--------------------------------------
return (
<>
<main id='app-container'>
<section className='app-section app-section--image-program' id='programContainer'>
<h2 className='program-title'>Join Our Program</h2>
<h3 className='program-subtitle'>Sed do eiusmod tempor incididunt<br />ut labore et dolore magna aliqua</h3>
<form className='submitFieldWrapper' id='form'>
<div
className="form-wrapper"
id="emailForm"
style={{
display: displayMode
}}
>
<input
className='form-input'
id='submit-info'
type='text'
placeholder='Email'
value={email}
onChange={(e) => setEmail(e.target.value)}
></input>
</div>
<input
id='subscribeButton'
className='app-section__button submit-btn'
type='button'
value={isSubscribed}
style={{
opacity
}}
onClick={() => { changeButtonState() }}
disabled={isDisabled} ></input>
</form>
</section>
</main>
</>
)
}
export default JoinUsSection
In many components, I need to fetch some data and I'm ending up with a lot of similar code. It looks like this:
const [data, setData] = useState();
const [fetchingState, setFetchingState] = useState(FetchingState.Idle);
useEffect(
() => {
loadDataFromServer(props.dataId);
},
[props.dataId]
);
async function loadDataFromServer(id) {
let url = new URL(`${process.env.REACT_APP_API}/data/${id}`);
let timeout = setTimeout(() => setFetchingState(FetchingState.Loading), 1000)
try {
const result = await axios.get(url);
setData(result.data);
setFetchingState(FetchingState.Idle);
}
catch (error) {
setData();
setFetchingState(FetchingState.Error);
}
clearTimeout(timeout);
}
How can I put it into a library and reuse it?
Thank you guys for the suggestion, I came up with the following hook. Would be happy to some critics.
function useFetch(id, setData) {
const [fetchingState, setFetchingState] = useState(FetchingState.Idle);
useEffect(() => { loadDataFromServer(id); }, [id]);
async function loadDataFromServer(id) {
let url = new URL(`${process.env.REACT_APP_API}/data/${id}`);
let timeout = setTimeout(() => setFetchingState(FetchingState.Loading), 1000)
try {
const result = await axios.get(url);
setData(result.data);
setFetchingState(FetchingState.Idle);
}
catch (error) {
setData();
setFetchingState(FetchingState.Error);
}
clearTimeout(timeout);
}
return fetchingState;
}
And this is how I use it:
function Thread(props) {
const [question, setQuestion] = useState();
const fetchingState = useFetch(props.questionId, setQuestion);
if (fetchingState === FetchingState.Error) return <p>Error while getting the post.</p>;
if (fetchingState === FetchingState.Loading) return <Spinner />;
return <div>{JSON.stringify(question)}</div>;
}
You can wrap your APIs calls in /services folder and use it anywhere
/services
- Auth.js
- Products.js
- etc...
Example
Auth.js
import Axios from 'axios';
export const LoginFn = (formData) => Axios.post("/auth/login", formData);
export const SignupFn = (formData) => Axios.post("/auth/signup", formData);
export const GetProfileFn = () => Axios.get("/auth/profile")
in your component
import React, { useState } from 'react'
import { LoginFn } from '#Services/Auth'
export LoginPage = () => {
const [isLoading, setIsLoading] = useState(false);
const LoginHandler = (data) => {
setIsLoading(true)
LoginFn(data).then(({ data }) => {
// do whatever you need
setIsLoading(false)
})
}
return (
<form onSubmit={LoginHandler}>
.......
)
}
I found myself continuously writing the same shape of code for asynchronous calls so I tried to wrap it up in something that would abstract some of the details. What I was hoping was that in my onError callback I could pass a reference of the async function being executed so that some middleware could implement retry logic if it was necessary. Maybe this is a code smell that I'm tackling this the wrong way but I'm curious if it's possible or if there are other suggestions for handling this.
const runAsync = (asyncFunc) => {
let _onBegin = null;
let _onCompleted = null;
let _onError = null;
let self = this;
return {
onBegin(f) {
_onBegin = f;
return this;
},
onCompleted(f) {
_onCompleted = f;
return this;
},
onError(f) {
_onError = f;
return this;
},
async execute() {
if (_onBegin) {
_onBegin();
}
try {
let data = await asyncFunc();
if (_onCompleted) {
_onCompleted(data);
}
} catch (e) {
if (_onError) {
_onError(e ** /*i'd like to pass a function reference here as well*/ ** );
}
return Promise.resolve();
}
},
};
};
await runAsync(someAsyncCall())
.onBegin((d) => dispatch(something(d)))
.onCompleted((d) => dispatch(something(d)))
.onError((d, func) => dispatch(something(d, func)))
.execute()
I'm thinking you could use a custom hook. Something like -
import { useState, useEffect } from 'react'
const useAsync = (f) => {
const [state, setState] =
useState({ loading: true, result: null, error: null })
const runAsync = async () => {
try {
setState({ ...state, loading: false, result: await f })
}
catch (err) {
setState({ ...state, loading: false, error: err })
}
}
useEffect(_ => { runAsync() }, [])
return state
}
Now we can use it in a component -
const FriendList = ({ userId }) => {
const response =
useAsync(UserApi.fetchFriends(userId)) // <-- some promise-returning call
if (response.loading)
return <Loading />
else if (response.error)
return <Error ... />
else
return <ul>{response.result.map(Friend)}</ul>
}
The custom hook api is quite flexible. The above approach is naive, but we can think it through a bit more and make it more usable -
import { useState, useEffect } from 'react'
const identity = x => x
const useAsync = (runAsync = identity, deps = []) => {
const [loading, setLoading] = useState(true)
const [result, setResult] = useState(null)
const [error, setError] = useState(null)
useEffect(_ => {
Promise.resolve(runAsync(...deps))
.then(setResult, setError)
.finally(_ => setLoading(false))
}, deps)
return { loading, error, result }
}
Custom hooks are dope. We can make custom hooks using other custom hooks -
const fetchJson = (url = "") =>
fetch(url).then(r => r.json()) // <-- stop repeating yourself
const useJson = (url = "") => // <-- another hook
useAsync(fetchJson, [url]) // <-- useAsync
const FriendList = ({ userId }) => {
const { loading, error, result } =
useJson("some.server/friends.json") // <-- dead simple
if (loading)
return <Loading .../>
if (error)
return <Error .../>
return <ul>{result.map(Friend)}</ul>
}