State does not update using UseEffect - javascript

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} />
))}

Related

Handling data rendering on redux state change

I'm trying to setup a form. It has Edit feature where on edit I call an API and get the data into state.
I'm struggling to display data in the form after api call. There's no problem utilizing the API or calling the redux functions. Problem is that my Form only displays last data in the redux state but not the updated data.
That's how I'm doing the stuff.
Calling API if isEdit===True at the same time Form is being displayed on component mount.
Updateding state after success as an object called customer
accessing the customer object like this
const { customer } = useSelector((state) => state.customers)
Lets say I have a input field where I want to display the email of customer.
I'm handling this think like that:
email: isEdit ? customer?.email : '', // At this point there is some problem
It loads the previous data that was stored in the state.customer but not the new one.
I believe my email field is rendering first and then doesn't updated the value when change happens in state.customer.
So how I can fix this? So that email value should be changed at the same time if state.customer got changed
Here is the full component. Still removed irrelevant part.
const CustomerNewEditForm = ({ isEdit, id, currentUser}) => {
const dispatch = useDispatch()
const navigate = useNavigate()
console.log('isEdit', isEdit, 'id', id, 'currentUser', currentUser)
// get sales reps
const { customer } = useSelector((state) => state.customers)
// const customer = () => {
// return isEdit ? useSelector((state) => state.customers?.customer) : null
// }
const { enqueueSnackbar } = useSnackbar()
const defaultValues = useMemo(
() => ({
email: isEdit ? customer?.email : '',
name: isEdit ? customer?.name : '',
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[currentUser]
)
const methods = useForm({
resolver: yupResolver(NewUserSchema),
defaultValues
})
const {
reset,
watch,
control,
setValue,
handleSubmit,
formState: { isSubmitting }
} = methods
const values = watch()
useEffect(() => {
if (isEdit === true) {
dispatch(getCustomerDetails(id))
console.log(customer)
}
if (isEdit && currentUser) {
reset(defaultValues)
}
if (!isEdit) {
reset(defaultValues)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isEdit, currentUser])
const onSubmit = async () => {
try {
await new Promise((resolve) => setTimeout(resolve, 500))
reset()
let body = {
email: values.email,
name: values.name,
}
console.log(body)
dispatch(createCustomer(body))
enqueueSnackbar(!isEdit ? 'Create success!' : 'Update success!')
// navigate(PATH_DASHBOARD.admin.root)
} catch (error) {
console.error(error)
}
}
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Grid item md={3}>
{' '}
<RHFTextField name="name" label="Customer Name" />
</Grid>
<Grid item md={3}>
{' '}
<RHFTextField name="email" label="Email Address" />
</Grid>
</FormProvider>
)
}
export default CustomerNewEditForm
Here in the component defaultValues carries the previous data from customer object if its True and renders the form with those values. but new data comes a miliseconds later but form renders first.
First of all try to console.log your customer data and make sure that it gets a fresh data on last render.
If it gets fresh data, try take a look at your Input component, it might set some initial data, so the input will be editable and controlled by some state.
Try to modify your input's state on redux store update in useEffect.
Currently that's all that I can suggest, update your post with code with your form and input, also post your console.log result, if my answer doesn't helped you.
If the problem would be not in form\input state and console.log wouldn't show you actual updated data in last render, then I will need to see your redux store code to resolve this issue.
Hope it helped

React state updating without setstate, takes on state of deleted item (SOLVED)

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!

React conditional re-rerender of an array based on filter

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])

React hooks: Set state 'ModalVisible' but state does not update immediately

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)

React Native load data from API using hooks

Im new in ReactNative and I'm trying to take some data from here https://www.dystans.org/route.json?stops=Hamburg|Berlin
When I try console.log results it return full API response. I dont know why in first results.distance works and return distance, but when I'm trying to do it inside FlatList nothing is returned. Sometimes it works when i want to return only item.distance but can't somethnig like <Text>{item.stops[0].nearByCities[0].city}</Text> nowhere in my code also in console. Im getting error:
undefined is not an object (evaluating 'results.stops[0]')
imports...
const NewOrContinueScreen = ({ navigation }) => {
const [searchApi, results, errorMessage] = useDystans();
console.log(results.distance);
return (
<SafeAreaView forceInset={{ top: "always" }}>
<Text h3 style={styles.text}>
Distance: {results.distance}
</Text>
<Spacer />
<FlatList
extraData={true}
data={results}
renderItem={({ item }) => (
<Text>{item.distance}</Text>
// <Text>{item.stops[0].nearByCities[0].city}</Text>
)}
keyExtractor={item => item.distance}
/>
<Spacer />
</SafeAreaView>
);
};
const styles = StyleSheet.create({});
export default NewOrContinueScreen;
And here is my hook code:
import { useEffect, useState } from "react";
import dystans from "../api/dystans";
export default () => {
const [results, setResults] = useState([]);
const [errorMessage, setErrorMessage] = useState("");
const searchApi = async () => {
try {
const response = await dystans.get("route.json?stops=Hamburg|Berlin", {});
setResults(response.data);
} catch (err) {
setErrorMessage("Something went wrong with useDystans");
}
};
useEffect(() => {
searchApi();
}, []);
return [searchApi, results, errorMessage];
};
As the name implies, FlatList is designed to render a list. Your API endpoint returns a JSON Object, not an Array, so there's nothing for the FlatList to iterate. If you want to show all the stops in the list, try passing in the stops list directly.
<FlatList
data={results.stops}
renderItem={({ item }) => (<Text>{item.nearByCities[0].city}</Text>)}
/>
Some side notes: (1) The extraData parameter is used to indicate if the list should re-render when a variable other than data changes. I don't think you need it here at all, but even if you did, passing in true wouldn't have any effect, you need to pass it the name(s) of the variable(s). (2) The keyExtractor parameter is used to key the rendered items from a field inside of them. The stop objects from the API don't have a member called distance so what you had there won't work. From my quick look at the API response, I didn't see any unique IDs for the stops, so you're probably better off letting React key them from the index automatically.

Categories