I use setModalConfirmVisible(true) but the state modalConfirmVisible does not update immediately. So, Modal does not display.
How can I update this state immediately?
When I click Remove button. The console will show only false from
console.log(modalConfirmVisible)
useEffect(() => {
(async () => {
try {
setFetchLoading(true)
setTransactions(await fetchTransactions())
setFetchLoading(false)
} catch (err) {
console.error(err)
}
})()
}, [modalConfirmVisible])
async function handleRemoveTransaction(id) {
setRemoveLoading(true)
setModalConfirmVisible(true)
console.log(modalConfirmVisible)
await handleConfirmRemoveTransaction(true, id)
}
return (
{modalConfirmVisible && (
<ModalConfirm
onConfirmRemove={handleConfirmRemoveTransaction}
onCancel={() => setModalConfirmVisible(false)}
visible={true}
/>
)}
)
// ModalConfirm.js
const ModalConfirm = ({ onConfirmRemove, visible }) => {
return (
<Modal
visible={visible}
title="Do you want to delete these items?"
icon={<ExclamationCircleOutlined />}
content="When clicked the OK button, this dialog will be closed after 1 second"
onOk={() => onConfirmRemove(true)}
onCancel={() => onConfirmRemove(false)}
/>
)
}
You should try to split out the logic of opening your modal from handling the confirmation. This allows the state update to set modalConfirmVisible and then on the next render cycle the confirmModal can open.
// from component in screen cap click remove, just open the modal
function openRemoveConfirmation() {
setRemoveLoading(true)
setModalConfirmVisible(true)
}
// handle confirmation separately
function handleRemoveTransaction(id) {
handleConfirmRemoveTransaction(true, id)
}
return (
{modalConfirmVisible && (
<ModalConfirm
onConfirmRemove={handleConfirmRemoveTransaction}
onCancel={() => setModalConfirmVisible(false)}
visible={true}
/>
)}
)
Because you are using react-hooks, so you change any state immediately.
The right way is to check the value of modalConfirmVisible in the next cycle. And because you setState and the state is changed, the function will execute again:
const [modalConfirmVisible, setModalConfirmVisible] = useState(false);
async function handleRemoveTransaction(id) {
setRemoveLoading(true)
setModalConfirmVisible(true)
await handleConfirmRemoveTransaction(true, id)
}
console.log(modalConfirmVisible)
Related
I have a notes state array that I have that stores the user's inputs. When a user initially opens this specific screen, the component should fetch the user's notes, and return that array or an empty one depending on if they have data or not, and display the notes on the screen if they exist. When they add a note, the component should push this new note to the notes array and call AsyncStorage.setItem to store the new array. The component should then re-render with the state variable changing.
When I run this code, though, nothing happens. My state does not seem to update, and even though I submit text, the screen does not re-render, nor does any new text appear in the section it is supposed to appear in. Anyone know where I went wrong?
UPDATE: I have added the full code block here
const [notes, setNotes] = React.useState(null);
let getNotes = async () => {
try {
let json = await AsyncStorage.getItem(`${id}-notes`);
if (json != null) {
setNotes(JSON.parse(json));
} else {
setNotes([]);
}
} catch (e) {
console.log(e);
}
}
React.useEffect(() => {
console.log(notes, '- Has changed');
getNotes();
}, [notes]);
// user input check
<TextInput
style={styles.text}
value={note}
onChangeText={text => {setNote(text)}}
onSubmitEditing={event => {
if (event.nativeEvent.text) {
setNotes([...notes, event.nativeEvent.text]);
AsyncStorage.setItem(`${id}-notes`, JSON.stringify(notes), (e) => {});
setVisible(false);
}
}}
multiline={true}
returnKeyType='go'
/>
// what i want the screen to render
{notes && notes.map(note => {
<Note note={note} />
})}
I don't think the problem is that the state is not being set,
I think the problem is that you aren't returning anything from you map function.
Either add the return keyword.
Like this:
{notes && notes.map(note => {
return <Note note={note} />
})}
Or simply remove the curly-brackets to turn the statement into an "implicit return".
Like this:
{notes && notes.map(note => (
<Note note={note} />
))}
I have a React notes app that has a delete button, and a state for user confirmation of deletion.
Once user confirms, the 'isConfirmed' state is updated to true and deletes the item from MongoAtlas and removes from notes array in App.jsx.
The problem is, the note that takes the index (through notes.map() in app.jsx I'm assuming) of the deleted notes position in the array has the 'isConfirmed' state set to true without calling setState. Thus, bugging out my delete button to not work for that specific note until page refresh.
I've included relevant code from my Delete Component:
function DeletePopup(props) {
const mountedRef = useRef(); //used to stop useEffect call on first render
const [isConfirmed, setIsConfirmed] = useState(false);
const [show, setShow] = useState(false);
function confirmDelete() {
// console.log("user clicked confirm");
setIsConfirmed(true);
// console.log(isConfirmed);
handleClose();
}
useEffect(() => {
// console.log("delete useEffect() run");
if (mountedRef.current) {
props.deleteNote(isConfirmed);
}
mountedRef.current = true;
}, [isConfirmed]);
Note Component:
function Note(props) {
function deleteNote(isConfirmed) {
props.deleteNote(props.id, { title: props.title, content: props.content }, isConfirmed);
console.log("note.deleteNote ran with confirmation boolean: " + isConfirmed);
}
return <Draggable
disabled={dragDisabled}
onStop={finishDrag}
defaultPosition={{ x: props.xPos, y: props.yPos }}
>
<div className='note'>
<h1>{props.title}</h1>
<p>{props.content}</p>
<button onClick={handleClick}>
{dragDisabled ? <LockIcon /> : <LockOpenIcon />}
</button>
<EditPopup title={props.title} content={props.content} editNote={editNote} />
<DeletePopup deleteNote={deleteNote} />
</div>
</Draggable>
}
App Component:
function App() {
const [notes, setNotes] = useState([]);
function deleteNote(id, deleteNote, isConfirmed) {
if (!isConfirmed) return;
axios.post("/api/note/delete", deleteNote)
.then((res) => setNotes(() => {
return notes.filter((note, index) => {
return id !== index;
});
}))
.catch((err) => console.log(err));
}
return (
<div id="bootstrap-override">
<Header />
<CreateArea
AddNote={AddNote}
/>
{notes.map((note, index) => {
return <Note
key={index}
id={index}
title={note.title}
content={note.content}
xPos={note.xPos}
yPos={note.yPos}
deleteNote={deleteNote}
editNote={editNote}
/>
})}
<Footer />
</div>);
}
I've tried inserting log statements everywhere and can't figure out why this is happening.
I appreciate any help, Thanks!
EDIT: I changed my Notes component to use ID based on MongoAtlas Object ID and that fixed the issue. Thanks for the help!
This is because you are using the index as key.
Because of that when you delete an element you call the Array.filter then you the elements can change the index of the array which when React tries to rerender the notes and as the index changes it cannot identify the note you've deleted.
Try using a unique id (e.g. an id from the database or UUID) as a key instead.
I hope it solves your problem!
I’m running into an error that I could use some help on
Basically, I have a react app that is executing an HTTP call, receiving an array of data, and saving that into a state variable called ‘tasks’. Each object in that array has a key called ‘completed’. I also have a checkbox on the page called ‘Show All’ that toggles another state variable called showAll. The idea is by default all tasks should be shown however if a user toggles this checkbox, only the incomplete tasks (completed==false) should be shown. I can get all tasks to display but can’t get the conditional render to work based on the checkbox click
Here’s how I’m implementing this. I have the HTTP call executed on the page load using a useEffect hook and available to be called as a function from other change handlers (edits etc.)
Before I call the main return function in a functional component, I’m executing a conditional to check the status of ’ShowAll’ and filter the array if it's false. This is resulting in too many re-render errors. Any suggestions on how to fix it?
See simplified Code Below
const MainPage = () => {
const [tasks, setTasks] = useState([]); //tasks
const [showAll, setShowAll] = useState(true); //this is state for the checkbox (show all or just incomplete)
useEffect( ()=> {
axios.get('api/tasks/')
.then( response => { //this is the chained API call
setTasks(response.data.tasks);
})
.catch(err => {
console.log('error');
})
}, []);
const fetchItems = (cat_id) => {
axios.get('/api/tasks/')
.then( response => {
setTasks(response.data.tasks);
})
.catch(err => {
console.log('error');
})
};
//change the checkbox state
const handleCheckboxChange = (e) => {
setShowAll(!showAll)
console.log('Checkbox: ', showAll)
};
//this part updates the tasks to be filtered down to just the incomplete ones based on the checkbox value
if (showAll === false) {
setTasks(tasks.filter(v => v['completed']===false)); //only show incomplete tasks
}
return (
<div>
<label className="checkb">
<input
name="show_all"
id="show_all"
type="checkbox"
checked={showAll}
onChange={handleCheckboxChange}
/> Show all
</label>
<br/>
{ tasks && tasks.map((task, index) => {
return (
<div key={index} className="task-wrapper flex-wrapper">
<div >
{ task.completed === false ? (
<span> {index +1}. {task.task_description} </span> ) :
(<strike> {index +1}. {task.task_description} </strike>) }
</div>
<div>
<button
onClick={()=> modalClick(task)}
className="btn btn-sm btn-outline-warning">Edit</button>
<span> </span>
</div>
</div>
)
})}
</div>
);
};
export default MainPage;
Thanks
Two things to fix:
Use the checked property on event.target to update the state:
const handleCheckboxChange = ({target: { checked }}) => {
setShowAll(checked)
};
Filter as you want but don't update the state right before returning the JSX as that would trigger a rerender and start an infinite loop:
let filteredTasks = tasks;
if (!showAll) {
filteredTasks = tasks?.filter(v => !v.completed));
}
and in the JSX:
{ tasks && tasks.map should be {filteredTasks?.map(...
use e.target.value and useEffect :
//change the checkbox state
const handleCheckboxChange = (e) => {
setShowAll(e.target.checked)
console.log('Checkbox: ', showAll)
if (!e.target.checked) {
let list =tasks.filter(v => v.completed===false);
setTasks(list ); //only show incomplete tasks
}
};
or
//change the checkbox state
const handleCheckboxChange = (e) => {
setShowAll(e.target.checked)
console.log('Checkbox: ', showAll)
};
useEffect(()=>{
if (showAll === false) {
let list =tasks.filter(v => v.completed===false);
setTasks(list ); //only show incomplete tasks
}
},[showAll])
I am trying to implement a search feature, using custom hooks, and retrieving data from an external API. The problem is that when the user enters an invalid search term, it immediately returns the JSX element inside the if condition. How can I delay showing the JSX element using setTimeout, so first show the user a spinner or something, then redirect them to the previous page. This is what I have:
if (!movies[0])
return (
<>
<Spinner />
<h1>NO RESULTS FOUND</h1>
</>
);
This is what i'd like to do theoretically:
if (!movies[0]) => show Spinner (for 200ms) => setTimeout(show error and redirect after 200ms, 200)
how could I implement this in react?
You can maybe try something like this :
const [loading, setLoading] = useState(true);
useEffect(() => {
let timer = setTimeout(() => {
setLoading(false);
}, 2000);
return () => { clearTimeout(timer) };
}, [];
return (
<>
{loading ? <div>Loading</div> : <div>...<div>}
</>
);
I am trying to move the open state for material-ui dialog to redux to prevent it from closing when a rerender occurs, but i having trouble with the dialog when a rerender occurs. Although the state is saved in redux and the dialog does stay open whenever a rerender occurs the open state stays open but the dialog does show the open animation (fading in) which is kinda annoying.
Students.js (parent component of the modal)
const Students = ({
app: { studentsPage: { savedAddDialogOpen }},
setStudentsPageAddDialogOpen}) => {
// Create the local states
const [dialogOpen, setDialogOpen] = React.useState(savedAddDialogOpen);
const dialogOpenRef = React.useRef(savedAddDialogOpen);
// Change redux dialog open
const setReduxDialogState = () => {
setStudentsPageAddDialogOpen(dialogOpenRef.current, savedAddDialogOpen);
};
// Open add student dialog
const dialogClickOpen = () => {
setDialogOpen(true);
dialogOpenRef.current = true;
setTimeout(() => setReduxDialogState(), 300);
};
// Close add student dialog
const dialogClose = () => {
setDialogOpen(false);
dialogOpenRef.current = false;
setTimeout(() => setReduxDialogState(), 300);
};
return (
<Container>
{/* Add student modal */}
<AddStudentModal dialogOpen={dialogOpen} dialogClose={dialogClose} />
</Container>
)
}
// Set the state for this component to the global state
const mapStateToProps = (state) => ({
app: state.app,
});
AddStudentModal.js
const AddStudentModal = ({
dialogOpen, dialogClose
}) => {
return (
<Dialog
open={dialogOpen}
>
{/* Lots of stuff*/}
<DialogActions>
<Button onClick={dialogClose}>
Close dialog
</Button>
</DialogActions>
</Dialog>
)
};
I hope this should be sufficient. I tried checking if the open state is actually correct when a rerender occurs and it is correct every time but it looks like the dialog is closed at a rerender no matter what the open state is and only a few ms later actually notices that it should be opened.
Any help would be really appreciated
Edit 1: Found out it has nothing to do with the open state coming from redux, if i use open={true} it still flashes, so probably a problem with material-ui itself?
Edit 2: PrivateRoute.js
const PrivateRoute = ({
auth: { isAuthenticated, loadingAuth },
user: { loggedInUser },
component: Component,
roles,
path,
setLastPrivatePath,
...rest
}) => {
useEffect(() => {
if (path !== '/dashboard' && path !== '/profile') {
setLastPrivatePath(path);
}
// Prevent any useless errors with net line:
// eslint-disable-next-line
}, [path]);
// If we are loading the user show the preloader
if (loadingAuth) {
return <Preloader />;
}
// Return the component (depending on authentication)
return (
<Route
{...rest}
render={props =>
!isAuthenticated ? (
<Redirect to="/login" />
) : (loggedInUser && roles.some(r => loggedInUser.roles.includes(r))) ||
roles.includes('any') ? (
<Component {...props} />
) : (
<NotAuthorized />
)
}
/>
);
};
// Set the state for this component to the global state
const mapStateToProps = state => ({
auth: state.auth,
user: state.user
});
I found the problem thanks to #RyanCogswell!
For anyone having the same problem here is the cause for me and the fix:
I was passing components into the Route component like this:
<PrivateRoute
exact
path={'/dashboard/students'}
component={(props) => (
<Students {...props} selectedIndex={selectedIndexSecondary} />
)}
roles={['admin']}
/>
By doing it this way i could pass props through my privateRoute function but this would also happen if you send the component this way in a normal Route component
Solution for me is just moving selectedIndexSecondary to redux and sending the component the normal way it prevented the re-mounting.
So just doing it like this will prevent this from happening.
<PrivateRoute
exact
path={'/dashboard/students'}
component={Students}
roles={['admin']}
/>
Also this will solve the localstates in your components from resseting to the default value. So for me it fixed two problems!