Modal shows previous data - reactjs

I have a little "bug" here. I have a modal that receives a text, that corresponds a message to show, a state and a property to restore the values of the states when the modal close. In the main, I have three states, each one represents different situations and each show a unique message. The problem occurs when I run the app, choose a date in a datetimepicker, evaluate that in a function, and then I sent that result in a conditional, if "result" is in a determined range -> I set a specific state, else, set another state. Once a state is true, the modal opens with the specific text.
The problem take place when the modal open, the information correspond to a previous "result". I tried to debug with console logs, and I saw that the states doesn't update.
Is it possible that the states take a time to update? Or could be a problem with the cache of the device?
I attach the code below.
Here are the definition of the states
const [modalVisible1, setModalVisible1] = useState(false)
const [modalVisible2, setModalVisible2] = useState(false)
const [modalVisible3, setModalVisible3] = useState(false)
This is the conditional that I explained. picker_value is a variable that take an option from a menu by the user.
const setModal = () => {
(picker_value ===2 && (result>6 && result<150)) ? setModalVisible1(true) : setModalVisible2(true)
if(picker_value == 3)
setModalVisible3(true)
}
And finally, I have this expression (one for each state), to show the modal with the respective message
{modalVisible1 && <MyModal
message={"Test123"}
state={modalVisible1}
restore={resetModal}
/>
}
And I detach the modal
export const MyModal = (props) => {
return (
<Modal visible={props.state}
transparent={true}
animationType='slide'
>
<View style={styles.container}>
<Text>{props.message}</Text>
<TouchableOpacity onPress={() => props.restore()}>
</TouchableOpacity>
</View>
</Modal>
);
EDIT: onChange Method. Date is the current day, and days is a state that saves the result from process_days (calculates the diff between 2 dates)
const [days, setDays] = useState(0)
const onChange = (event, selectedDate) => {
setShow(Platform.OS === 'ios')
setSelectedDay(selectedDate)
setDays(process_days(date, selectedDate))
setPickerValue(formData.pickerValue)
setModal()
}
Reset states
const resetModal = () => {
setModalVisible1(false)
//same for each modal
}

Related

Encountering stale state when trying to pass state to sibling component

I'm building a simple to do list, and the code I'm trying to execute here is that when each task is clicked ('Task' component), it updates the parent state ('taskEdit') with the current task object. When taskEdit changes, a useEffect would run to re-render the whole component so that the parent can pass that object to another component ('EditTask' component) which is a modal that allows the user to edit the task, AND setEditModalVisible(true) would be run so that the Edit Task modal can be shown.
However, the issue is that when I do this, the 'EditTask' component renders with the previous state.
I.e. I click task A, and then Edit Task renders with an empty task object ({}), i.e. the initial state.
Then I click task B, and then Edit Task renders with the task A object.
Code is below. Any help is much appreciated!
Parent Component - taskEdit state is initialized here, and setTaskEdit is passed to the child Task component. When it's updated, I intend to pass the updated taskEdit state to the Edit Task component
When I press the task, in addition to updating the task object, it would update taskEditTrigger to true, which would allow the setEditModalVisible to continue - then I'd set it to false so that any further renders wouldn't trigger editModalVisible
const [editModalVisible, setEditModalVisible] = useState(false);
const [taskEditTrigger, setTaskEditTrigger] = useState(false);
const [taskEdit, setTaskEdit] = useState({});
useEffect(() => {
if (taskEditTrigger) {
setEditModalVisible(true)
setTaskEditTrigger(false)
} else {
setTaskEditTrigger(false)
}
}, [taskEdit]);
const renderItem = ({ item }) => (
<Task
item={item}
setTaskEdit={setTaskEdit}
setTaskEditTrigger={setTaskEditTrigger}
/>
);
return (
<View>
<FlatList
data={toDos}
renderItem={renderItem}
/>
<EditTask
item={taskEdit}
editModalVisible={editModalVisible}
setEditModalVisible={setEditModalVisible}
/>
</View>
Task Component - on pressing the task component, I intended to update the taskEdit state in the parent component with the current task
const Task = ({ item, setTaskEdit, setTaskEditTrigger })
const onPressTask = () => {
console.log(item.text)
console.log('Task is rendering')
if (item.isComplete) {
setIsTaskCompleteAlertVisible(true)
} else {
setTaskEdit(item)
setTaskEditTrigger(true)
}
};
return (
<View>
<TouchableOpacity onPress={onPressTask}>
<Text>{item.text}</Text>
</TouchableOpacity>
</View>
Export default memo(Task, (prevProps, nextProps) => {
If (prevProps.item.id === nextProps.item.id &&
prevProps.item.text === nextProps.item.text)
{
return true
}
return false
})
**Edit Task Component - I then intended to pass the item={taskEdit} into the Edit Task component to show the relevant task information **
function EditTask({
item, editModalVisible, setEditModalVisible}) {
console.log(item)
const [editingText, setEditingText] = useState(item.text);
const onPressEditTask = () => {
setEditModalVisible(false)
setTaskEdit({})
};
return (
<Modal visible={editModalVisible}>
<TouchableOpacity onPress={onPressEditTask}>
<Text>{editingText}</Text>
</TouchableOpacity>
</Modal>
Thanks to all the comments - found the reason for this issue.
I was able to lift the lift the taskEdit state from the Task component and pass it down from the parent component to EditTask successfully.
However, in EditTask I was updating the editingText state using the item.text property - and that was what was stale. When I updated the editingText state directly in Task and passed it by the same way to the Edit Task component, it was no longer stale.

Most idiomatic way to only run React hook if its dependency changed from the last value

I have a react app consisting of ParentComponent and HelpSearchWindow. On the page for ParentComponent, there is a button that lets you open up a window containing HelpSearchWindow. HelpSearchWindow has an input field and a search button. When an input is typed and search button is clicked, a search is run and results are displayed to the table on the window. The window can be closed. I have set up a react.useEffect() hook with the dependency [documentationIndexState.searchTerm] so that the search functionality is only run if the searchTerm changes.
However, the window was not behaving as I expected it to. Since useEffect() was being called every time the window was opened after it was closed, it would run the search again no matter if the searchTerm in the dependency array was the same. Because of this, I added another state prop (prevSearchTerm) from ParentComponent to store the last searched term. This way if the window is opened and closed multiple times without a new searchTerm being set, there is no repeat search run.
My question is, is there a more idiomatic/react-ish way to do this? Any other code formatting pointers are welcome as well
import {
setSearchTerm,
} from 'documentationIndex.store';
interface Props {
searchInput: string;
setSearchInput: (searchInput: string) => void;
prevSearchTerm: string;
setPrevSearchTerm: (searchInput: string) => void;
}
export const HelpSearchWindow: React.FC<Props> = props => {
const documentationIndexState = useSelector((store: StoreState) => store.documentationIndex);
const dispatch = useDispatch();
// Only run search if searchTerm changes
React.useEffect(() => {
async function asyncWrapper() {
if (!documentationIndexState.indexExists) {
// do some await stuff (need asyncWrapper because of this)
}
if (props.prevSearchTerm !== documentationIndexState.searchTerm) {
// searching for a term different than the previous searchTerm so run search
// store most recently used searchTerm as the prevSearchTerm
props.setPrevSearchTerm(props.searchInput);
}
}
asyncWrapper();
}, [documentationIndexState.searchTerm]);
return (
<input
value={props.searchInput}
onChange={e => props.setSearchInput(e.target.value)}
/>
<button
onClick={e => {
e.preventDefault();
dispatch(setSearchTerm(props.searchInput));
}}
>
Search
</button>
<SearchTable
rows={documentationIndexState.searchResults}
/>
);
};
//--------- Parent Component----------------------------------------
const ParentComponent = React.memo<{}>(({}) => {
const [searchInput, setSearchInput] = React.useState(''); // value inside input box
const [prevSearchTerm, setPrevSearchTerm] = React.useState(''); // tracks last searched thing
return(
<HelpSearchWindow
searchInput={searchInput}
setSearchInput={setSearchInput}
prevSearchTerm={prevSearchTerm}
setPrevSearchTerm={setPrevSearchTerm}
/>
);
});
From the given context, the use of useEffevt hook is redundant. You should simply use a click handler function and attach with the button.
The click handler will store the search term locally in the component and also check if the new input value is different. If it is itll update state and make the api call.

Disabled Button in React Native

I'm in a login problem trying to disable a interaction with a button.
The idea is to disable a submit button when 2 text field are empty, but I have the same result when is empty or not (always clickable, or always not). Below I specify the idea that I have, and how I implement it.
I have two states that stores the values from two text input, then I have a function "isEmpty" that consult if that states are empty (" "). Later, in a button "Submit" I have a property 'disabled' that which value is the result of isEmpty, i.e., if isEmpty is true, disabled is true and the user won't click the button unless the text input are filled. I tried some things, like change the return for another alternatives, or change the value of disable but I don't have good news.
I search from the web and this site, and the solutions doesn't resolve my problem.
Here I detach some code of the function, states and properties.
<View style={styles.submit}>
<TouchableOpacity
onPress={() => checkValidation()}
disabled={emptyFields}
>
<Text style={styles.button}> Submit </Text>
</TouchableOpacity>
</View>
checkEmptyFields -> function call by checkValidation to determinate if the text inputs are empties
const checkEmptyFields = () => {
if (
(id === "" && psw === "") ||
(id === "" && psw !== "") ||
(id !== "" && psw === "")
)
setEmptyFields(true);
};
const checkValidation = () => {
checkEmptyFields();
verifyUser();
resetStates();
};
States used
const [id, setId] = useState("");
const [psw, setPsw] = useState("");
const [emptyFields, setEmptyFields] = useState(false);
I found the problem. In the property disabled, i evaluate a state that ONLY calculates in a onPress function. The fix was to copy the line checkEmptyFields inside the disabled.

why child state value is not updating in parent callback function at first time?

why child state value is not updating in parent callback function at first time? i want to make my input-Field disable based on state.
Sandbox with full example: https://z3wu6.csb.app/
Issue
PageHeader
Initial state is true, when you click the the button the editViewHandler is called. It toggles the edit state of this component but then calls the editCallback callback with the current value of edit which is initially true. You're sending the non-updated value to the callback! (you set it to true again in UserProfile) You can fix this by also inverting the edit value sent to editCallback.
const [edit, setEditView] = useState(true);
const editViewHandler = () => {
setEditView(!edit);
editCallback(!edit); // <-- send the same value you update state to
};
I see you've also duplicated this edit state in UserProfile. You shouldn't duplicate state. You want a single source of truth.
You already pass editCallback from UserProfile so just attach that as the callback to the button.
Suggestion Solution
Toggle the value in the source callback in UserProfile
const UserProfile = () => {
const [edit, setEdit] = useState(true);
const editCallback = () => setEdit(edit => !edit);
return (
<>
<PageHeader
button
editCallback={editCallback}
title="User Profile"
subtitle="Overview"
/>
<UserAccountDetails edit={edit} />
</>
);
};
And attach to button's onClick handler
const PageHeader = ({ title, subtitle, button, editCallback }) => (
<div className="page-header py-4 withBtn d-flex align-items-center">
{button ? <Button onClick={editCallback}>Edit Detail</Button> : null}
</div>
);

increment/decrement click component. How to save data to db.json?

I got a react functional component that can increment or decrement a number.
When the page is loaded, i want this number to be read in the db.json file from my JSON-SERVER and displayed in my view. I also want to update the number in the db.json file when i increment or decrement the value in my page.
I tried to console.log the data coming from the db.json, and what i see is that console.log is displayed 2 times in my console :
first time an empty []
second time it is good [{"clicks":20,"id":1},{"clicks":50,"id":2}]
What i tried so far was to display the clicks value with { clicked[0].clicks }
but the '.clicks' leads me to an undefined error...
What am i doing wrong ? Thanks for your help!
const CountClick = (props) => {
const componentTitle = 'Count Click Component';
const [clicked, setClicked] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'http://localhost:4000/clicked'
);
setClicked(result.data);
};
fetchData();
},[]);
console.log('clicked array', JSON.stringify(clicked));
return (
<div>
<span className="result">Counter value: { clicked.clicks }</span>
<button onClick={ () => {setClicked(clicked.clicks + 1);saveClicks(clicked.clicks + 1)} }>+1</button>
<button onClick={ () => {setClicked(clicked.clicks - 1);saveClicks(clicked.clicks - 1)} }>+1</button>
</div>
);
I except to display the "clicks" value in my view
Assuming save and load functions work correctly is a simple check for the display, you can use sth like lodash isEmpty or check length of the array if more than 1 item display count.
IsEmpty(Clicked) ? Loading : clicked[0].clicks
UseEffect works in a similar pattern to component did mount. The data is loaded after the component renders to screen so at the time your clicked value is empty and no clicks can be displayed aka undefined

Resources