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')
}
};
Related
The problem I'm having is, that I have a useContext in which I provide all logged users. On the initial run of the app or when the users' log in the array gets populated with all the users that are currently on the server... Which works as expected. But I have also the functionality, that whenever the server "user-connected" event runs, the front-end should just push the user to the end of this array. And there lays the problem. From the backend, the right user is sent, but when I access the connectedUsers array, the array is empty... but it should be already populated.
UsersProvider.tsx
export const inState = {
connectedUsers: [],
addUser: (user: any) => {},
sortUsers: (user: any, socketID: string) => {},
setLoggedUsers: () => {},
};
export interface initState {
connectedUsers: any[];
addUser(user: any): void;
sortUsers(users: any, socketID: string): void;
setLoggedUsers: React.Dispatch < React.SetStateAction < any[] >> ;
}
const UsersContext = createContext < initState > (inState);
export const useUsers = () => {
return useContext(UsersContext);
};
const initUserProps = (user: any) => {
user.messages = [];
user.hasNewMessages = false;
};
export const UsersProvider = ({
children
}: Props) => {
const [connectedUsers, setLoggedUsers] = useState < any[] > ([]);
const addUser = (user: any) => {
console.log('add', connectedUsers);
// This is empty, but it should be already populated when next user connected.
};
const sortUsers = (users: any, socketUserID: string) => {
const usersCopy = users;
usersCopy.forEach((u: any) => {
for (let i = 0; i < usersCopy.length; i++) {
const existingUser = usersCopy[i];
if (existingUser.userID === u.userID) {
existingUser.connected = u.connected;
break;
}
}
u.self = u.userID === socketUserID;
initUserProps(u);
});
// put the current user first, and sort by username
let sorted = usersCopy.sort((a: any, b: any) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
setLoggedUsers([...sorted]);
};
return ( <
UsersContext.Provider value = {
{
connectedUsers,
setLoggedUsers,
addUser,
sortUsers
}
} >
{
children
} <
/UsersContext.Provider>
);
};
And the part of ChatBoard.tsx, you can find addUser function initiated whenever user-connected happens. I really don't know why the would array be empty, if it is populated on the first run with users event.
const ChatBoard = (props: Props) => {
const socket = useSocket();
const {
connectedUsers,
setLoggedUsers,
addUser,
sortUsers
} = useUsers();
useEffect(() => {
if (socket == null) return;
socket.on('users', (users) => {
console.log(users);
if (socket.userID) {
const socketID: string = socket ? .userID;
sortUsers(users, socketID);
}
});
socket.on('user-connected', (user: any) => {
console.log(user, 'this user connected!');
const connectingUser = user;
addUser(connectingUser);
});
socket.on('user-disconnected', (userID) => {
console.log('disconnected user');
const users = [...connectedUsers];
users.forEach((u) => {
if (u.userID === userID) {
u.connected = false;
setLoggedUsers([...users]);
}
});
});
return () => {
socket.off('users');
socket.off('user-connected');
};
}, [socket]);
CodeSandbox
So I have found the problem... so with React hooks sometimes a problem occurs called "Stale Closures", which means that React was picking up the old state (empty one, the one that was not yet populated and always returning that one.).
The solution to this problem, in my case is that when you use setState you use it with a callback. Like so, so you always get the latest state.
const addUser = (user: any) => {
setLoggedUsers((oldUsers) => {
const newUsers: any[] = [...oldUsers];
console.log(newUsers);
for (let i = 0; i < newUsers.length; i++) {
const existingUser = newUsers[i];
if (existingUser.userID === user.userID) {
existingUser.connected = true;
return newUsers;
}
}
initReactiveProperties(user);
newUsers.push(user);
return newUsers;
});
};
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]);
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 have i a component on scroll of bottom i need to increment the results of an api data but as total Results will be dynamic and when we reach the total Results count i dont want to call the increment results , stop api fetching and set loader to false,but api keeps calling again and again even if reach the total results count.
const initialState = {
resultsData: [],
resultsPerPage: 3,
totalResults: null,
loader: true,
}
Action
export const set_Results = (value) => {
return async (dispatch) => {
dispatch({ type: SET_RESULTS_PERPAGE, payload: value });
};
};
const getTotalResults = useSelector((state) => {
return state.results.totalResults;
});
const getresultsData = useSelector((state) => {
return state.results.resultsData;
});
Reducer
case SET_RESULTS_PERPAGE:
return {
...state,
resultsPerPage: action.payload,
};
dispatch
const loaderAndScroll = () => {
if (getresultsData.length < getTotalResults) {
return getResultsPerPage + 3;
}
};
const scrollToEnd = (event) => {
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
if (scrollHeight - scrollTop === clientHeight) {
dispatch(set_Results(loaderAndScroll()));
}
};
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);
});
}