I am building a quiz in a React app, and I am working on a specific question that allows the user to select multiple options via checkboxes. So far, for other questions that are only radio button options, I have been able to set the selected answer to the state just fine. But I am getting stuck on how I would do this for a question that is checkboxes. I need the state to be updated each time a checkbox is checked or unchecked. The code for this checkbox question is:
import React, { useState, useEffect } from "react";
import { connect } from "frontity";
import { GridWrap, GridRow, GridColumn } from "emotion-flex-grid";
import QuizCheckbox from "/src/components/quiz-checkbox";
import QuestionCommonContainer from "../question-common.style";
const Question3 = ({ actions }) => {
const [checkedItems, setCheckedItems] = useState({});
const checkboxes = [
{
key: "checkBox1",
label: "Back",
},
{
key: "checkBox2",
label: "Side",
},
{
key: "checkBox3",
label: "Stomach",
},
];
const handleChange = (event) => {
setCheckedItems({
...checkedItems,
[event.target.name]: event.target.checked,
});
};
return (
<QuestionCommonContainer>
<GridWrap>
<GridColumn px={["l"]}>
<GridRow wrap="wrap" justify="center">
<GridColumn width={[12, 12, 12, 4]}>
{checkboxes.map((item, id) => (
<GridColumn pb={"l"} key={id}>
<QuizCheckbox
name={item.label}
label={<h5>{item.label}</h5>}
selected={checkedItems[item.label]}
onChange={handleChange}
/>
</GridColumn>
))}
</GridColumn>
</GridRow>
</GridColumn>
</GridWrap>
</QuestionCommonContainer>
);
};
export default connect(Question3);
I am thinking that I need to either alter the handleChange function, but I am not completely sure if that's right or how I would need to change it. Any help would be greatly appreciated.
UPDATE:
The first two comments helped. I think I am very tired lol. I see that the items are being added to state, but with a little glitch:
So I have 3 checkboxes. I added console.log(checkedItems) to the handleChange function so I can watch the behavior in the console.
I go to make my first selection and the console returns Object { } ... an empty object, even though I have selected one item. I try to select a second item and the console returns
{
"Side": true
}
which was the second selection. I am not sure why the first selection is not being included in the returned object.
Try this fix:
const handleChange = (event) => {
const name = event.target.name;
setCheckedItems(prevState => ({
...prevState,
[name]: !prevState[name],
}));
};
I’m not entirely sure what’s wrong with your code, it looks alright to me. But if anything is broken, chances are the lib you use "#baalspots/tmm-theme/src/components/quiz-checkbox" is not behaving properly.
The above fix try to mitigate misbehavior of lib. If it doesn’t help, then you need to post more details. What exactly is broken? Any error message? Plus you need to post link to an online playground of you code somewhere, like codesandbox, so people can experiment.
Ah I guess I've debugged enough now to answer my own question. The handleChange function was just fine, and I realized that I was not getting correct console logs because I had the console.log() in the wrong place.
Related
Hello friendly people of stackoverflow, I'm having a map function issue and I just can't put my finger on what the problem could be. Context is just for user data and its not needed to replicate the issue, context/state can be removed or changed so I'm not so sure it's even a state issue. Strict mode changes do nothing. Id/key changes aren't helping. I've tried every iteration from good to bad of map key/id practices (using the best answers on the website for map functions duplicating) and it's always the same issue, it even happens when no errors are thrown like with the code example below (using index) and when errors/warnings for duplicate id's throw (by giving bad keys/id) it still happens - it always adds two more posts to the feed on refresh. Maybe I'm misunderstanding or missing something about the map function or even Next/react (this is using NextJS) so I thought I'd reach out. I just need to figure out why its duplicating but I can't for the life of me. It works as it should until I refresh. If I refresh again, it just displays the same post(s) with the same two duplicates that were added to the feed on the first refresh. Can anyone explain to me why this is happening?
import Gun from "gun/gun";
import { useReducer, useState, useEffect, useContext } from "react";
import {
Box,
Center,
Container,
VStack,
Avatar,
useColorModeValue,
Textarea,
Input
} from '#chakra-ui/react';
import { UserContext } from "../../../lib/context";
// initialize gun locally
const gun = Gun({
peers: [
'http://localhost:4000/gun'
]
})
// create the initial state to hold the messages
const initialState = {
messages: []
}
// Create a reducer that will update the messages array
function reducer(state, message) {
return {
messages: [message, ...state.messages]
}
}
export default function Feed() {
// the form state manages the form input for creating a new message
const [formState, setForm] = useState({
name: '', message: ''
})
const [user] = useContext(UserContext);
// initialize the reducer & state for holding the messages array
const [state, dispatch] = useReducer(reducer, initialState)
// when the app loads, fetch the current messages and load them into the state
// this also subscribes to new data as it changes and updates the local state
useEffect(() => {
const messages = gun.get('messages')
messages.map().on(m => {
dispatch({
userid: m.user?.issuer,
name: m.name,
message: m.message,
createdAt: m.createdAt
})
})
}, [])
// set a new message in gun, update the local state to reset the form field
function saveMessage() {
const messages = gun.get('messages')
messages.set({
userid: user?.issuer,
name: formState.name,
message: formState.message,
createdAt: Date.now()
})
setForm({
name: '', message: ''
})
}
// update the form state as the user types
function onChange(e) {
setForm({ ...formState, [e.target.name]: e.target.value })
}
return (
<div style={{ padding: 30 }}>
<input
onChange={onChange}
placeholder="Name"
name="name"
value={formState.name}
/>
<input
onChange={onChange}
placeholder="Message"
name="message"
value={formState.message}
/>
<button onClick={saveMessage}>Send Message</button>
{
state.messages.map((message, index) => (
<div key={index}>
<h4>{message.userid}</h4>
<h2>{message.message}</h2>
<h3>From: {message.name}</h3>
<p>Date: {message.createdAt}</p>
</div>
))
}
</div>
);
}
Thanks in advance for anyone who takes the time to explain this issue. I can add more pictures if needed but I'm probably going to start working on a solution without reducers and maps and then compare for now. Will update when that's done if no replies!
link to duplicate example without errors
it looks solved by changing .on back to .once. not sure why I changed that in the first place. spent 6 hours messing with map thinking that was the issue lol. mfw :O
I have a react app with many entries, each entry can have many tags.
It is a moderation app, so the entries are listed on a page and a user can click on an entry to moderate it (for example, to add or remove tags). Once clicked, the entry will show up in a modal.
Once the modal is open, a user can chain the entries with a 'next' button, so that the modal does not close. When the user clicks 'next', the next entry gets loaded into the modal.
In the modal, I have a react CreatableSelect component that takes the tag list of that loaded entry.
The issue is that when the user clicks 'next', the tags in the CreatableSelect don't update, it is still showing the tags of the first loaded entry.
Here is the code, transformed to make my issue hopefully clearer.
first, the component is loaded with an empty array of codes
second, the useEffect is triggered and populates the state with 2 dummy codes
Although when I console.log the state, it is correctly updated with the 2 dummy codes, the CreatableSelect still shows empty.
What I would like to understand is why the CreatableSelect does not rerender with the new state?
Thank you!
const SelectTags = ({ nextEntry, entry, topicId, updateEntry }) => {
const projectCodes = useSelector(state => state.project.codes);
const formatedCodes = projectCodes.map(code => ({value: code, label: code, isFixed: true}) );
const [selectedTags, setSelectedTags] = useState([]);
useEffect(() => {
const newTags = [{value: 'hello', label: 'hello'}, {value: 'world', label: 'world'}];
setSelectedTags([...newTags]);
}, [entry]);
const handleChange = newValue => setSelectedTags([...newValue]);
const setSubmittingFalse = () => setSubmitting(false);
return (
<CreatableSelect isMulti onChange={handleChange} options={formatedCodes} defaultValue={selectedTags} />
)
};
export default SelectTags;
Alright, switching the CreatableSelect props from defaultValue to value apparently solved that issue!
<CreatableSelect key={entry.id} isMulti onChange={handleChange} options={formatedCodes} value={tags} />
I am using FormControlLabel for my custom checkboxes but they don't seem to be checked/unchecked based on the state being passed to them.
The onchange handler updates everything in redux/BE but on initial render or after reload they are all unchecked again.
I have tried so many methods that I have lost track and ended up in a bit of a spiral.
I have also tried controlling the checkbox using both value and checked.
when the checkboxes are rendered via the .map they have the correct boolean value being received from the state but the checkbox doesn't reflect that.
Any ideas where I might be going wrong?
I've put the file in a gist here: https://gist.github.com/SerdarMustafa1/052bffd6958858eb5770991f59d1e187
styled checkbox is :
export const CustomCheckbox = withStyles({
root: {
color: `${COLORS.MAIN_ORANGE}`,
"&$checked": {
color: `${COLORS.MAIN_ORANGE}`
}
},
checked: {}
})(props => <Checkbox color="default" {...props} />);
and the redux action/reducer:
export const SET_IS_PIN_CODE_CHECK_IN_CHECKED =
"venueTemplates/SET_IS_PIN_CODE_CHECK_IN_CHECKED";
export const setIsPinCodeCheckInChecked = createAction(
SET_IS_PIN_CODE_CHECK_IN_CHECKED
);
export const handlers = {
[SET_IS_PIN_CODE_CHECK_IN_CHECKED]: (state, { payload: value }) => ({
...state,
isPinCodeCheckInChecked: value
}),
Been going around in circles for ~10hrs, any ideas or help appreciated.
Maybe not the best solution but what worked for me was to use defaultChecked
example:
defaultChecked={item?.isPinCodeCheckInChecked}
You need to use checked (boolean) instead of value (string):
<CustomCheckbox
id={id || ""}
onChange={event => handlePinCodeChecked(event)}
checked={!!isPinCodeCheckInChecked[id]}
name={name || ""}
/>
I created to do list using react, but I want it to be local storage - so when the user refresh the page it still saved the items and will present them.
I read I need to use localStorage but I'm not sure where and how, attach the app.js and TodoItem component
class App extends Component {
state = {
items: [],
id: uuidv4(),
item: "",
editItem: false
};
handleChange = e => {
...
};
handleSubmit = e => {
e.preventDefault();
const newItem = {
id: this.state.id,
title: this.state.item
};
const updatedItems = [...this.state.items, newItem];
this.setState({
items: updatedItems,
item: "",
id: uuidv4(),
editItem: false
});
};
...
render() {
return (
<TodoInput
item={this.state.item}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
editItem={this.state.editItem}
/>
<TodoList
items={this.state.items}
clearList={this.clearList}
handleDelete={this.handleDelete}
handleEdit={this.handleEdit}
/>
);
}
}
export default class TodoItem extends Component {
state = {
avatarURL: '',
}
componentDidMount() {
imgGen().then(avatarURL => this.setState({ avatarURL }));
}
render() {
const { title, handleDelete, handleEdit } = this.props;
const { avatarURL } = this.state;
return (
<h6>{title}</h6>
<span className="mx-2 text-success" onClick={handleEdit}>
</span>
<span className="mx-2 text-danger" onClick={handleDelete}>
</span>
);
}
}
You can do it like this, mind the comments
class App extends Component {
state = {
// load items while initializing
items: window.localStorage.getItem('items') ? JSON.parse(window.localStorage.getItem('items')) : [],
id: uuidv4(),
item: "",
editItem: false
};
handleChange = e => {
// ...
};
handleSubmit = e => {
e.preventDefault();
const newItem = {
id: this.state.id,
title: this.state.item
};
const updatedItems = [...this.state.items, newItem];
// Save items while changing
window.localStorage.setItem('items', JSON.stringify(updatedItems));
this.setState({
items: updatedItems,
item: "",
id: uuidv4(),
editItem: false
});
};
// ...
render() {
return (
<>
<TodoInput
item={this.state.item}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
editItem={this.state.editItem}
/>
<TodoList
items={this.state.items}
clearList={this.clearList}
handleDelete={this.handleDelete}
handleEdit={this.handleEdit}
/>
</>
);
}
}
Here's some simple logic you can use in your componentDidMount() method of your App.
const localStorageList = localStorage.getItem('todo-list')
if (!localStorageList) {return null} else {this.setState({items: localStorageList})
To add to the localStorage please look at this question
and this resource
Let me help you with this, using the least no. of codes. I have written a clear explanation of the steps, for you all to better understand, please bear with me , it is definitely with the time to read.
Also, note this solution is perfectly crafted for functional components. However I have mentioned how to do it in class components, you have to tweak some things if you are using class components. Like you can not use hooks in class-based components, but access this instance, so it will be fine, either ways
Please give it a full read, if you are having a tough time understanding the functionality, I have tried to break down the process in layman. The explanation is long, the lines of code is just under 10. happy to help
Persisting states of the todo app, upon page refresh, is pretty simple.
We can use State management libraries for it, or local storage as well.
Here, we will just go with the most simple one - using local storage.
Before we jump to the code, let us build the functionality visually.
So, after the user enters things in the todo space, we want few things to happen:
We want to store the list of items (which will essentially be an array) in the local storage. (We can skip the JSON.parse, here, since the array that will be saved, will be string, bcz user enters string in the todo-app, generally, however, it's not a bad idea to parse the userinputs).
useEffect(()=>{
window.localStorage.setItems("key" , value)
}, [dependency])
After you do this, make sure you check the dev-tools => application => localStorage => to see if the key and values are being stored. You shall be able to see them.
However, you will notice, that upon refresh, the localStorage values stay, but the data in the UI are lost. Not surprising.
This is the last and important step.
What we want upon page reload? Let us break it down :
We want to check if there is any data that is there in the localStorage. If there is: we will change the state of the app, based on the previous user inputs.
If there is no data in the LocalStorage, we will just pass an empty array.
Using hooks, in the functional component is actually What I prefer, class components require many boiler plates, so, the code...
import {useState} from 'react';/for functional components
//for class components you can just init the state , in the constructor(props) and
change it using the this.setState method
//to getItems from localStorage to render in the UI
useEffect(()=>{
const storedData = localStorage,getItems("keys" , value)
storedData ? setValue(value) : [];
},[])
[] : because we want it to render on every reload, once.
smake sure to initiliaze the state using useState;
const [value , setValue] = useState("")
//to setItems in localStorage
useEffect(()=>{
window.localStorage.setItems("key" , value)
}, [dependency])
useEffect is essentially a hook for functional components which is similar to componentDidMount in-class components.
If using class components, instead of using the useState, hook, use this.setState.
You could format your todolist into a JSON string and store it using :
localStorage.setItem("todolist", "your_JSON_string_here");
However, web Local Storage have storage limitations which will cause issues if the data stored are getting larger in time.
More info at here
Perhaps you could consider IndexedDB (if you are storing huge data) INFO
Will preface that I have about 4 weeks of coding experience and so apologies in advance if I'm missing something obvious.
I'm trying to pass an array of objects from Profile.js to Calendar.js, with Calendar.js currently ultimately being returned again in Profile.js. The idea's that a user would be able to set a schedule and be able to see the same schedule when they go back to the profile page. I was trying to get this to work by having the saved array of objects be displayed upon rendering the Calendar.js, but right now it basically resets itself every time the user goes back to the page.
Two issues I'm guessing are relevant here:
Initializing this.state to this.props.calendar isn't working (seems like this.props.calendar returns empty during initialization) but when I log this.props.calendar in the handleChange or render() it does show up correctly.
I believe handleChange runs whenever the page is clicked and so was thinking there might need to be some way for Calendar.js to display the old saved array of objects instead of an empty array (in case no schedule is selected at the time of click). Was trying to implement some kind of conditional operator here but handleChange currently stops working if it is called while a new empty schedule's selected (so if the user happens to click on the page before selecting a schedule).
This came out to be a bit longer than I expected, but any help would be greatly appreciated!
Calendar.js:
export default class Calendar extends React.Component {
constructor(props) {
super(props);
this.state = {
schedule: this.props.calendar
};
}
handleChange = newSchedule => {
this.setState({ schedule: newSchedule.length !==0 ? newSchedule : this.props.calendar });
this.props.handleSubmitCalendar(newSchedule); // call API function to save current selected schedule to a database
}
render() {
return (
<ScheduleSelector
selection={this.state.schedule}
...code...
/>
)
}
}
Profile.js:
import Calendar from "../components/Calendar";
export default function Profile() {
const [calendar, setCalendar] = useState([]);
useEffect(() => {
async function onLoad() {
try {
const profileCalendar = await loadCalendar();
setCalendar(profileCalendar);
} catch (e) {
onError(e);
}
}
onLoad();
}, []);
async function handleSubmitCalendar(calendar) {
setIsLoading(true);
try {
updateCalendar(calendar);
setIsLoading(false);
} catch (e) {
onError(e);
setIsLoading(false);
}
}
function loadCalendar() {
return API.get("notes", "/calendar");
}
function updateCalendar(calendar) {
return API.put("notes", "/calendar", {
body: {calendar}
});
}
return (
<div className="Calendar">
<Calendar calendar={calendar} handleSubmitCalendar={handleSubmitCalendar}/>
</div>
);
}
Welcome! You should really read the React documentation and go through the examples. Everything you need to known to answer this question is in there and going through it all will make you a more competent developer and save you a lot of headaches. Available here. If you still don't understand once you've gone through the docs, ask again here and I will help you.