Unable to keep checkbox checked on save - javascript

Issue: Every time I refresh the page, the previously checked boxes get unchecked. I have 4 checkboxes. I checked two and then I refresh. The other two wont save. Im not sure what's wrong here. Could you please point my mistake. Much Appreciated.
const handleCheckboxChange = ([event]) => {
return event.target.checked;
};
const organizationInfo = {
// label name, variable name, value
"Organization Name": ["name", name],
"Organization Contact E-mail": ["email", email],
};
const renderNeedSection = () => {
if (organization) {
return (
<div>
{Object.entries(NEEDS).map(([key, label]) => (
<CheckBoxWrapper key={key}>
<Controller
as={Checkbox}
defaultChecked={needs[key]}
name={`needs.${key}`}
control={control}
onChange={handleCheckboxChange}
>
<Label inputColor="#000000">{label}</Label>
</Controller>
</CheckBoxWrapper>
))}
<span style={errorStyles}>
{errors.needs ? "Please select at least one option" : ""}
</span>
</div>
);
}
};

React state isn't persisted across reloads. If you want your data to persist you have among other options
Store it in a server and retrieve it when the page loads
Use the browser localStorage

Related

"Cannot read property 'length' of undefined" on an empty array

Hi I'm new to Node/React, and I'm creating a learning project. It's platform that connects freelancers with nonprofit companies. Users (freelancers) view a list of companies, and click a button to connect to a company. Once this is clicked, the user will have that company added as a relationship in the database. This is working correctly.
Now I'm trying to have a page where the user can view all their connections (the companies they connected with). The solution below works but only if the user has at least one connection. Otherwise, I get the error Cannot read property 'length' of undefined.
To figure out which JSX to render, I'm using a conditional to see if the user has connections. If not, I wanna show "You have no connections". I'm doing this by checking if (!companies.length) then show "you have no connections". companies is set as in empty array in the state. I don't understand why it's undefined. Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error? How can I improve this code to avoid this problem?
function UserConnections() {
const { currentUser } = useContext(UserContext);
const connections = currentUser.connections;
const [companies, setCompanies] = useState([]);
useEffect(() => {
const comps = connections.map((c) => VolunteerApi.getCurrentCompany(c));
Promise.all(comps).then((comps => setCompanies(comps)));
}, [connections]);
if (!companies.length) {
return (
<div>
<p>You have no connections</p>
</div>
)
} else {
return (
<div>
{companies.map(c => (
<CompanyCard
key={c.companyHandle}
companyHandle={c.companyHandle}
companyName={c.companyName}
country={c.country}
numEmployees={c.numEmployees}
shortDescription={c.shortDescription}
/>
))}
</div>
);
}
};
Edit: Sorry, I should've included that the error is being thrown from a different component (UserLoginForm). This error is thrown when the user who has no connections logs in. But in the UserConnections component (code above), if I change if (!companies.length) to if (!companies), the user can login fine, but UserConnections will not render anything at all. That's why I was sure the error is refering to the companies.length in the UserConnections component.
The UserLoginForm component has been working fine whether the user has connections or not, so I don't think the error is coming from here.
UserLoginForm
function UserLoginForm({ loginUser }) {
const [formData, setFormData] = useState({
username: "",
password: "",
});
const [formErrors, setFormErrors] = useState([]);
const history = useHistory();
// Handle form submission
async function handleSubmit(evt) {
evt.preventDefault();
let results = await loginUser(formData);
if (results.success) {
history.push("/companies");
} else {
setFormErrors(results.errors);
}
}
// Handle change function
function handleChange(evt) {
const { name, value } = evt.target;
setFormData(d => ({ ...d, [name]: value }));
}
return (
<div>
<div>
<h1>User Login Form</h1>
<div>
<form onSubmit={handleSubmit}>
<div>
<input
name="username"
className="form-control"
placeholder="Username"
value={formData.username}
onChange={handleChange}
required
/>
</div>
<div>
<input
name="password"
className="form-control"
placeholder="Password"
type="password"
value={formData.password}
onChange={handleChange}
required
/>
</div>
{formErrors.length
? <Alert type="danger" message={formErrors} />
: null
}
<button className="btn btn-lg btn-primary my-3">
Submit
</button>
</form>
</div>
</div>
</div>
);
};
Edit 2: The solutions provided in this thread actually solved the problem. The error message kept persisting due to a different problem coming from another component.
Change this:
{companies.map(c => (...)}
to this:
{companies && companies.map(c => (...)}
and this:
if (!companies.length) {
to this:
if (!companies || companies.length === 0) {
This will then check for a nullish value, before running the map operation or checking length.
Even if the user has no connections, companies is still an empty array. so why why would companies.length return this error?
Your assumption that companies is an empty array is incorrect. Somehow it is set to undefined. You can either protect against this by doing
if (!companies || companies.length == 0) {
or by making sure companies is always set to an array.

How to add function to "X" icon in Material UI Searchbar?

I have the following code:
import SearchBar from "material-ui-search-bar";
const data = [
{
name: "Jane"
},
{
name: "Mark"
},
{
name: "Jason"
}
];
export default function App() {
const [results, setResults] = useState();
const filteredResults = data.filter((item) => {
return Object.keys(item)?.some((key) => {
return item[key].includes(results?.toLowerCase());
});
});
return (
<div className="App">
<SearchBar
value={results}
onChange={(value) => setResults(value)}
placeholder="Please enter name..."
/>
{filteredResults.map((item) => {
return <li>{item.name}</li>;
})}
</div>
);
}
codesandbox
when I delete name from search bar using delete keyboard all names from data are displayed below the search bar, but if I click X button, it clears the search bar but doesn't display all names. Is there a way to add a function to this X button so when I click it, it acts the same way as delete keyboard?
You can pass a function to the onCancelSearch prop to reset the results state variable.
<SearchBar
value={results}
onChange={(value) => setResults(value)}
onCancelSearch={() => setResults('')}
/>
Suggestions
It's better to initialize results with an empty string. You can now remove the ? in results?.toLowerCase() since results will never be nullish (undefined or null).
const [results, setResults] = useState('')
You should pass the key prop to the li element. You can add an id property to the items array to use as the key or use the item index.
{
filteredResults.map((item) => (
<li key={item.id}>{item.name}</li>
))
}
And there are a couple of issues with the filtering logic.
You're converting the search query to lowercase but not the name. In your example, if you search for 'ja', nothing would show up even though matches exist (Jane and Jason).
filteredResults will throw an error if any of the object values do not have the includes method (You can reproduce the issue by adding a numeric id field to the array items). You could fix it by using a searchableKeys array to only perform the search in specific fields.
const searchableKeys = ['name']
const filteredResults = data.filter((item) =>
searchableKeys.some((key) =>
item[key].toLowerCase().includes(results.toLowerCase())
)
)
I would recommend renaming results to query or searchQuery for clarity.
Hello upon checking your problem, the reason why its remapping the list on delete key (in keyboard) because it triggers the onChange event of the searchBar to have the same event as the searchBar, i've tried it on my end and it seems that this solution can be solve your issue
<SearchBar
value={results}
onChange={(value) => setResults(value)}
placeholder="Please enter name..."
closeIcon={<button onClick={() => setResults("")}>clear</button>}
/>
the closeIcon props - overrides the close icon and its methods..
here is the documentation that i check material-ui-search-bar
here also the replicated/solved code-sandbox

How to switch the state of buttons in react hooks after updating it through rest api?

Hey guys I am very new to react and trying to make the frontend of a blog web application. I am able to show the posts on the homepage and I am able to make the like button work without API calls, just with managing states.
Now with API call, the like button shows red(button fills with red) if the post is liked by the user and I am able to unlike it by clicking it, it changes the count and it unlike the post in the backend, but it doesn't change the button state to unlike button and it keeps on unliking it rather than switching to like button state.
If the post is not liked by the user, then the button completely disappears and doesn't show on the screen, so I am not able to like the post.
This is the code I have written, It is not a good way to write react code I think, If anyone can help resolve this issue, it would be highly enlightening as I am still learning. Please do ask for more information if needed.
This is the code.
const [liked, setLiked] = useState(null)
function setlikeCount(post){
return(
post.like_count = post.like_count + 1
)
}
function setunlikeCount(post){
return(
post.like_count = post.like_count - 1
)
}
function likePosts(post) {
console.log('liked the post')
return(
axiosInstance.post('api/posts/' + post.slug + '/like/')
)
}
function unlikePosts(post) {
console.log('unliked the post')
return(
axiosInstance.delete('api/posts/' + post.slug + '/like/')
)
}
{myposts.posts && myposts.posts.results.map((post) => {
return (
<h4>{post.title}</h4>
)
}
{post.likes && post.likes.map((lik, index) => {
console.log(user, lik.id)
return (
user === lik.id ? (<FavoriteRoundedIcon style={{ color: "red" }}
key={index}
onClick={ () =>{
unlikePosts(post)
setunlikeCount(post)
setLiked((liked) => liked===false)
}}
/>)
: (<FavoriteBorderRoundedIcon key={index}
onClick={ () =>{
likePosts(post)
setlikeCount(post)
setLiked((liked)=> liked===true)
}}
/>)
)
})
}
const [myposts, setPosts] = useState({
posts: null,
})
fetching posts
useEffect(() => {
axiosInstance.get('api/posts/myhome').then((res) => {
const allPosts = res.data;
setLoading(false)
setError("")
setPosts({ posts: allPosts })
// console.log(allPosts.results['0'].likes['0']);
})
.catch(() => {
setLoading(false)
setPosts({})
setError('Something went wrong!')
})
}, [setPosts])
In the code, the user has the user's id.
Is it possible to check the condition like user in lik.id than user === lik.id, like how we check conditions in python?
lik looks like this [{id: 1, username: "testuser12"}]
Thanks
You need to show the button based on the content of the array like below
{post.likes && post.likes.find(x=>x.id===user) ?
(<FavoriteRoundedIcon style={{ color: "red" }}
key={index}
onClick={ () =>{
unlikePosts(post)
setunlikeCount(post)
setLiked((liked) => liked===false)
}}
/>)
: (<FavoriteBorderRoundedIcon key={index}
onClick={ () =>{
likePosts(post)
setlikeCount(post)
setLiked((liked)=> liked===true)
}}
/>)
}
If the array has values and the user is part of the array you show red button and if the array is not defined or user is not in the array you show the other button.
Firstly, your setLiked method isn't right. if you want to set it to true/false just call:
setLiked(true)
Secondary, you should init your liked state. Meaning you need to useEffect (when the component loads) and read from your API if post liked or not. But the initial value better to be false and not null.

How to select group based checkbox antd in reactjs

I want to select a group based checkbox. The problem is that when I click on the group, the entire checkbox is selected. I don't want to select the entire checkbox. this is my Initial State.
const plainOptions = ["can_view", "can_create", "can_update"];
state = {
checkedList: [],
indeterminate: true,
checkAll: false
};
Method: onchange method basically works each individual checkbox.
onChange = checkedList => {
console.log(checkedList);
this.setState({
checkedList,
indeterminate:
!!checkedList.length && checkedList.length < plainOptions.length,
checkAll: checkedList.length === plainOptions.length
});
};
This method works for selected all checkbox
onCheckAllChange = e => {
console.log(e.target.checked);
this.setState({
checkedList: e.target.checked ? plainOptions : [],
indeterminate: false,
checkAll: e.target.checked
});
};
{
["group", "topGroup"].map(item => (
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={this.state.checkAll}
>
{item}
</Checkbox>
<CheckboxGroup
options={plainOptions}
value={this.state.checkedList}
onChange={this.onChange}
/>
</div>
));
}
However, my accepted Data format is
{group:["can_view","can_create"],topGroup:["can_view","can_create"}
I want to get this format output when user selected on the checkbox
Here is the code sandbox : https://codesandbox.io/s/agitated-sea-1ygqu
The reason both groups change when you click something in one of them is because both groups use the same internal state.
["group", "topGroup"].map(item => (
<div className="site-checkbox-all-wrapper">
<Checkbox
indeterminate={this.state.indeterminate}
onChange={this.onCheckAllChange}
checked={this.state.checkAll}
>
{item}
</Checkbox>
<CheckboxGroup
options={plainOptions}
value={this.state.checkedList}
onChange={this.onChange}
/>
</div>
));
Both the group and topGroup use the same this.state.checkList state.
The easiest way to solve this is by extracting each group into its own component. This way they have their own state separate of each other.
You could also opt to keep one component, but you must manage multiple internal states. You could for example use state = { checkList: [[], []] } where the first sub-array is to store the group state and the second sub-array is to store the topGroup state.
If groups are dynamic you can simply map over the groups and create your states that way:
state = { checkList: groups.map(() => []) };
You would also need to manage multiple indeterminate and checkAll states. This can be avoided when you deduce those from the checkList state. For example:
isIndeterminate(index) {
const checkList = this.state.checkList[index];
return checkList.length > 0 && checkList.length < plainOptions.length;
}
This would also avoid conflicting state, since there is one source of truth.

Handle change and select list of options

I'm new to React and I'm trying to select an option from the list and click the button to confirm the selection. I save the selected option in the "pickedUser" object. But when I change the state of the "pickedUser", I think the render is start again and the list is like at the beginning. I just want to select the option and click on the button without restarting select list. If you can help solve the problem and point out the mistakes I need to correct in order to get better. Thanks!
//pickedUser saving selected option from list.
constructor(props){
super(props);
this.state = {
users: [],
pickedUser:{
name:"",
email:"",
uloga:""
},
isLoading: true,
errors: null
};
this.handleChange = this.handleChange.bind(this);
this.routeChange = this.routeChange.bind(this);
}
//In handleChange I'm trying to set a value I get from select to pickedUser.
async handleChange(event) {
const eName = event.target.value.split(',')[0];
const eEmail = event.target.value.split(',')[1];
const eUloga = event.target.value.split(',')[2];
await this.setState({
pickedUser:{
name : eName,
email: eEmail,
role: eUloga
}
});
}
//And this is a part of my render.
render() {
const { isLoading, users, pickedUser } = this.state;
return (
<div>
<select
id="list"
value={pickedUser}
onChange={event => this.handleChange(event)}
>
{!isLoading ? (
users.map((user, key) => (
<option key={key}>
{user.name},  {user.email},   {user.role}
</option>
))
) : (
<p>Loading...</p>
)}
</select>
<button id="b1" type="button"
value={pickedUser}
onClick={event => {
this.handleSubmit(event);
this.routeChange();
}}>
Sign in
</button>
I wanted to function normally, that when I select the option, it stays selected, but it happens that when I select it, it is refreshed again.
I just have to tell that the value is good when the option is selected but the display is not.
Technically you only have to correct this line
<select
id="list"
value={pickedUser.name + "," + pickedUser.email + "," + pickedUser.role}
onChange={event => this.handleChange(event)}
>
value should not be object (pickedUser), but it should be string.
This is working example
But I can suggest following:
Make state.users object (not array). Email should be unique, so it can be used as key. For example:
this.state = { users: { "jack#mail.com": {name: "Jack", uloga: "aaa"},
"mark#mail.com": {name: "Mark", uloga: "aaa"} } }
In this case you'll be able to extract user from users by it email.
Object also support iteration like arrays useng Object.keys(users) or Object.values(users)
Use email as key for <option>. Index as keys is not good practice in React.
Add id to each <option> to easily identify selected option in event handler
Suggested version is here

Categories

Resources