How can I implement a react callback - reactjs

Im quite new to react and I am working on a registration page checkbox element
And below is the Choice component implementation,
const ChoiceItem = ( { options, itemId, selectedIndex, onChange } ) => (
handleChange: function(checked){
const value = [];
options.map( option, i ) => (
value[ i ] = checked;
)
},
<Card>
{
options
.map( ( option, i ) => (
<Checkbox
id={ `Choiceitem-checkbox-${ i }-${ itemId }` }
key={ i }
label={ option.text }
style={ styles.checkbox }
value={ '' + i }
onChange={ ( checked ) => onChange( itemId, [ i ], checked ) }
/>
) )
}
</Card>
);
What I want to do over here is to loop through the options and get there values in to an array named value and call back the handleAnswerChange in registration form and set the value over there. Can anyone tell me how I can archive that?
Thank you.

Sounds like the best solution for you is two way binding. You can check out the react docs on adding two way binding.
react two way binding.
Another option would be using jquery

You are actually very close to having this one. I would update your handleChange in MultipleChoiceItem:
handleChange = (index) => (checked) => {
const newValues = options.map((option, i) => {
// if it is the changed returned the new state
// otherwise just return the current value
return index === i ? checked : option;
});
this.props.onChange(this.props.itemId, newValues);
};
Then inside of your render I would set the onChange:
onChange={this.handleChange(i)}
This should then update state on your MyRegisterForm on each checkbox click.

Related

React Hooks "useState/useEffect/useCallback" are called conditionally

Please tell me where do I need to put the list.length condition to remove the React Hooks are called conditionally error? I tried to wrap it in useEffect, but in this case an empty list is returned at the first render. It is important that the list is returned at the first render in the same way as with the logic in the code below.
const List = ({ list }) => {
if (list.length === 0) {
return <div>LOADING...</div>;
}
const [localList, setLocalList] = useState(list);
useEffect(() => {
setList(localList);
}, [localList]);
const handleChange = useCallback((id) => {
setLocalList((prevLocalList) =>
prevLocalList.map((item, index) => {
return index !== id ? item : { ...item, checked: !item.checked };
})
);
}, []);
return (
<>
{localList?.map((item, index) => (
<MemoRow key={index} {...item} handleChange={handleChange} />
))}
</>
);
};
The rendered result is returned at the end of the component, not at the beginning. Make that first operation part of the overall return at the end:
return (
list.length === 0 ?
<div>LOADING...</div> :
<>
{localList?.map((item, index) => (
<MemoRow key={index} {...item} handleChange={handleChange} />
))}
</>
);
Additionally, there is a logical issue in your component. When a parent component passes the list value, you are duplicating that in local state in this component. If the parent component changes the value of list, this component will re-render but will not update its local state.
Given the term "LOADING..." in the UI, this implies that's exactly what's happening here. So on a re-render, list.length === 0 is now false, but localList is still empty.
As a "quick fix" you can just update localList any time list changes:
useEffect(() => {
setLocalList(list);
}, [list, setLocalList]);
Of course, this will also over-write any local changes to localList if the parent component ever changes list again. But since this is duplicated state then it's not really clear what should happen in that case anyway. Perhaps you could only conditionally update it if localList is empty:
useEffect(() => {
if (localList.length === 0) {
setLocalList(list);
}
}, [list, setLocalList, localList]);
It's really up to you how you want to handle edge cases like that. But ultimately you're going to need to update localList after list has changed if you want those changes to be reflected in your local state.

React not rendering list after the state is changed

I am creating a simple tracker which records all the activities done. It is my first project in react. I have created three state one for storing all the items(name of state is list), one for pending items(name of state is pending) , one for completed items(name of state is completed). The items have a button which when clicked marks it into done state and vice-versa. It is completely rendering items for main list. But for other two its not rendering. When I am checking with react developer tools, it is working fine, i.e. it is adding to pending list or completed list as it should. But it is not compiling them on screen. list is already filled with items. I have added all the code for just in case.
function Filters(props){
const [completed, setCompleted] = useState([]);
const [pending, setPending] = useState([]);
const [state, setState] = useState("None");
const [list,setList] = useState([]);
function viewState(){
setState("View-all");
}
//it is getting the clicked item id and marking it complete in main list
function markComplete(id){
list.map((items,index)=>{
if(index===id){
if(items.done===true)
items.done = false;
else{
items.done=true;
}
}
})
}
//i am simply scanning the main list and the items which are pending will be added to this list. //this happens whenever the person click on pending button
function pendingState(){
setState("pending-all");
setPending([]);
list.map(items=>{
if(items.done!==true){
setPending(prev=>{
return [...prev,items];
})
}
})
}
function completedState(){
setState("completed-all");
setCompleted([]);
list.map(items=>{
if(items.done===true){
setCompleted(prev=>{
return [...prev,items];
})
}
})
}
return (
<div>
<div className="input-section">
<Note setList={setList} />
</div>
<button type="button" onClick={viewState} >View All</button>
<button type="button" onClick={completedState}>Completed</button>
<button type="button" onClick={pendingState}>Pending</button>
<div>
{
(()=>{
if(state==="View-all")
{
return (
<div>
<h1>Working {completed}</h1>
{(list).map((items,index)=>
{
return (
<Excercise
key={index}
id={index}
title={items.name}
list={props.list}
setList={props.setList}
content={items.desp}
markComplete={markComplete}
/>
)
})}
</div>
)
}
else if(state==="completed-all")
{
return (
<div>
{completed.map((items,index)=>{
<Excercise
key={index}
id={index}
title={items.name}
list={props.list}
setList={props.setList}
content={items.desp}
markComplete={markComplete}
/>
})}
</div>
)
}
})()
}
</div>
</div>);
}
Kindly help. Thank you.
Hi #DREW
The function code :
function markComplete(id){
setList(lists=>{
lists.map(item=>{
return item.id===id ?{...item,done: !item.done} : (item);})
}
)
}
When I am using it instead of
const markComplete = (id) => {
setList((list) =>
list.map((item) =>
item.id === id
? {
...item,
done: !item.done
}
: item
)
);
};
it is showing, "Cannot read properties of undefined (reading 'filter')"
arent the both same. If not, what am I doing wrong. Sorry for bugging so many times, I have just started with react.
I think you've overcomplicated things a bit. You only need one array to store the exercises in, the "pending" and "completed" states are easily derived from the list state and the state filter state value.
Issues
markComplete callback is mutating the list state. When updating the list state not only is a new array reference necessary, but also new element object references are necessary for the elements that are being updated.
Uses poor boolean comparisons to set a boolean value. You can either toggle a boolean or set the value to the result of a boolean expression.
Use the viewState, pendingState, and completedState handlers to simply set the filter value, and then derive the computed state when rendering by adding an inline filter function.
Use the exercise id property as a React key and as the property used for toggling the completed (done) state.
Solution
function Filters(props) {
const [state, setState] = useState("None");
const [list, setList] = useState([
...
]);
function viewState() {
setState("View-all");
}
function pendingState() {
setState("pending-all");
}
function completedState() {
setState("completed-all");
}
const markComplete = (id) => {
setList((list) =>
list.map((item) =>
item.id === id
? {
...item,
done: !item.done
}
: item
)
);
};
return (
<div>
<div className="input-section">
<Note setList={setList} />
</div>
<button type="button" onClick={viewState}>
View All
</button>
<button type="button" onClick={completedState}>
Completed
</button>
<button type="button" onClick={pendingState}>
Pending
</button>
<div>
{list
.filter((item) => {
if (state === "pending-all") {
return !item.done;
} else if (state === "completed-all") {
return item.done;
}
return true;
})
.map((item) => (
<Excercise
key={item.id}
id={item.id}
done={item.done}
title={item.name}
content={item.desp}
markComplete={markComplete}
/>
))}
</div>
</div>
);
}
try to add dependecies in useEffect
in this function you are mutating a state, so in order to do so you need to use the setState function, in this case, it will be setList().
function markComplete(id){
list.map((items,index)=>{
if(index===id){
if(items.done===true)
items.done = false;
else{
items.done=true;
}
}
})
}
So a better way to implement this function could be, and remember, everything time you need to update a state you don't want to change the state directly, instead, you should make a copy and set the state to that copy
function markComplete(id){
const newList = [...list];
newList.map((items,index)=>{
if(index===id){
if(items.done===true)
items.done = false;
else{
items.done=true;
}
}
}
setList(newList)
}
The reason of your app not updating is because when your state changes react is not re-rendering it again.
so use useEffect, there are many hooks which can be used as per requirement.
try putting this line of code
useEffect( ( ) => {
console.log( 'Check console' );
}, [ dependency_here ] );
in dependency_here try adding state, completed, pending one by one and see the result.
You can also add multiple dependencies like [ state, pending, etc.. ];
Try on your own you'll understand it faster.
Hope hint will help you!

I am trying to give an active className to mapped data when I click. Currently the previous active class doesn't change when i click on another text

Parent component
Here is the mapped function
function SubHeader() {
const categories = category?.data?.data;
return (
{categories?.map((data) => (
<Smaller data={data} />
))} );
}
Child component
Here is where I am using the state to control the color of the text when it is clicked. Not sure I can figure what isn't right.
function Smaller({ data }) {
const [active, setActive] = useState(false);
const colorPicker = (dataId) => {
setActive(dataId ? !active : active);
};
return (
<Text
color={active ? 'brand.blue' : 'brand.dark'}
onClick={() => colorPicker(data?.id)}
>
{data?.name}
</Text>
);
}
The issue is when you click on another text, it doesn't change the active state of other / previous texts. So you're just toggling the active class of each component but it doesn't "untoggle" anywhere.
I found a way to solve this but I used a simple example because I didn't have your data.
Solution:
Each child component should have an ID.
Check if the child component's ID matches the activeElementID.
Parent Component
function SubHeader() {
const [activeElementID, setActiveElementID] = useState()
const categories = category?.data?.data;
return (
{categories?.map((data) => {
<Smaller data = {data} id = {data?.id} activeElementID={activeElementID} setActiveElementID={setActiveElementID} />
})}
)
}
Child Component
function Smaller({data, id, activeElementID, setActiveElementID}) {
function clicked() {
setActiveElementID(id)
}
return (
<p onClick={clicked} className={activeElementID === id ? "blue" : "red"}>{data}</p>
)
}
Furthermore, I would also recommend checking the data instead of using the "?" operation. For example, category?.data and data?.data
Because you are saying that you are sure the data exists. You can do this instead.
const categories = category.data.data
if (categories) {
return (....)
}
Hope this helps!

How to conditionally disable check boxes

I have a drop down with check boxes inside it (used Bootstrap). When two are checked, I want to make it so the users cannot check anymore until the others are unchecked. I attempted this using the disable attribute but keep getting the error below.
What I tried:
export default function Test() {
const [countries, setCountries] = useState([]);
const [disableRadio, setDisableRadio] = useState(false);
function createCountryList() {
const countries = ["a", "b", "c", "d"];
return countries.map((country, key) => (
<Form.Check
disabled={disableRadio ? (e) => e.target.checked : false}
key={key}
label={country}
onChange={(e) =>
e.target.checked ? handleChecked(e) : handleUncheck(e)
}
/>
));
}
function handleChecked(e) {
const Arr = countries;
Arr.push(e.target.value);
setCountries(Arr);
if (countries.length > 1) {
setDisableRadio(true);
} else {
setDisableRadio(false);
}
}
function handleUncheck(e) {
const Arr = countries;
const index = Arr.indexOf(e.target.value);
Arr.splice(index, 1);
setCountries(Arr);
}
return (
<>
<Dropdown>
<Dropdown.Menu as={CustomMenu}>
{createCountryList()}
</Dropdown.Menu>
</Dropdown>
</>
);
}
Getting this error: react-dom.development.js:67 Warning: Invalid value for prop 'disabled' on <input> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.
What I want:
Thanks!
There are several issues with your code, and I have a feeling that you're overcomplicating the handling of checkbox events.
You have countries as a state, and also as a fixed array of available countries. I'd suggest you store them separately: one is pure data (fixed array of available countries), and the other is the component state.
You can use Set to handle adding/removing of countries, without too much of a fuss. There is no need to have separate event handlers for checked/unchecked states. We can handle adding/removing selected countries to the array using this logic below:
const [countries, setCountries] = useState(new Set());
function onChange(e) {
if (e.target.checked) {
setCountries(c => new Set([...c, e.target.value]));
} else {
setCountries(c => new Set([...c].filter(x => x !== e.target.value)))
}
}
There is no need to store disableRadio in a separate state. Instead, take advantage of useMemo, or simply use the logic of countries.size > 1 in the template itself. Combine that with countries.has(country) to check whether a given checkbox's value is in the set of values. In this sense, a checkbox is disabled when (1) more than 1 countries have been selected AND (2) the count is NOT in the list of selected countries
disabled={countries.size > 1 && !countries.has(country)}
Your checkbox options are missing the value attribute: remember to add that.
With all these changes proposed, your code can be simplified into something like this:
export default function Test() {
// NOTE: Store available countries as a constant
const availableCountries = ["a", "b", "c", "d"];
// NOTE: countries here refer to SELECTED countries
// We use new Set() so we automatically dedupe values
const [countries, setCountries] = useState(new Set());
function createCountryList() {
return availableCountries.map((country, key) => (
<Form.Check
disabled={countries.size > 1 && !countries.has(country)}
key={key}
value={country}
label={country}
onChange={onChange}
/>
));
}
function onChange(e) {
if (e.target.checked) {
setCountries(c => new Set([...c, e.target.value]));
} else {
setCountries(c => new Set([...c].filter(x => x !== e.target.value)))
}
}
return (
<>
<Dropdown>
<Dropdown.Menu as={CustomMenu}>
{createCountryList()}
</Dropdown.Menu>
</Dropdown>
</>
);
}
See example of CodeSandbox:

Clear multiple select fields values in react-select from external action

I am using https://github.com/JedWatson/react-select to display set of single/multiple select lists in the UI. And now I need to clear all the fields values at once.
In the docs there are options to add a clear button into or next to the element - Clearable property. But I want to call it from outside the elements on the container component level, using redux states for example.
Component structure is as follows:
renderSelectFilters() {
const { filters } = this.props;
return (
filters.map((filter) => {
if (filter.type == 'Checkbox' || filter.type == 'Select') {
return (
<SelectContainer
key={filter.name}
name={filter.name}
options={filter.options}
multi={filter.type == 'Checkbox' ? true : false}
handleChange={this.handleChange}
clearValues={this.clearValues}
searchable={filter.type == 'Checkbox' ? true : false}
/>
);
}
})
)
}
clearFilters() {
//stuff here
}
renderClearFiltersButton = () => {
return (
<Button
text={'Clear Filters'}
onClick={this.clearFilters}
/>
)
}
render() {
return (
<div className="filters-bar">
<div className="some-class">
{this.renderSelectFilters()}
{this.renderClearFiltersButton()}
</div>
</div>
)
}
I've checked this solution React-select clear value while keeping filter but it's about setting the existing value not completely removing the value.
I would sync react-select value with redux so that when you clear the value in redux it would be automatically cleared on react-select.

Resources