I have a handleValid function to validate a form, and when I click submit, the function is triggered for validation and calls handleSelfValidation, in the handleSelfValidation app they write the form state information and change the state, but handleInfoCheck is looking at the previous state, and for this reason I need to click twice to "Send".
const handleValid = () => {
members
.filter((member: Tourist) => {
return member.createdIn === touristCreatedIn && !member.isEmployee;
})
.forEach((member: any, index: any) => {
personSchema
.validate(member, { abortEarly: false })
.then(() => {
setFieldError({
[index]: {}
})
})
.catch((errs: any) => {
setFieldError({})
errs?.inner?.forEach((err: any) => {
setFieldError((prev)=> ({
...prev,
[index]: {
...prev[index],
[err.path]: err.message,
},
}))
});
});
personSchema
.isValid(member)
.then((v: any) => {
console.log('тут', v, index)
handleSelfValidation(v, index); //isFormValid - true
})
.catch((err: any) => {
// eslint-disable-next-line
console.error('TouristData YUP isValid Err', err);
});
});
setTimeout(handleInfoCheck);
};
const handleSelfValidation = (isFormValid: boolean, formIndex: number) => {
console.log(isFormValid, formIndex, 'test')
setIsFormsValid((prev) => ({
...prev,
[formIndex]: isFormValid,
}))
};
const handleInfoCheck = () => {
setFirstVisit();
if (
Object.values(isFormsValid).every((item: any) => {
return item === true;
})
) {
switch (permissionType) {
case 'tour':
history.push(`${addTourUrl}/tour-data`);
break;
case PERMISSION_TYPE_TRANZIT:
history.push(`${addTourUrl}/tranzit-data`);
break;
default:
history.push(`${addTourUrl}/tour-data`);
break;
}
}
};
Issue
The issue here is that React state updates are asynchronously processed, and the state from the current render cycle is closed over in handleValid/handleInfoCheck callback scope.
Solution
Allow the isFormsValid state update to occur in handleSelfValidation and use a useEffect hook with a dependency on isFormsValid to run the additional code.
const handleValid = () => {
members
.filter((member: Tourist) => {
return member.createdIn === touristCreatedIn && !member.isEmployee;
})
.forEach((member: any, index: any) => {
...
personSchema
.isValid(member)
.then((v: any) => {
console.log('тут', v, index)
handleSelfValidation(v, index); // <-- updates state
})
.catch((err: any) => {
// eslint-disable-next-line
console.error('TouristData YUP isValid Err', err);
});
});
};
...
useEffect(() => {
if (isFormsValid) {
handleInfoCheck();
}
}, [handleInfoCheck, isFormsValid]);
Related
I'm trying to keep session stayed logged in after refreshing the browser. The user data that is being fetched is not rendering after being fetched. The console is saying "Cannot read properties of undefined (reading 'user'). This is my code for the login/sign up page.
The data I'm trying to access is in the picture below:
(Auth.js)
const Auth = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [isSignup, setIsSignup] = useState(false);
const [inputs, setInputs] = useState({
name: "",
username: "",
email: "",
password: ""
})
const handleChange = (e) => {
setInputs(prevState => {
return {
...prevState,
[e.target.name]: e.target.value
}
})
}
const sendRequest = async (type = '') => {
const res = await axios.post(`/user/${type}`, {
name: inputs.name,
email: inputs.email,
username: inputs.username,
password: inputs.password,
}).catch(error => console.log(error))
const data = await res.data;
console.log(data)
return data;
}
const handleSubmit = (e) => {
e.preventDefault()
console.log(inputs)
if (isSignup) {
sendRequest("signup")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
} else {
sendRequest("login")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
}
}
Redux store file
const authSlice = createSlice({
name: "auth",
initialState: { isLoggedIn: false },
reducers: {
login(state) {
state.isLoggedIn = true
},
logout(state) {
state.isLoggedIn = false
}
}
})
export const authActions = authSlice.actions
export const store = configureStore({
reducer: authSlice.reducer
})
Chaining promises using .then() passes the resolved value from one to the next. With this code...
sendRequest("...")
.then(() => dispatch(authActions.login()))
.then(() => navigate("/posts"))
.then(data => localStorage.setItem('token', data.user))
You're passing the returned / resolved value from navigate("/posts") to the next .then() callback. The navigate() function returns void therefore data will be undefined.
Also, your redux action doesn't return the user so you can't chain from that either.
To access the user data, you need to return it from sendRequest()...
const sendRequest = async (type = "") => {
try {
const { data } = await axios.post(`/user/${type}`, { ...inputs });
console.log("sendRequest", type, data);
return data;
} catch (err) {
console.error("sendRequest", type, err.toJSON());
throw new Error(`sendRequest(${type}) failed`);
}
};
After that, all you really need is this...
sendRequest("...")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
Since you're using redux, I would highly recommend moving the localStorage part out of your component and into your store as a side-effect.
I'm getting this error when triggering a setState inside of a custom React hook. I'm not sure of how to fix it, can anyone show me what I'm doing wrong. It is getting the error when it hits handleSetReportState() line. How should I be setting the report state from inside the hook?
custom useinterval poll hook
export function usePoll(callback: IntervalFunction, delay: number) {
const savedCallback = useRef<IntervalFunction | null>()
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
function tick() {
if (savedCallback.current !== null) {
savedCallback.current()
}
}
const id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])
}
React FC
const BankLink: React.FC = ({ report: _report }) => {
const [report, setReport] = React.useState(_report)
if ([...Statues].includes(report.status)) {
usePoll(async () => {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}, 5000)
}
... rest
This is because you put usePoll in if condition, see https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
You can put the condition into the callback
usePoll(async () => {
if ([...Statues].includes(report.status)) {
const initialStatus = _report.status
const { result } = await apiPost(`/links/search` });
const currentReport = result.results.filter((item: { id: string; }) => item.id === _report.id)
if (currentReport[0].status !== initialStatus) {
handleSetReportState(currentReport[0])
console.log('status changed')
} else {
console.log('status unchanged')
}
}
}, 5000)
And if the delay will affect report.status, use ref to store report.status and read from ref value in the callback.
I'm working on a project with graphs and I need to be able to cancel my requests if the user selects a different tab.
Here's my API call
export const getDifferentialData = (
sourceId: string,
sourceLine: string,
source: any
) => {
const graph1Request = getData(
sourceId,
sourceLine,
source
)
const graph2Request = getData(
sourceId,
sourceLine,
source
)
return Promise.all([graph1Request, graph2Request]).then(results => {
const [graphA, graphB] = results
return {
graphA: parsedData(graphA),
graphB: parsedData(graphB),
}
})
}
export const getData = (
sourceId: string,
sourceLine: string,
source?: any
) => {
if (sourceId && sourceLine) {
return api.get(`apiGoesHere`, { cancelToken: source.token }).then(response => {
const { data } = response
return parsedData(data)
})
} else {
return api.get(`apiGoesHere`, { cancelToken: source.token }).then(response => {
const { data } = response
return parsedData(data)
})
}
}
And the component where I'm doing the call. userDidChangeTab is called when pressing on a tab and it calls fetchGraph
const Graph: FC<Props> = () => {
const source = axios.CancelToken.source();
// we ensure that the query filters are up to date with the tab selected
const userDidChangeTab = (tabIndex: number) => {
const isDifferentialTabSelected = isDifferentialTab(tabIndex)
let newFilters = queryFilters
if (isDifferentialTabSelected) {
newFilters = {
// props go here
}
} else {
newFilters = {
// props go here
}
}
source.cancel()
fetchGraph(isDifferentialTabSelected)
setActiveTab(tabIndex)
}
// Function to fetch two differential graphs.
const fetchGraph = (isDifferential: boolean) => {
setFetching(true)
if (isDifferential) {
getDifferentialData(
sourceId,
sourceLine,
source
)
.then(({ graphA, graphB }: any) => {
setGraphData(graphA)
setMatchData(new diffMatch(graphA, graphB, 1.0))
})
.catch(reason => {
const errorMessage = errorMessageFromReason(reason)
addMessageToContainer(errorMessage, true)
})
.finally(() => {
setFetching(false)
})
} else {
getGraph(
sourceId,
sourceLine,
source
)
.then((graphData: any) => {
setGraphData(graphData)
setMatchData(null)
})
.catch(reason => {
const errorMessage = errorMessageFromReason(reason)
addMessageToContainer(errorMessage, true)
})
.finally(() => {
setFetching(false)
})
}
}
}
I'm making simple To Do List app,Everything is working.I just want to make sure I'm doing it right without any mistakes.
I'm concerned about Check box update part,Please check the code and tell me if I'm doing anything wrong.
Here is the put method for Checkboxes
checkBoxRouteUpdate = () => {
let {todos} = this.state
let newArray = [...todos]
axios
.put(`http://localhost:8080/checkEdit/`, {
checked: newArray.every(todo => todo.checked)
}).then((res) => {
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}
checking all of them
checkAllCheckBox = () => {
let {todos} = this.state
let newArray = [...todos]
if (newArray.length !==0) {
newArray.map(item => {
if (item.checked === true) {
return item.checked = false
} else {
return item.checked = true
}
})
this.checkBoxRouteUpdate()
this.setState({todos: newArray})
}
}
Checking single Check Box
checkSingleCheckBox = (id) => {
let {todos} = this.state
let newArray = [...todos]
newArray.forEach(item => {
if (item._id === id) {
item.checked = !item.checked
axios
.put(`http://localhost:8080/edit/${id}`,{
checked:item.checked
})
.then(res => {
this.setState({todos: newArray})
console.log('res',res)
})
.catch((err) => {
console.log("err", err);
});
} else {
}
})
}
Deleting Only Checked Items
deleteAllChecked = () => {
const todos = this.state.todos.filter((item => item.checked !== true))
axios
.delete('http://localhost:8080/deleteAllChecked')
.then((res) => {
this.setState({ todos,
pageCount: Math.ceil(todos.length / 10)})
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}
You can check/uncheck them another way
this.checkBoxRouteUpdate()
this.setState(state => ({
...state,
todos: state.todos.map(todo => ({
...todo,
checked: !item.checked
}))
}))
I think you should delete after api returns ok status
.then((res) => {
this.setState(state => {
const todos = state.todos.filter((item => item.checked !== true));
return {
...state,
todos,
pageCount: Math.ceil(todos.length / 10)
}
})
I add a lot of comments, some of these some just another way to do what you do and others are personal preferences, but the most important is that you can see alternatives ways to do things :).
checkBoxRouteUpdate = () => {
const todos = [...this.state.todos] // Better use const and initialize the array of objects directly
/*since you will use this array just in one place, is better if you iterate in
the [...todos] directly without save it in a variable
let newArray = [...todos]
*/
axios
.put(`http://localhost:8080/checkEdit/`, {
checked: todos.every(({checked}) => checked) // here you can use destructuring to get checked
}).then((res) => {
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}
```
checking all of them
```
checkAllCheckBox = () => {
const todos = [...this.state.todos] // Better use const and initialize the array of objects directly
// let newArray = [...todos] same as in the first function,
// isn't neccesary this if because if the array is empty, the map doesn't will iterate
// if (newArray.length !==0) {
/* this is optional, but you can write this like
const modifiedTodos = [...todos].map(({checked}) => checked = !checked)
*/
/* In general, is better use const when possible because in this way
you will reassign a variable just when is necessary, and this is related with
avoid mutate values. */
const modifiedTodos = todos.map(item => {
if (item.checked === true) {
return item.checked = false
} else {
return item.checked = true
}
})
this.checkBoxRouteUpdate()
this.setState({ todos: modifiedTodos })
}
// Checking single Check Box
checkSingleCheckBox = (id) => {
// since you need be secure that the todos is an array, you can do this instead of the destructuring
const todos = [...this.state.todos]
// same as in the above function
// let newArray = [...todos]
// Here is better to use destructuring to get the _id and checked
[...todos].forEach(({checked, _id}) => {
/* this is totally personal preference but I try to avoid put a lot of code inside an if,
to do this, you can do something like:
if(_id !== id) return
and your code doesn't need to be inside the if
*/
if (_id === id) {
/* this mutation is a little difficult to follow in large codebase, so,
is better if you modified the value in the place you will use it*/
// checked = !item.checked
axios
.put(`http://localhost:8080/edit/${id}`, {
checked: !checked
})
.then(res => {
this.setState({ todos: todos }) // or just {todos} if you use the object shorthand notation
console.log('res', res)
})
.catch((err) => {
console.log("err", err);
});
}
// this else isn't necessary
// else {
// }
})
}
// Deleting Only Checked Items
deleteAllChecked = () => {
const todos = this.state.todos.filter((item => item.checked !== true))
/* Another way to do the above filtering is:
const todos = this.state.todos.filter((item => !item.checked))
*/
axios
.delete('http://localhost:8080/deleteAllChecked')
.then((res) => {
this.setState({
todos,
pageCount: Math.ceil(todos.length / 10)
})
console.log("res", res);
})
.catch((err) => {
console.log("err", err);
});
}
I've been baffled as to why an item - card does not disappear from the DOM after deletion and state update. My edit function works fine and I've soured my code for spelling errors and wrong variables, etc.
Here's my App (top component) state object:
state = {
decks: [],
cards: [],
selectedCards: [],
selectedDecks: [],
currentUser: null,
users: []
}
my Delete function (optimistic) in App that gets passed down to a deckLayout component:
deleteCard = (cardId, deckId) => {
const cardCopy = this.state.cards.slice()
const foundOldCardIdx = cardCopy.findIndex(card => card.id === cardId)
cardCopy.splice(foundOldCardIdx, 1)
this.setState({
cards: cardCopy
}, console.log(this.state.cards, cardCopy))
this.filterCards(deckId)
console.log(this.state.cards)
fetch(`http://localhost:9000/api/v1/cards/${cardId}`, {
method: 'DELETE'
})
};
And this is a filterCards functions that gets called after Delete and State update (this works for Edit):
filterCards = (deckId) => {
if (this.state.cards.length === 0) {
alert('No cards yet!')
} else {
const filteredCards = this.state.cards.filter(card => card.deck_id === deckId)
this.setState({
selectedCards: filteredCards
})
this.filterDecks()
}
};
which then calls a filterDecks function:
filterDecks = () => {
if (this.state.currentUser) {
const filteredDecks = this.state.decks.filter(deck => {
return deck.user_id === this.state.currentUser.id
})
this.setState({
selectedDecks: filteredDecks
})
} else {
alert('Login or sign up')
}
};