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
Related
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;
Bit of a noob to redux but this appears to be quite a difficult question! I hope someone may be able to help me :)
I have build a page where you can input a search for different types of jobs. From this, it will make a get request to my DB and get all the info on this job. As this page is multi-levelled, I want to use redux to dispatch and pass the state throughout. This will help me pass my data on the job, e.g Data Analyst, through to each component so it can use the data and populate fields.
However, this was how my input field was originally setup:
export function SearchBarComp(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside ] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [jobPostings, setjobPostings] = useState([]);
const [noRoles, setNoRoles] = useState(false)
const isEmpty = !jobPostings || jobPostings.length === 0;
const changeHandler = (e) => {
//prevents defaulting, autocomplete
e.preventDefault();
if(e.target.value.trim() === '') setNoRoles(false);
setSearchQuery(e.target.value);
}
const expandedContainer = () => {
setExpanded(true);
}
//LINK THE BACKEND!
const prepareSearchQuery = (query) => {
//const url = `http://localhost:5000/api/role/title?title=${query}`;
const url = `http://localhost:5000/api/role/titlerole?title=${query}`;
//replaces bad query in the url
return encodeURI(url);
}
const searchRolePosition = async () => {
if(!searchQuery || searchQuery.trim() === "")
return;
setLoading(true);
setNoRoles(false);
const URL = prepareSearchQuery(searchQuery);
const response = await axios.get(URL).catch((err) => {
console.log(err);
});
if(response) {
console.log("Response", response.data);
if(response.data && response.data === 0)
setNoRoles(true);
setjobPostings(response.data);
}
setLoading(false);
}
useDebounce(searchQuery, 500, searchRolePosition)
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoRoles(false);
if (inputRef.current) inputRef.current.value = "";
};
// console.log("Value", searchQuery)
useEffect(()=> {
if(isClickedOutside)
collapseContainer();
}, [isClickedOutside])
return (
<SearchBarContainer animate = {isExpanded ? "expanded" : "collapsed"}
variants={containerVariants} transition={containerTransition} ref={parentRef}>
<SearchInputContainer>
<SearchIconSpan>
<SearchIcon/>
</SearchIconSpan>
<SearchInput placeholder = "Search for Roles"
onFocus={expandedContainer}
ref={inputRef}
value={searchQuery}
onChange={changeHandler}
/>
<AnimatePresence>
{isExpanded && (<CloseIconSpan key="close-icon"
inital={{opacity:0, rotate: 0}}
animate={{opacity:1, rotate: 180}}
exit={{opacity:0, rotate: 0}}
transition={{duration: 0.2}}
onClick={collapseContainer}>
<CloseIcon/>
</CloseIconSpan>
)}
</AnimatePresence>
</SearchInputContainer>
{isExpanded && <LineSeperator/>}
{isExpanded && <SearchContent>
{!isLoading && isEmpty && !noRoles && (
<Typography color="gray" display="flex" flex="0.2" alignSelf="center" justifySelf="center">
Start typing to search
</Typography>
)}
{!isLoading && !isEmpty && <>
{jobPostings.map((searchRolePosition) => (
<JobSection
title={searchRolePosition.title}
//will need to do something like ----
//people = {searchRolePosition.title && searchRolePosition.title.average}
// future implementations
/>
))}
</>}
</SearchContent>}
</SearchBarContainer>
)
}
As you can see, the main thing is the 'query' this creates a backend request to my titlerole, such as getting the data on Data Analyst. This all works in my frontend right now, but I can't pass that data down to the next component etc
So i'm looking to use redux.
I've created the following slice:
import { createSlice } from "#reduxjs/toolkit";
const jobSearchSlice = createSlice({
name: "jobsearch",
initialState: {
currentRole: null,
isFetching: false,
error: false,
},
reducers: {
jobsearchStart: (state) => {
state.isFetching = true;
},
jobsearchSuccess: (state, action) => {
state.isFetching = false;
state.currentRole = action.payload;
},
jobsearchFailure: (state) => {
state.isFetching = false;
state.error = true;
},
},
});
export const { jobsearchStart, jobsearchSuccess, jobsearchFailure } = jobSearchSlice.actions;
export default jobSearchSlice.reducer;
With this, I'm also using the following apiCalls.js file:
import { jobsearchStart, jobsearchSuccess, jobsearchFailure } from "./jobsearchSlice";
import { publicRequest } from "../requestMethods";
export const roleSearchQuery = async (dispatch, jobsearch) => {
dispatch(jobsearchStart());
try{
const res = await publicRequest.get("`http://localhost:5000/api/role/titlerole?title=${query}`", jobsearch);
dispatch(jobsearchSuccess(res.data))
}catch(err){
dispatch(jobsearchFailure());
}
};
My question is as a Redux noob, how do I implement this query functionality into a redux API request? What's the way to do this properly as I begin to tranisition this to an app which uses standard state management!
i want to send message while typing text by using websocket.
but have websocket reference error while typing text.
i think ShareTextComponent onInputEvent function parameter is not exeucte in another location.
so websocket property can't reference in this field
but i don't know how can i fix it.
and i didn't use any state management library such as redux and mobx.
just pure reactjs
[error]
[code]
const ShareTextComponentView = (props) => {
const [isShareMode, setShareMode] = useState(false)
const [syncText, setSyncText] = useState("")
const [isOpen, setOpen] = useState(false)
const [name, setName] = useState("")
let ws = null;
const connect = () => {
ws = new WebSocket(ADDRESS + `/${name}`)
//액세스코드들을 전부 보냄
ws.onopen = () => {
ws.send(JSON.stringify({command: "CONNECT", accessCode: generateAccessCode()}))
console.log(`${ADDRESS}에 접속 되었습니다.`)
setOpen(true)
}
ws.onclose = () => {
ws.send(JSON.stringify({command: "CLOSE"}))
console.log(`${ADDRESS}에 접속 종료 되었습니다.`)
setOpen(false)
}
ws.onmessage = (evt) => {
if (isShareMode) {
return
}
const message = JSON.parse(evt.data)
console.log({message: message})
setSyncText(message.data)
}
ws.onerror = (err) => {
console.log("접속중에 에러가 발생되었습니다.")
console.log(err)
}
}
const close = () => {
if (ws !== null && ws.readyState !== WebSocket.CLOSED) {
ws.close()
}
}
// p2p로 웹소켓으로 연결
useEffect(() => {
if (isOpen) {
return
}
connect()
setOpen(true)
return () => {
//만약 공유모드를 종료했을때 websocket에 shutdown 메세지를 보냄
if (isOpen) {
close()
console.log(`${ADDRESS}에 접속 종료 되었습니다.`)
}
setOpen(false)
}
}, [isShareMode])
const onTextInput = (text) => {
const {name, value} = text.target
if (!isShareMode) {
return
}
console.log("websocket status")
console.log(ws)
console.log("input value")
console.log(value)
if (ws.readyState === WebSocket.CLOSED) {
console.log("Connect Close 되었습니다.")
} else {
ws.send(JSON.stringify({command: "SEND", message: value}))
}
}
const generateAccessCode = () => {
return "hello"
}
const reconnect = () => {
connect()
console.log(ws)
}
return (
<div className="container">
<h1> Please Enter This Area Section</h1>
<h1> {isOpen ? "Connect" : "Disconnect"}</h1>
<div className="name-container">
<label> Name : </label>
<input type="text" onChange={(e) => {
setName(e.target.value)
}}/>
</div>
<button className="reconnect-mode" onClick={reconnect}>Connect</button>
<button className="is-receiever" onClick={() => setShareMode(!isShareMode)}>공유자 입니까?</button>
<h1>{isShareMode ? "공유모드" : "수신모드"}</h1>
<ShareTextComponent accessCode={generateAccessCode()} onInputEvent={onTextInput} syncText={syncText}/>
</div>
)
}
export default ShareTextComponentView;
[after logging in onTextInput]
Add a null check to the top of the function ->
const onTextInput = (text) => {
if (!ws) return;
This will at least help you get past that error and narrow down the rest of the flow.
So I was trying to implement a filter that is controlled by a search bar input. So I think part of the problem is that I have this filter hooked on a timer so that while the user is typing into the search bar, it isn't re-running for each letter typed in.
What it is currently doing is that after the item is typed in the search bar, the timer goes off and the filters are working but it doesn't appear that the app is re-rendering with the new filtered variable.
I suspect that it might have something to do with useEffect but I'm having trouble wrapping my head around it and it wasn't working out for whatever I was doing with it.
Here's the code:
const RecipeCards = (props) => {
const inputTypingRef = useRef(null);
let preparingElement = props.localRecipes;
let cardElement;
let elementsSorted;
const ingredientCountSort = (recipes) => {
elementsSorted = ...
}
const elementRender = (element) => {
cardElement = element.map((rec) => (
<RecipeCard
name={rec.name}
key={rec.id}
ingredients={rec.ingredients}
tags={rec.tags}
removeRecipe={() => props.onRemoveIngredients(rec.id)}
checkAvail={props.localIngredients}
/>
));
ingredientCountSort(cardElement);
};
if (inputTypingRef.current !== null) {
clearTimeout(inputTypingRef.current);
}
if (props.searchInput) {
inputTypingRef.current = setTimeout(() => {
inputTypingRef.current = null;
if (props.searchOption !== "all") {
preparingElement = props.localRecipes.filter((rec) => {
return rec[props.searchOption].includes(props.searchInput);
});
} else {
preparingElement = props.localRecipes.filter((rec) => {
return rec.includes(props.searchInput);
});
}
}, 600);
}
elementRender(preparingElement);
return (
<div className={classes.RecipeCards}>{!elementsSorted ? <BeginPrompt /> : elementsSorted}</div>
);
};
Don't worry about ingredientCountSort() function. It's a working function that just rearranges the array of JSX code.
Following up to my comment in original question. elementsSorted is changed, but it doesn't trigger a re-render because there isn't a state update.
instead of
let elementsSorted
and
elementsSorted = ...
try useState
import React, { useState } from 'react'
const RecipeCards = (props) => {
....
const [ elementsSorted, setElementsSorted ] = useState();
const ingredientCountSort = () => {
...
setElementsSorted(...whatever values elementsSorted supposed to be here)
}
Reference: https://reactjs.org/docs/hooks-state.html
I used useEffect() and an additional useRef() while restructuring the order of functions
const RecipeCards = (props) => {
//const inputTypingRef = useRef(null);
let preparingElement = props.localRecipes;
let finalElement;
const [enteredFilter, setEnteredFilter] = useState(props.searchInput);
let elementsSorted;
const [elementsFiltered, setElementsFiltered] = useState();
const refTimer = useRef();
const filterActive = useRef(false);
let cardElement;
useEffect(() => {
setEnteredFilter(props.searchInput);
console.log("updating filter");
}, [props.searchInput]);
const filterRecipes = (recipes) => {
if (enteredFilter && !filterActive.current) {
console.log("begin filtering");
if (refTimer.current !== null) {
clearTimeout(refTimer.current);
}
refTimer.current = setTimeout(() => {
refTimer.current = null;
if (props.searchOption !== "all") {
setElementsFiltered(recipes.filter((rec) => {
return rec.props[props.searchOption].includes(enteredFilter);
}))
} else {
setElementsFiltered(recipes.filter((rec) => {
return rec.props.includes(enteredFilter);
}))
}
filterActive.current = true;
console.log(elementsFiltered);
}, 600);
}else if(!enteredFilter && filterActive.current){
filterActive.current = false;
setElementsFiltered();
}
finalElement = elementsFiltered ? elementsFiltered : recipes;
};
const ingredientCountSort = (recipes) => {
console.log("sorting elements");
elementsSorted = recipes.sort((a, b) => {
...
filterRecipes(elementsSorted);
};
const elementRender = (element) => {
console.log("building JSX");
cardElement = element.map((rec) => (
<RecipeCard
name={rec.name}
key={rec.id}
ingredients={rec.ingredients}
tags={rec.tags}
removeRecipe={() => props.onRemoveIngredients(rec.id)}
checkAvail={props.localIngredients}
/>
));
ingredientCountSort(cardElement);
};
//begin render /////////////////// /// /// /// /// ///
elementRender(preparingElement);
console.log(finalElement);
return (
<div className={classes.RecipeCards}>{!finalElement[0] ? <BeginPrompt /> : finalElement}</div>
);
};
There might be redundant un-optimized code I want to remove on a brush-over in the future, but it works without continuous re-renders.
While my code works from the functionalities, I have to click "refresh" after each click to see the changes. For example when I click "Add note" I have to refresh the page in order to see it. While it compiles successfully, the console shows three errors:
import { API, graphqlOperation } from "aws-amplify";
import { withAuthenticator } from "aws-amplify-react";
import React, { useEffect, useState } from "react";
import { createNote, deleteNote, updateNote } from "./graphql/mutations";
import { listNotes } from "./graphql/queries";
import {
onCreateNote,
onDeleteNote,
onUpdateNote
} from "./graphql/subscriptions";
const App = () => {
const [id, setId] = useState("");
const [note, setNote] = useState("");
const [notes, setNotes] = useState([]);
useEffect(() => {
getNotes();
const createNoteListener = API.graphql(
graphqlOperation(onCreateNote)
).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote;
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id);
const updatedNotes = [...oldNotes, newNote];
return updatedNotes;
});
setNote("");
}
});
const deleteNoteListener = API.graphql(
graphqlOperation(onDeleteNote)
).subscribe({
next: noteData => {
const deletedNote = noteData.value.data.onDeleteNote;
setNotes(prevNotes => {
const updatedNotes = prevNotes.filter(
note => note.id !== deletedNote.id
);
return updatedNotes;
});
}
});
const updateNoteListener = API.graphql(
graphqlOperation(onUpdateNote)
).subscribe({
next: noteData => {
const updatedNote = noteData.value.data.onUpdateNote;
setNotes(prevNotes => {
const index = prevNotes.findIndex(note => note.id === updatedNote.id);
const updatedNotes = [
...prevNotes.slice(0, index),
updatedNote,
...prevNotes.slice(index + 1)
];
return updatedNotes;
});
setNote("");
setId("");
}
});
return () => {
createNoteListener.unsubscribe();
deleteNoteListener.unsubscribe();
updateNoteListener.unsubscribe();
};
}, []);
const getNotes = async () => {
const result = await API.graphql(graphqlOperation(listNotes));
setNotes(result.data.listNotes.items);
};
const handleChangeNote = event => setNote(event.target.value);
const hasExistingNote = () => {
if (id) {
const isNote = notes.findIndex(note => note.id === id) > -1;
return isNote;
}
return false;
};
const handleAddNote = async event => {
event.preventDefault();
// Check if we have an exisiting note. If so, then update it.
if (hasExistingNote()) {
handleUpdateNote();
} else {
const input = { note };
await API.graphql(graphqlOperation(createNote, { input }));
}
};
const handleUpdateNote = async () => {
const input = { id, note };
await API.graphql(graphqlOperation(updateNote, { input }));
};
const handleDeleteNote = async noteId => {
const input = { id: noteId };
await API.graphql(graphqlOperation(deleteNote, { input }));
};
const handleSetNote = ({ note, id }) => {
setNote(note);
setId(id);
};
return (
<div className="flex flex-column items-center justify-center pa3 bg-washed-red">
<h1 className="code f2-l">Amplify Notetake</h1>
{/* Note Form */}
<form onSubmit={handleAddNote} className="mb3">
<input
type="text"
className="pa2 f4"
placeholder="Write your note"
onChange={handleChangeNote}
value={note}
/>
<button className="pa2 f4" type="submit">
{id ? "Update note" : "Add note"}
</button>
</form>
{/* Notes list */}
<div>
{notes.map(item => (
<div key={item.id} className="flex items-center">
<li onClick={() => handleSetNote(item)} className="list pa1 f3">
{item.note}
</li>
<button
onClick={() => handleDeleteNote(item.id)}
className="bg-transparent bn f4"
>
<span>×</span>
</button>
</div>
))}
</div>
</div>
);
};
export default withAuthenticator(App, { includeGreetings: true });
If you are using CLI version 2.0 and above, owner is a required argument. This is explained more in the link below:
https://aws-amplify.github.io/docs/cli-toolchain/graphql#authorizing-subscriptions
After I added Auth in the import
import { API, graphqlOperation, Auth } from 'aws-amplify';
captured the current user and passed it into the subscription it started working for me.
useEffect(() => {
getNotes();
const owner = Auth.user.getUsername();
const createNoteListener = API.graphql(
graphqlOperation(onCreateNote, { owner })
).subscribe({
next: noteData => {
const newNote = noteData.value.data.onCreateNote;
setNotes(prevNotes => {
const oldNotes = prevNotes.filter(note => note.id !== newNote.id);
const updatedNotes = [...oldNotes, newNote];
return updatedNotes;
});
setNote("");
}
});