I have an edit page of a recipe that is populated correctly using findOne axios call and populates all of the information for that recipe. However, it only displays the checkboxes that are checked and not the checkbox objects that are unchecked. I want it to display all checkboxes, both checked and unchecked. See below code.
Edit.jsx
const Edit = (props) => {
const { _id } = useParams({})
const [form, setForm] = useState({
title: "",
description: "",
tags: [
{ name: "Athletic/Higher caloric", isChecked: false },
{ name: "Aggressive Weight Loss", isChecked: false },
{ name: "Kid-Friendly", isChecked: false },
{ name: "Non-Vegan", isChecked: false },
{ name: "Diabetes reversal", isChecked: false },
{ name: "Quick and Easy", isChecked: false },
],
})
useEffect(() => {
axios.get(`http://localhost:8000/api/recipes/${_id}`)
.then(res => {
console.log(res.data.results);
setForm(res.data.results);
})
.catch(err => {
console.log(err)
setErrors(err)
})
}, [_id])
My checkedHandler from create page:
const handleCheckedTags = (index) => {
setForm(prev => ({
...prev,
tags: [
...prev?.tags?.map(
({ isChecked, ...rest }, idx) => (
idx === index ?
{ ...rest, isChecked: !isChecked } :
{ ...rest, isChecked })
)]
}));
}
And here is my return code (the edit form):
return (
<form>
{
form.tags.map((tag, i) => (
<div className="form-inline mx-3" key={i}>
<input
type="checkbox"
value={tag.name}
checked={tag.isChecked}
onChange={(event) => handleCheckedTags(i)}
key={i}
/>
</div>
))}
</div>
<input type="submit" />
</form>
I know I have a single form state to begin with, which was how I setup my create form. Should I have set it up differently or is there a way to make sure the unchecked checkboxes display for the edit page?
Thanks in advance for your help.
**Update**
With some help I was able to merge the results from the server (with the recipe info in it) into the original form by using map operators. See below.
useEffect(() => {
document.title = "NutritarianEats - Edit"
axios.get(`http://localhost:8000/api/recipes/${_id}`)
.then(res => {
const updatedTags = [...form.tags];
const result = res.data.results;
result.tags.map((tag, i) => {
updatedTags.map((existingTag) => {
if (existingTag.name === tag.name) {
existingTag.isChecked = tag.isChecked
}
})
})
setForm({ ...result, tags: updatedTags });
})
.catch(err => {
setErrors(err)
})
}, [_id])
I think I could have not made the tags an array initially setting up my model and instead made it one object. Something I need to work on, but this code worked for me.
Related
I have this dynamic checkbox, that I want to update the state with the selected options only ,I tried to add some checks to filter the state on change , but it seems I am not seeing what went wrong!
const checkBoxesOptions = [
{ id: 1, title: 'serviceOne' },
{ id: 2, title: 'serviceTwo' },
{ id: 3, title: 'serviceThree' },
];
const [selectedCheckBoxes, setSelectedCheckBoxes] = React.useState([]);
{checkBoxesOptions.map((checkBox, i) => (
<CheckBox
key={i}
label={checkBox.title}
value={1}
checked={false}
onChange={value => {
let p = {
title: checkBox.title,
isTrue: value,
};
if (p.isTrue) {
const tempstate = selectedCheckBoxes.filter(
checkbox => checkbox !== checkBox.title
);
console.log('temp state', tempstate);
setSelectedCheckBoxes([...selectedCheckBoxes, p.title]);
}
console.log(p);
}}
/>
))}
The value parameter is the event object.
(event) => {
const value = event.target.checked
<... Rest of the code ...>
}
How to add a class name in every row without effect the rest of the rows
import React, { useState } from 'react';
import './testEfect.css';
const Test = () => {
const arrayTest = [
{
name: '11',
id: '11'
},
{
name: '22',
id: '22'
},
{
name: '33',
id: '33'
},
]
const [state, setState] = useState(false);
const handleClick = (event) => {
const newState = event;
setState(state ? false : true);
}
return (
<div className="App">
{arrayTest.map((x, index) => {
return (
<ul key={index} className={state ? 'deletEfect' : ''}>
<li id={x.id} >
{x.name}
<button onClick={(event) => handleClick(x.id)}>Delete</button>
</li>
</ul>
)
})}
</div>
)
}
The problem here is that when you say the state is false; it is assuming the state is false for the whole component. It doesn't update the row but the whole component. So, at first, you need to add a deleted property that will take a different value for each row.
So,
const arrayTest = [
{
name: "11",
id: "11",
deleted: false
},
{
name: "22",
id: "22",
deleted: false
},
{
name: "33",
id: "33",
deleted: false
}
];
const [state, setState] = useState(arrayTest); //initial state
Now, when you render, you don't need to use that arrayTest. But you need to use the state. We won't touch arrayTest ever again. So we use,
{state.map((x, index) => {
return (
<ul key={index} className={x.deleted ? "testEfect" : ""}>
<li id={x.id}>
{x.name}
<button onClick={(event) => handleClick(x.id)}>Delete</button>
</li>
</ul>
);
})}
Notice we use state.map. We also send x.id to handleClick function.
Why? Because we will use that id to change the deleted value of the object. So our handleClick becomes,
const handleClick = (id) => {
const newState = state.map((element) => {
if (element.id === id)
return Object.assign({}, element, {
deleted: element.deleted ? false : true
});
return element;
});
setState(newState);
};
This is just updating the state in an immutable way.
Here is the full codesandbox for your convenience.
I have something like this on React:
const CheckboxItems = (t) => [ // that t is just a global prop
{
checked: true,
value: 'itemsCancelled',
id: 'checkBoxItemsCancelled',
labelText: t('cancellations.checkBoxItemsCancelled'),
},
{
checked: true,
value: 'requestDate',
id: 'checkboxRequestDate',
labelText: t('cancellations.checkboxRequestDate'),
},
{
checked: true,
value: 'status',
id: 'checkboxStatus',
labelText: t('cancellations.checkboxStatus'),
},
{
checked: true,
value: 'requestedBy',
id: 'checkboxRequestedBy',
labelText: t('cancellations.checkboxRequestedBy'),
},
];
class TableToolbarComp extends React.Component {
state = {
items: CheckboxItems(),
};
onChange = (value, id, event) => {
const { columnsFilterHandler } = this.props;
this.setState(({ items }) => {
const item = items.slice().find(i => i.id === id);
if (item) {
item.checked = !item.checked;
columnsFilterHandler(id, item.value, item.checked);
return { items };
}
});
};
render() {
const { items } = this.state;
return(
<>
{items.map(item => (
<ToolbarOption key={item.id}>
<Checkbox
id={item.id}
labelText={item.labelText}
value={item.value}
checked={item.checked}
onChange={this.onChange}
/>
</ToolbarOption>
))}
</>
)
}
export default compose(
connect(
({ cancellations }) => ({
columnId: cancellations.columnId,
columnValue: cancellations.columnValue,
isChecked: cancellations.isChecked,
}),
dispatch => ({
columnsFilterHandler: (columnId, columnValue, isChecked) => {
dispatch(columnsFilterAction(columnId, columnValue, isChecked));
},
}),
),
)(translate()(TableToolbarComp));
That works very well and it is dispatching the data I would need to use later.
But I have a mess on the Redux part which is changing the state of all of the checkboxes at once and not separately as it should. So, once I uncheck one of the checkboxes the other 3 also get checked: false. I don't see this change to checked: false on the UI, only I see it on the Redux console in the browser.
This is what I have in the reducer
const initialState = {
checkboxes: [
{
checked: true,
value: 'itemsCancelled',
id: 'checkBoxItemsCancelled',
},
{
checked: true,
value: 'requestDate',
id: 'checkboxRequestDate',
},
{
checked: true,
value: 'status',
id: 'checkboxStatus',
},
{
checked: true,
value: 'requestedBy',
id: 'checkboxRequestedBy',
},
],
}
[ActionTypes.COLUMNS_FILTER](state, action) {
return initialState.checkboxes.map(checkbox => {
if (!checkbox.id === action.payload.id) {
return checkbox;
}
return {
...checkbox,
checked: action.payload.isChecked,
};
});
}
Action:
const columnsFilterAction = (columnId, columnValue, isChecked) => ({
type: ActionTypes.COLUMNS_FILTER,
payload: { columnId, columnValue, isChecked },
});
So all I need to know is what I have to do manage the state of those checkboxes on Redux as it working on React. As all I see is that when I toggle the checkboxes all of them reach the same state.
You have !checkbox.id === action.payload.id as your condition logic. As all of your checkbox IDs are 'truthy', then this !checkbox.id evaluates to false, and is the same as writing if(false === action.payload.id).
I suspect you meant to write: if(checkbox.id !== action.payload.id).
What you want to do is pass the id of the checkbox you want to toggle in an action. That's all you need in an action to toggle state. Then in the reducer you want to map over the current state and just return the checkbox for any that don't match the id passed in the action. When the id does match, return a new option spreading the current checkbox's properties into the new object and setting the checked property to it's opposite.
Given this action:
const TOGGLE_CHECKBOX = 'TOGGLE_CHECKBOX'
function toggleCheckbox(id) {
return {
type: TOGGLE_CHECKBOX,
id
}
}
Actions - Redux - Guide to actions and action creators provided by the author of Redux.
This reducer will do the job.
function checkboxReducer(state = [], action = {}) {
switch(action.type) {
case TOGGLE_CHECKBOX:
return state.map(checkbox => {
if (checkbox.id !== action.id) {
return checkbox;
}
return {
...checkbox,
checked: checkbox.isChecked ? false : true,
}
})
default:
return state;
}
}
Reducers - Redux - Guide to reducers and how to handle actions provided by the author of Redux.
Here is a working Code Sandbox to demonstrate it working. You can click the checkboxes to see them toggling as expected.
I have a small problem with my dynamic form. In the code below the render method I have code that maps an input and a dropdown select menu to fill in state.questions[{title: "", type: ""}]. You can see the addQuestionsHandler method to add more questions and the questionsInputHandler to handle the questions values.
The surveyInputHandler method handles the static questions in the return function.
The problem I'm having is that in my code for the dynamic questions the input value and the select dropdown value are ending ending up the same in state.questions[{title: "", type: ""}]. If I input "Test" - both title and type will be "Test". If I input "Test" and select value = "Radio Button" - both title and type will be "Radio Button". If I don't select a dropdown option value, then both will be the input value. If I do select a dropdown option value then the input value will be overridden by the dropdown select value.
I've racked my brain for a while but I need more eyes on it. Can you please let me know what I'm not doing correctly? Thanks so much.
const questionTypes = [
"Select Question Type",
"Textbox",
"Radio Button",
"Checkbox"
];
class SurveyQuestions extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
description: "",
pointsValue: 0,
questions: [
{
title: "",
type: ""
}
]
};
}
surveyInputHandler = e => {
console.log(e.target.value);
this.setState({
[e.target.name]: e.target.value,
[e.target.title]: e.target.value,
[e.target.description]: e.target.value,
[e.target.pointsValue]: e.target.value
});
};
questionsInputHandler = idx => e => {
console.log(e.target.value);
const newQuestions = this.state.questions.map((question, qidx) => {
if (idx !== qidx) return question;
return {
...question,
title: e.target.value,
type: e.target.value
};
});
this.setState({
questions: newQuestions
});
};
addQuestionHandler = () => {
this.setState(prevState => ({
questions: [...prevState.questions, { title: "", type: "" }]
}));
};
submitHandler = e => {
const { title, description, pointsValue, questions } = this.state;
console.log(
title,
description,
pointsValue,
questions.map(question => ({ ...question }))
);
this.setState({
title: "",
description: "",
pointsValue: "",
questions: [{ title: "", type: "" }]
});
e.preventDefault();
};
render() {
const { title, description, pointsValue, questions } = this.state;
const questionsDisplay = questions.map((question, idx) => (
<div key={idx} className="SurveyQuestions__QuestionContainer">
<h5>Question {idx + 1}</h5>
<label htmlFor="questionTitle">Question Title</label>
<input
type="text"
id="questionTitle"
placeholder={`Question Title #${idx + 1}`}
value={question.title}
onChange={this.questionsInputHandler(idx)}
/>
<label htmlFor="questionType">Type</label>
<select
className="SurveyQuestions__QuestionTypesDropdown"
value={question.type}
onChange={this.questionsInputHandler(idx)}
>
{questionTypes.map((type, tidx) => (
<option key={tidx} id={`${type}-${tidx}`} value={type}>
{type}
</option>
))}
</select>
</div>
));
Solved:
So the simple solution was to create a separate input handler for the select dropdown menu. The code is below:
questionTypeInputHandler = idx => e => {
const newQuestions = this.state.questions.map((question, qidx) => {
if (idx !== qidx) return question;
return {
...question,
type: e.target.value
};
});
this.setState({
questions: newQuestions
});
};
Print selected radio button value in console.
If all radiogroup is answered then print in console only the selected= true radioValue. for example: if NO radiobutton= true its value is 2. It should print value 2. Like that all true radiovalue should print in console.
Thanks
//array of cards coming from the backend
const data = [
{
cardName: 'Do you want sugar in your coffee',
options: [
{ radioName: 'Yes',radioValue: '1', selected: false },
{ radioName: 'No',radioValue: '2', selected: false }]
},
{
cardName: 'Do you want milk in your coffee',
options: [
{ radioName: 'Yes',radioValue: '1', selected: false },
{ radioName: 'No',radioValue: '2', selected: false }]
},
{
cardName: 'Do you want low-fat-milk in your coffee',
options: [
{ radioName: 'Yes',radioValue: '1', selected: false },
{ radioName: 'No',radioValue: '2', selected: false }]
}
];
class CardsList extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: [],
};
}
componentDidMount() {
setTimeout(() => {
// mimic an async server call
this.setState({ cards: data });
}, 1000);
}
onInputChange = ({ target }) => {
const { cards } = this.state;
const nexState = cards.map(card => {
if (card.cardName !== target.name) return card;
return {
...card,
options: card.options.map(opt => {
const checked = opt.radioName === target.value;
return {
...opt,
selected: checked
}
})
}
});
this.setState({ cards: nexState })
}
onSubmit = () => {
console.log(this.state.cards.map(({ cardName, options }) => {
const option = options.filter(({ selected }) => selected)[0]
return ` ${option.radioValue}`
}))
};
onReset = () => {
this.setState({cards:[]});
}
render() {
const { cards } = this.state;
return (
<div>
{
cards.length < 1 ? "Loading..." :
<div>
{cards.map((card, idx) => (
<ul>
{card.cardName}
{card.options.radioName}
{
card.options.map((lo, idx) => {
return <input
key={idx}
type="radio"
name={card.cardName}
value={lo.radioName}
checked={!!lo.selected}
onChange={this.onInputChange}
/>
})
}
</ul>
))
}
< button onClick={this.onSubmit}>Submit</button>
< button onClick={this.onReset}>Clear</button>
</div>
}
</div>
);
}
}
ReactDOM.render(<CardsList />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Change your log in onSubmit to this
console.log(this.state.cards.map(({ cardName, options }) => {
const option = options.filter(({ selected }) => selected)[0]
return `${cardName}: ${option.radioName}`
}))
This way you filter the options to the one, where selected is truthy, and take the first one.
To address your first question, just map over your this.state.cards array before doing the log and check, if there is exactly 1 option, where selected is true. If this is not the case, tell the user in whatever way you want.
Also you can remove your constructor and change it to that:
state = {
cards: [],
}
Because you do not access your props in your constructor
You can go with the answer of #george,
for you to check if either of the radio buttons is clicked for each card, you can run a validation check
let unselectedCards = this.state.cards.filter((card) => {
return !card.options[0].selected && !card.options[1].selected
});
use the unselectedCards variable to highlight the cards.
you can use map options again inside the cards map if you would be having more options.