How can I add multiple functions to the React js button? - javascript

I want to add startClick to BackButton as in NextButton. In other words, when the BackButton is clicked, the startClick function should work first, then the dispatch (giveForm2PreviousStep (props.currentStep, props.goToStep)) method should work in order. How can I do that?
Question JS
const Question = props => {
const dispatch = useDispatch()
const loading = useSelector(state => state.app.isLoading)
const error = useSelector(state => state.app.error)
const reduxF2 = useSelector(state => state.app.forms.f2)
const [input, setInput] = useState({
value: reduxF2.PastReceivables.value,
valid: true,
})
const changeSelected = val => {
setInput({ ...input, value: val })
}
useEffect(() => {
setInput({ ...input, value: reduxF2.PastReceivables.value })
}, [reduxF2.PastReceivables.value])
useEffect(() => {
if (reduxF2.bulkSaved && props.currentStep === 2) {
dispatch(giveForm2NextStep(props.currentStep, props.goToStep))
dispatch(resetForm2SavedStatus())
}
}, [reduxF2.bulkSaved])
const startClick = e => {
if (input.value === null || input.value === '') {
setInput({ ...input, valid: false })
} else {
setInput({ ...input, valid: true })
const questions = getPastReceivablesArray('PastReceivables', input.value, reduxF2)
if (questions.length == 0) {
dispatch(giveForm2NextStep(props.currentStep, props.goToStep))
} else {
dispatch(updateForm2(questions))
}
}
}
return (
<>
<MyProgressBar now='8' />
<Question>Question here</Question>
<QuestionForm>
<NumericInput
valid={input.valid}
onChange={changeSelected}
value={input.value}
/>
</QuestionForm>
<div className='d-flex justify-content-between'>
<BackButton onClick={() => dispatch(giveForm2PreviousStep(props.currentStep, props.goToStep))} />
<NextButton onClick={startClick} loading={loading} />
</div>
<Warning error={error} />
</>
)
}
BackButton JS
const BackButton = ({ text = 'Back', onClick = null, loading = false, width = '7.5rem' }) => {
return (
<Button
variant='secondary'
className='back-button'
onClick={onClick}
disabled={loading}
style={{width}}
>
<MySpinner loading={loading} />
{!loading && <>{text}</>}
</Button>
)
}

You can call multiple functions in onClick event like below
<BackButton
onClick={(e) => {
startClick(e);
dispatch(giveForm2PreviousStep(props.currentStep, props.goToStep))
}}
/>

You can call multiple function in onclick or else you can send call backs to startclick.
so the call backs will be executed after startclick.
easy to give all the fucntions in onClick itself.

Related

Uncaught TypeError: cannot read properties of undifined (reading 'length')

I'm getting this error
and here's my code:
const Search = () => {
const [searchBox, setSearchBox] = useState(false)
const [searchIcon, setSearchIcon] = useState(true)
const {searchText, setSearchText} = useState('')
const [searchChanged, setSearchChanged] = useState(false)
const searchBoxRef = useRef()
const history = useHistory()
useEffect(() => {
document.addEventListener('mousedown', outsideSearchClickListener, false)
return () => {
document.removeEventListener('mousedown', outsideSearchClickListener, false)
}
}, [])
useEffect(() => {
if (searchText.length > 0) {
history.push({
pathname: '/search',
search: `?q=${searchText}`
})
} else if (searchChanged && searchText.length === 0) {
history.replace({ pathname: '/browse' })
}
}, [history, searchText, searchChanged])
const searchClickHandler = () => {
setSearchBox(true)
}
const outsideSearchClickListener = event => {
if (searchBoxRef.current && !searchBoxRef.current.contains(event.target)) {
setSearchBox(false)
}
}
const searchTextChangeHandler = event => {
const textValue = event.target.value
setSearchText(textValue)
setSearchChanged(true)
}
const clickCrossHandler = () => {
setSearchText('')
}
const searchBar = (
<CSSTransition in={searchBox} classNames="search-animation" timeout={500} unmountOnExit
onEnter={() => setSearchIcon(false)}
onExiting={() => setSearchBox(false)}
onExited={() => setSearchIcon(true)}>
<div className="Holder">
<FontAwesomeIcon className="Icon" size="lg" icon={faSearch} />
<input autoFocus placeholder="Titles, people, genres"
onChange={searchTextChangeHandler} value={searchText} />
{searchText.length > 0 ?
<FontAwesomeIcon onClick={clickCrossHandler} size="lg" icon={faTimes} /> : null
}
</div>
</CSSTransition>
)
return (
<div className="SearchBox" onClick={searchClickHandler} ref={searchBoxRef}>
{searchIcon && <FontAwesomeIcon size="lg" icon={faSearch} />}
{searchBar}
</div>
)
}
I rechecked my source code and compare mine with the original code for this but still no clue on this error. The console output is kind of ambiguous. FYI: the original one is work, but it's not work for me
Maybe change your handler like this so you ensure the value is always a string
const searchTextChangeHandler = event => {
const textValue = event.target.value || ""
setSearchText(textValue)
setSearchChanged(true)
}

Search component render problem with validate js

I have search component with validate js.
Problem: when my input in foucs first time, validate and request dont work, but when i lose focus my input, and click it again, and try again, search working without validation
interface IProps {
onSearchChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const Search: React.FC<IProps> = ({ onSearchChange }) => {
const inputRef = useRef<HTMLInputElement>(null);
const [inputIsTouched, setInputIsTouched] = useState(false);
const currentValue = inputRef.current?.value && inputRef.current.value;
const validateErrors = validate({ currentValue }, constraints);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (validateErrors?.currentValue) {
return;
}
currentValue && onSearchChange && onSearchChange(event);
setInputIsTouched(true);
};
const debouncedOnChange = debounce(handleChange, 1000);
return (
<div className={classes['Root']}>
<Input
type="text"
autoComplete="off"
placeholder="..."
onChange={debouncedOnChange}
ref={inputRef}
onBlur={() => setInputIsTouched(true)}
isError={inputIsTouched && !!validateErrors?.currentValue}
/>
<div className={classes['ErrorContainer']}>
{inputIsTouched && validateErrors?.currentValue && (
<Text color="error" size="s">
{validateErrors.currentValue}
</Text>
)}
</div>
</div>
);
};
That's expected because on first render, currentValue is undefined (as inputRef.current is null) and there's nothing calling handleChange to trigger the search.
You need to make sure the handleChange logic also runs on the initial render, so it should look something like this:
const Search: React.FC<IProps> = ({ onSearchChange }) => {
// Use a single object for all input state props:
const [{
isTouched,
validateErrors,
}, setInputState] = useState({
isTouched: false,
validateErrors: null,
});
const inputRef = useRef<HTMLInputElement>(null);
// Debounce only search callback:
const debouncedSearchChange = debounce(onSearchChange, 1000);
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
// Get the current value:
const currentValue = e.currentTarget.value;
// Validate it:
const validateErrors = validate({ currentValue }, constraints);
if (validateErrors?.currentValue) {
// And handle error:
setInputState(prevState => ({ ...prevState, validateErrors }));
return;
}
// Or success:
setInputState(prevState => ({ ...prevState, validateErrors: null }));
// And trigger the debounced search if needed:
if (currentValue && debouncedSearchChange ) debouncedSearchChange(event);
}, [constraints, debouncedSearchChange]);
// Trigger validation and search on first render:
useEffect(() => {
const inputElement = inputRef.current;
// TypeScript will complain about this line, so you might want to
// re-structure the logic above to accommodate this:
if (inputElement) handleChange({ currentTarget: inputElement });
}, []);
return (
<div className={classes['Root']}>
<Input
type="text"
autoComplete="off"
placeholder="..."
onChange={handleChange}
ref={inputRef}
onBlur={() => setInputState(prevState => ({ ...prevState, isTouched: true }))}
isError={inputIsTouched && !!validateErrors?.currentValue}
/>
<div className={classes['ErrorContainer']}>
{inputIsTouched && validateErrors?.currentValue && (
<Text color="error" size="s">
{validateErrors.currentValue}
</Text>
)}
</div>
</div>
);
};

React useState being ignored

I have a small React app where you can click on a piece of text to replace it with an input field, to allow you to edit it.
The problem is that the EditableText component does not seem to want to display the editingText state in the input field.
See example here: https://codesandbox.io/s/distracted-ellis-kyrdb
For posterity, the code for the main app is:
export default function App() {
const [cats, setCats] = useState([
{
id: 1,
name: "fred"
},
{
id: 2,
name: "jim"
}
]);
const [activeCat, setActiveCat] = useState({});
const handleClick = (cat, e) => {
setActiveCat(cat);
};
const saveName = (newName) => {
// save this cat's new name...
};
return (
<div className="App">
<div className="catMenu">
{cats.map((row) => (
<div
className="catItem"
key={row.id}
onClick={(e) => handleClick(row, e)}
>
{row.name}
</div>
))}
</div>
<div className="activeCat">
<h2>
<EditableText text={activeCat.name} saveText={saveName} />
</h2>
</div>
</div>
);
}
and for the EditableText component:
const EditableText = (props) => {
const { text, saveText } = props;
const [editing, setEditing] = useState(false);
const [editingText, setEditingText] = useState(text);
useEffect(() => {
const handleKeydown = (e) => {
if (editing) {
if (e.keyCode === 13) {
// Enter key pressed
saveText(editingText);
setEditing(false);
}
if (e.keyCode === 27) {
// Escape key pressed
setEditingText(text);
setEditing(false);
}
}
};
window.addEventListener("keydown", handleKeydown);
return () => {
window.removeEventListener("keydown", handleKeydown);
};
}, [text, saveText, editing, editingText]);
return (
<div>
<div
onClick={() => setEditing(true)}
className={`et-${editing ? "hidden" : "active"}`}
>
{text}
</div>
<input
type="text"
value={editingText}
onChange={(e) => setEditingText(e.target.value)}
className={`et-input-${editing ? "active" : "hidden"}`}
/>
</div>
);
};
export default EditableText;
Add another useEffect to listen to text prop change, you only use it as initial empty value:
useEffect(() => {
setEditingText(text);
}, [text]);

To do list making with React hooks

I'm a bit new to React. I'm trying to make simple To do list with react hooks and struggling to make "delete all button". I thought it could be work to using setState [] or return []
but it didn't work...
and also it's showing an error.
TypeError: tasks.map is not a function
Does anyone know how it figure out?
Here is my code
import React, {useState} from 'react'
let INITIAL_TASK = {
title: 'React',
doing: false,
}
const App = () => {
const [tasks, setTasks] = useState([INITIAL_TASK])
const [task_title, setTask_title] = useState('')
const handleTextFieldChanges = e => {
setTask_title(e.target.value)
}
const resetTextField = () => {
setTask_title('')
}
const isTaskInclude = () => {
return tasks.some(task => task.title === task_title)
}
const addTask = () => {
setTasks([...tasks, {
title: task_title,
doing: false,
}])
resetTextField()
}
const deleteTask = (task) => {
setTasks(tasks.filter(x => x !== task))
}
const deleteAllTask = () => {
-------------
}
const handleCheckboxChanges = task => {
setTasks(tasks.filter(x => {
if (x === task) x.doing = !x.doing
return x
}))
}
return (
<React.Fragment>
<Container component='main' maxWidth='xs'>
<CssBaseline/>
<Box
mt={5}
display='flex'
justifyContent='space-around'
>
<TextField
label='title'
value={task_title}
onChange={handleTextFieldChanges}
/>
<Button
disabled={task_title === '' || isTaskInclude()}
variant='contained'
color='primary'
onClick={addTask}
href=''
>
add
</Button>
<Button
// disabled={task_title === '' || isTaskInclude()}
variant='contained'
color='secondary'
onClick={deleteAllTask}
href=''
>
all delete
</Button>
</Box>
<List
style={{marginTop: '48px'}}
component='ul'>
{tasks.map(task => (
<ListItem key={task.title} component='li'>
<Checkbox
checked={task.doing}
value='primary'
onChange={() => handleCheckboxChanges(task)}
/>
<ListItemText>{task.title}</ListItemText>
<Button
href=''
onClick={() => deleteTask(task)}
>
delete
</Button>
</ListItem>
))}
</List>
</Container>
</React.Fragment>
)
}
export default App
You can try doing below
const deleteAllTask = () => {
setTasks([]);
};
or if you want it to set to initial value, you can do below
const deleteAllTask = () => {
setTasks([INITIAL_TASK]);
};

How to add a whole array to useState variable

I'm trying to add a whole array to useState variable
import React, { Fragment, useState, useEffect } from 'react';
import { Form, Button, Popover, OverlayTrigger } from 'react-bootstrap';
const Filter = props => {
const [formData, setFormData] = useState({
filter: ''
});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci","paris", "london"];
console.log(retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const { filter } = formData;
const onChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
props.filterData(filter);
//Close the Popover
document.body.click();
};
const popover = (
<Popover id="popover-basic">
<Form>
<Form.Group controlId="formGroupEmail">
<Form.Label>Add New Filter</Form.Label>
<Form.Control
type="text"
placeholder="New Filter"
name="filter"
onChange={e => onChange(e)}
/>
</Form.Group>
<Button variant="dark" type="submit" onClick={e => onSubmit(e)}>
Add
</Button>
</Form>
</Popover>
);
return (
<Fragment>
<label>
<p className="filter-title">{props.title}</p>
</label>
<div className={props.className ? props.className : 'filter'}>
{!props.retrievedFilter
? items.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})
: props.retrievedFilter.map((item, index) => {
return (
<div className="filter-text" key={index}>
{item}
<Button
className="filter-button"
size="sm"
onClick={() => deleteFilter(index)}
>
X
</Button>
</div>
);
})}
<OverlayTrigger
trigger="click"
placement="right"
rootClose
overlay={popover}
>
<p className="text-field">Type new one</p>
</OverlayTrigger>
</div>
</Fragment>
);
};
export default Filter;
however retrievedItems shows as an empty array in the console.
any help would be appreciated.
setState is async. You have to console.log inside an effect hook with the array as a parameter.
useEffect(() => console.log(retrieved_items), [ retrievedItems ])
The second parameter ensures that the effect fires in repose to a change in the values passed to it.
Per my comment, here is a code snippet that I think does what you want.
I couldn't get it running in SO but here's a codepen: https://codepen.io/anon/pen/PrYYmz?editors=1010 (watch the chrome console as you add items)
import React, {
Fragment,
useState,
useEffect
} from 'react';
const Filter = props => {
const [formData, setFormData] = useState({filter: ''});
const [items, setItems] = useState([]);
const [retrievedItems, setRetrievedItems] = useState([]);
const addToFilter = newFilter => {
let retrievedFilter = ["da vinci", "paris", "london"];
console.log('add', retrievedFilter);
if (retrievedFilter.length > 0) {
setRetrievedItems([...retrievedItems, retrievedFilter]);
retrievedFilter = 0;
setRetrievedItems([...retrievedItems, newFilter]);
} else {
setItems([...items, newFilter]);
}
console.log('items are: ', items);
console.log('retrieve filter', props.retrievedFilter);
console.log('retrieved items: ', retrievedItems);
};
useEffect(() => {
console.log('useEffect ', retrievedItems);
}, [retrievedItems]);
const deleteFilter = index => {
// props.retrievedFilter.splice(index, 1);
items.splice(index, 1);
setItems([...items]);
// setItems([...props.retrievedFilter, ...items]);
console.log(items);
};
const {filter} = formData;
const onChange = e => {
setFormData({ ...formData,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
addToFilter(filter);
// Passing filter data up (i.e: to components that use <Filter />)
//props.filterData(filter);
//Close the Popover
document.body.click();
};
return (
<Fragment >
<label >
<p className = "filter-title" > {
props.title
} </p> </label> <
div className = {
props.className ? props.className : 'filter'
} > {!props.retrievedFilter ?
items.map((item, index) => {
return ( <
div className = "filter-text"
key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)}>X</button></div>
);
}) :
props.retrievedFilter.map((item, index) => {
return ( <div className = "filter-text" key = {index} > {item} <button className = "filter-button" size = "sm" onClick = {() => deleteFilter(index)} >X</button></div>);})} <input type = "text" placeholder = "New Filter" name = "filter" onChange = {e => onChange(e) }/>
<button variant = "dark" type = "submit" onClick = {e => onSubmit(e)} >Add</button>
</div>
</Fragment>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>

Categories