Strange behavior after React state update with new entry - javascript

In my CRUD application, I am facing a problem every time the initial state ( array list ) updates with a new entry.
Clicking on an add button should open a modal and fill out a react-hook-form. On submit should update the state by adding the new entry.
Clicking on the edit button should open a modal and load data to the react-hook-form. On submit should update the state by updating the corresponding entry.
Everything works fine until I add a new entry in the state.
The entry is displayed in the table and clicking the edit button of that or any other entry works fine. When clicking the button of any entry, again the isEdit state stops to change as it should. I have a working demo here
In App.js
These are the states in the application. One for users list, one for distinguishing between add and update functions, and the last for passing the default values to the form when updating a user.
const [users, setUsers] = useState([]);
const [isEdit, setIsEdit] = useState(false);
const [defaultValues, setDefaultValues] = useState(initialDefaultValues);
There is a list of users coming from a GET request. When the request resolves successfully, I set the returned data to the state.
// get Users
const fetchUsers = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const resData = await res.json();
if (res.status === 200) {
setUsers(resData);
} else {
alert(resData);
}
};
// execute on component render
useEffect(() => {
fetchUsers();
}, []);
There is a component that renders a react-bootstrap-table-next and takes the data from the state.
<UsersTable
data={users}
prepareUpdateUser={prepareUpdateUser}
prepareDeleteUser={prepareDeleteUser}
/>
This table has two buttons for each entry. An edit button and a delete button. On click these buttons the two prepare functions are executed accordingly (prepareUpdateUser, prepareDeleteUser). This is the code for the prepareUpdateUser(). The function takes the user as an argument, changes the isEdit state to true, updates the defaultValues to pass to the react-hook-form, and finally opens the modal.
const prepareUpdateUser = (user) => {
setIsEdit(true);
setDefaultValues({
id: user.id,
name: user.name,
email: user.email
});
toggleUserModal();
};
When the modal close, I reset the isEdit and `defaultValues`` state to their initial values.
const [userModal, toggleUserModal] = useModali({
animated: true,
title: isEdit ? "Update User " : "Add User ",
onHide: () => {
isEdit && setIsEdit(false);
setDefaultValues(initialDefaultValues);
}
});
The problem is that after adding a new entry in the state and then try to click the edit button of any entry, everything works, but the next time you click the button the isEdit state stops updating every time the modal closes, and each time the prepareUpdateUser runs. The problem does not appear when updating an entry.
This is the code to add a new user to the users list
const addUser = (data) => {
const newArray = users.slice();
newArray.splice(0, 0, data);
setUsers(newArray);
toggleUserModal();
};
This is the code to update a user
const updateUser = (data) => {
const updatedUser = users.findIndex((user) => user.email === data.email);
const newArray = users.slice();
newArray[updatedUser] = data;
setUsers(newArray);
toggleUserModal();
};
If you are going to use the demo link, here are the steps to reproduce the problem.
Click on any edit button. Everything loads and the isEdit state is true.
Click the add button. The form is empty and the isEdit state is false.
Add a new entry.
The list updates with the new entry.
Click an edit button. Seems to work.
Click again an edit button and now isEdit is false and no data loaded in the form
Has anyone faced something similar? I am fairly new to react, and I cannot understand why this happens. Any advice will be really helpful.
Thank you all in advance.

**I have just modified below block of code**
const prepareUpdateUser = (user) => {
setIsEdit(true);
setDefaultValues({
id: user.id,
name: user.name,
email: user.email
});
};
**Newly added one useEffect**
useEffect(() => {
if (isEdit) {
toggleUserModal();
}
}, [isEdit]);
try this. Please give your valuable feedback

Related

How to fetch data only on first click via react-query and then disable refetch on subsequent clicks

I encountered a problem while implementing the form where the data used in select, retrieved from the database via react-query, should only be retrieved once when clicking on input, and subsequent clicks should not cause a refetch.
How to do it in clean way?
Hook:
export const useUnitOfMeasureSelectData = ({
queryPageIndex,
queryPageSize,
queryFilters,
enabled,
refetchOnWindowFocus,
}) => {
return useQuery(
["unitofmeasure-list", queryPageIndex, queryPageSize, queryFilters],
() => fetchItemData(queryPageIndex, queryPageSize, queryFilters),
{
keepPreviousData: true,
staleTime: 60000,
enabled,
refetchOnWindowFocus,
select: (data) => {
const categories = data.data.results.map((obj) => ({
value: obj.id,
label: obj.name,
}));
return categories;
},
}
);
};
In component calling onClick={()=>refetch()} refetch data on every click.
const {
data: unitOfMeasureSelectData = [],
refetch: refetchUnitOfMeasureSelectData,
} = useUnitOfMeasureSelectData({
queryPageIndex: queryPageIndex,
queryPageSize: queryPageSize,
queryFilters: filterString,
enabled: false,
refetchOnWindowFocus: false,
});
looks like you want a lazy query with infinite stale time:
const [enabled, setEnabled] = useState(false)
useQuery(key, fn, { enabled, staleTime: Infinity })
then, when you click on the input, you call setEnabled: true. The infinite stale time makes sure that the query is always considered fresh, so no more background refetch is happening.
The button in react/native has a property disabled that you can set to false when you are done. Probably the way to do this is with state
<button disabled={this.state.btnDisable}/>
Then you can update the state when you first click the button
//class component
this.setState({btnDisable: false})
//function component
setbtnDisable(false)

Discord.js - How to change style of Button

This is how I create and send the button:
client.on('messageCreate', (message) => {
/* ... Checking Command ... */
const actionRow = new MessageActionRow().addComponents(
new MessageButton()
.setStyle("PRIMARY")
.setLabel("X")
.setCustomId("test"));
message.channel.send({ content: "Test", components: [actionRow] });
}
A blue Button appears in the chat, as expected.
This is my Button-Listener:
client.on("interactionCreate", (interaction) => {
if (interaction.isButton()) {
if (interaction.customId === "test") {
//Before: console.log(interaction.component);
interaction.component.setStyle("DANGER");
//After: console.log(interaction.component);
}
}
});
Logging the component-object before and after .setStyle("DANGER") also reveals, that the style got changed from Primary to Danger successfully.
But in my Discord Client, the Style/Color didn't change, and ontop of that I am getting an error, saying that the interaction failed.
The style-property doesn't seem to be read-only: https://discord.js.org/#/docs/main/stable/class/MessageButton?scrollTo=style
So what am I doing wrong?
You updated the style only locally, you didn't send the changed component back to the Discord API.
To get rid of the error "This interaction failed", you need to respond to the interaction. One way to respond is to use MessageComponentInteraction.update(), which updates the original message.
client.on("interactionCreate", (interaction) => {
if (interaction.isButton()) {
if (interaction.customId === "test") {
// Change the style of received button component
interaction.component.setStyle("DANGER");
// Respond to the interaction,
// and send updated component to the Discord API
interaction.update({
components: [
new MessageActionRow().addComponents(interaction.component)
]
});
}
}
});
To make this work with multiple buttons, use the example below.
client.on("interactionCreate", (interaction) => {
if (interaction.isButton()) {
// Make this work only for certain buttons,
// with IDs like switch_0, switch_1, etc.
if (interaction.customId.startsWith("switch_")) {
// Change the style of the button component,
// that triggered this interaction
interaction.component.setStyle("DANGER");
// Respond to the interaction,
// and send updated components to the Discord API
interaction.update({
components: interaction.message.components
});
}
}
});
For any future viewers who might be using Discordjs V14+ you can't edit the components directly anymore, so you need to recreate them in order to edit them. This is a solution I came up with that flips the color when clicked!
const collector = interaction.channel.createMessageComponentCollector({ time: 15000 });
collector.on('collect', async i => {
//loop through each action row on the embed and update it accordingly
let newActionRowEmbeds = i.message.components.map(oldActionRow => {
//create a new action row to add the new data
updatedActionRow = new ActionRowBuilder();
// Loop through old action row components (which are buttons in this case)
updatedActionRow.addComponents(oldActionRow.components.map(buttonComponent => {
//create a new button from the old button, to change it if necessary
newButton = ButtonBuilder.from(buttonComponent)
//if this was the button that was clicked, this is the one to change!
if(i.component.customId == buttonComponent.customId){
//If the button was a primary button then change to secondary, or vise versa
if(buttonComponent.style == ButtonStyle.Primary){
newButton.setStyle(ButtonStyle.Secondary)
}
else if (buttonComponent.style == ButtonStyle.Secondary){
newButton.setStyle(ButtonStyle.Primary)
}
}
return newButton
}));
return updatedActionRow
});
// and then finally update the message
await i.update({components: newActionRowEmbeds})
});

How to use the updated useState value?

I use DataGrid Material UI.
I want to delete the selected row by clicking on the button.
To do this, I get the ID value in the onRowSelected attribute, write it to the useState hook.
However, when I click on any row, the row I selected earlier is deleted, not the one I selected now.
I know that useState is asynchronous, but what should I do in this case?
I need to send the updated value in the delete request to the server, not the previous one.
const [selectionModel, setSelectionModel] = useState([])
const handleDeleteUser = (e) => {
const config = {
headers: {
Authorization: 'Token ' + localStorage.getItem('token')
}
}
e.preventDefault()
axios
.delete(baseUrl + '/api/v1/users/' + `${selectionModel}/` , config)
.then((res) => {
swal({
title: "Are you sure?",
buttons: [true, "Do it!"],
})
updateData()
})
<DataGrid
rows={datas}
columns={columns}
pageSize={10}
onRowSelected={(row) => {
setSelectionModel(row.data.id)
}} />
picture
From what you've shown here I can't tell you exactly what the issue is, but I would suggest setting the selected id like you currently are here in the onRowSelected event, and then (if you're wanting to delete every time you click a row), use a useEffect hook with a dependency array to call your delete logic everytime that id changes. It would look something like this:
useEffect(() => {
deleteSelection(selectionModal)
}, [selectionModal]);
const deleteSelection = (id) => {
// your delete logic here
}
If you put together a working codepen I can help you more.

Showing modal on successfully posting data from redux

Hey guys i am using React Hooks with redux, so what i want is lets say user need a signup on my website, so after posting his details i am sending back status :200 and if it has status:200 i am updating a state value in my userReducer like say "status" to true (which initially was false) and based on this boolean value i am showing a modal to user with the message. So code goes like
Actions.js
export const userRegisterAction = (data: any) => async (dispatch: any) => {
console.log(data);
axios.post("http://localhost:4000/register_user", data).then((res) => {
console.log("hittinh server");
console.log(res.data);
if (res.data.status == 200) {
dispatch({
type: USER_REGISTRATION_SUCCESS,
payload: res.data,
});
} else {
dispatch(errorActions());
}
});
};
Reducer.js
case USER_REGISTRATION_SUCCESS:
return {
...state,
status: true,
info: action.payload.doc.ops[0],
};
and then in my component
const user = useSelector((state: RootState) => {
console.log(state.user.info);
console.log(state.error.iserror);
return { user: state.user };
});
<IonAlert
isOpen={user.user.status == true}
cssClass="my-custom-class"
header={"User Registered!"}
backdropDismiss={false}
// subHeader={'User Registered !'}
message={"You have been registered successfully."}
// buttons={["OK"]}
buttons={[
{
text: "OK",
handler: () => {
console.log("Confirm Okay");
redirect();
},
},
]}
/>
Note. I am using redux persist and this reducer needs to be persisted for future purpose as i am gonna use this wherever i need user related task like if he needs to change password i will pick up the email from my user reducer so it doesnt gets lost on refresh
NOW COMING TO THE ISSUE, as my reducer state is persisted once the "status" is true if user wishes to signup from another account as soon as he goes to signup page he will again see the result of "ionAlert" as it will match the condition status==true , so what i am doing is on every unmount i am dispatching action which sets "status" to false, although this works fine, but i wanna know is this the correct approach , how are you all dealing with this stuff ??

React: Reloading the page doesn't fetch the data anymore

When I am on my home page and click on a "lesson" component, the lesson page loads, takes the id of the lesson from the url and gets the data (from a local js file) to populate the lesson page. Well, that only happens when I click from the home page. But when I'm already on the lesson page with the data populated and I reload the page, I takes the id from the url but this time the data appear "undefined"... I don't understand why it doesn't take the data as previously?
Here is my component implementation:
const Lesson = () => {
const { id } = useParams();
const [lesson, setLesson] = useState(getLesson(id));
console.log("id: ", id);
console.log("lesson: ", lesson);
return (...);
};
Here is the console when I click on my lesson component from the home page:
the console when it works
There is the console when I simply reload the lesson page: the console when it doesn't work
I tried using useEffect() with a setLesson(getLesson(id)) inside but nothing changed...
I also tried this:
if (id) lesson = getLesson(id);
But again, it didn't work... :(
getLesson() gets its data from this file called fakeLessonsService.js:
import getHipHopLessons from "./hipHopLessons";
const lessons = [...getHipHopLessons()];
export function getLesson(lessonId) {
return lessons.find((lesson) => lesson._id === lessonId);
}
The file "./hipHopLessons" simply returns an array of lesson objects.
getLesson() is only loaded on this page.
The argument to useState is only used on initial render.
You should use a useEffect to update the state if the id changes
const [lesson, setLesson] = useState({});
useEffect(() => {
getLesson(id).then(val => setLesson(val));
}, [id]);
PS. Make sure getHipHopLessons is not asynchronous
If it is async, then you must write the code like
import getHipHopLessons from "./hipHopLessons";
export async function getLesson(lessonId) {
const lessons = getHipHopLessons();
return lessons.find((lesson) => lesson._id === lessonId);
}

Categories

Resources