reactjs -- handle checkbox (multiple) click events - reactjs

I know there are some similar topics but none seems to be in the same direction of what I'm trying to do, thus a new thread.
I have a component that displays a list of keys, each with a checkbox attached to the string. In addition, I have a button that supposedly calls an API with all keys selected and delete these keys.
Several things I'm trying to achieve:
checking a check box enables the delete button
click the delete button should send a POST to API, the list should then reload
Since the list is reloaded, all checkbox should be unselected, thus the delete button is once again disabled
there's another button outside of this function that checks for the length of the list as well, which I don't know how to associate with this list if I fetch the list in the component.
I'm facing the problem which I don't know how to make the button and the checkboxes associate to each other. I tried using state with a checked state, which is a boolean, but that's only one boolean and cannot record several keys. I think using an array would work? Then again I'm not sure how to properly append or remove the key checked.
my code looks like
class AppList extends Component {
constructor() {
super();
this.state = {
checked: [],
apps: []
};
this.handleChecked = this.handleChecked.bind(this);
}
componentDidMount() {
fetch("some_url", {
method: 'post',
body: JSON.stringify({"user": "some_email"}),
headers: {'Content-Type': ' application/json'}
})
.then(res => res.json())
.then(
(result) => {
this.setState({apps: JSON.parse(result)});
},
(error) => {
console.log("error", error);
}
);
}
handleDeleteKey = (event) => {
// fetch(I'll worry about this later)
this.setState({checked: false});
console.log("delete!!!!");
}
handleChecked () {
this.setState({checked: !this.state.checked});
}
render () {
const apps = this.state.apps.map((app) =>
<div>
<input type="checkbox" onChange={this.handleChecked} />
{` ${app}`}
</div>
);
return (
<div>
<h4>Client Key List:</h4>
{this.state.apps.length > 0 ? <ul>{apps}</ul> : <p>No Key</p>}
{this.state.apps.length > 0 ? <button className="api-instruction-button" onClick={this.handleDeleteKey}>Delete Selected Key(s)</button> : null}
</div>
);
}
}
export default AppList;
I feel like my design is completely wrong but I don't know how to fix it. It seems like there are so many states to be passed around and nothing is the outermost, almost a cyclic dependency.
Anyone had any experience dealing with this problem? It seems like it's a common user action but I can't figure it out.
EDIT: after digging it a bit more, it seems like I need to call componentDidMount outside of the AppList. It should be in the component that uses AppList, let's call it MainApp.
MainApp calls componentDidMount which is the same as the one in AppList. The one in AppList gets removed, and the keys are passed to AppList as props.
I have trouble handling the clicking event. It seems like the component is always updating, so if I want to append the clicked key to the array, it wouldn't work. The same call will be made again and again.
Since there's another button in MainApp that requires the list of keys, I can't just pass the call into AppList. However, updating in AppList should update the MainApp as well. How does it work? I'm so confused
EDIT2:
https://codesandbox.io/s/7w2w11477j
This recreation should contain all functions I have so far, but I can't get them to work together.
Again my task is simply:
I have a list of strings, each with a checkbox
checking the checkbox selects the specific string
There's a button that I can click to delete these entries in my db by calling an API
Is refreshing the MainApp needed in this case? Otherwise I need to delete the strings in frontend so they don't display after the delete button is pressed

Here's what I believe you were going for: https://codesandbox.io/s/w23wv002yw
The only problem that made yours not work properly was you were just getting a little jumbled with where to put everything.
Contents:
The MainApp.js will only contain the apps and a method for deleting them in the backend. Other than those two methods, nothing else really concerns the MainApp.js file.
The AppList.js will contain all the methods that update its own checked state, the delete button itself, and a method to clear the checked state on delete.
Processes:
First, MainApp.js will load and remount with a backend api pull and populate its apps state. Once it's finished that, it will pass it on to AppList.js. From there, AppList.js will render that list as a multi-select field onscreen. The user can then select or deselect any of the options. As an option is selected, its index is pushed to the checked state and organized in ascending order.
(ordering the array isn't that necessary, but I figured it would help if you wanted to retool it sometime down the road)
When one or more option is selected, a delete button will appear. When the user clicks the delete button, AppList.js will call the delete function passed to it from MainApp.js, then it will clear the current checked state.

Related

Why every tweet is being deleted

So I'm using React and I made a little code that has a textbox with a submit button, and when something is typed in and submitted, it creates a tweet with the text I typed in and some pre-made elements as you see here:
[1]: https://i.stack.imgur.com/pvfSf.png
The random letters with the Like and Delete buttons is what I submitted each time.
What I'm confused is why every single tweet gets deleted when I use this code:
const deleteTweet = () =>{
setTweets(tweets.filter(tweet => tweet !== tweet))
}
But when I use this code, only the one specific tweet I clicked Delete on, gets removed:
const deleteTweet = () =>{
setTweets(tweets.filter(t => t !== tweet))
}
The 'tweet' after the !== is a prop I'm passing down.
You have a conflict between tweet used as parameter on map and tweet used as props, Javascript's scoping take the latest variable which is the map's tweet.
So when you put tweet on map function it overrides the props tweet.
You may take a look at closures : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

Why won't my app render when state changes?

I'm building an application that allows for posting community concerns with the ability to upvote those concerns using React, and right now, I am working on the upvote functionality. One way I'm trying to limit the currently logged in user to a single vote is to disable the button once an upvote has been successfully registered for any given post.
To do this, I created a function that checks if the logged in user's ID matches the ID of the upvote for each post. If no match is found, this means the user hasn't voted for already and can register the new upvote. Once this is complete, the button is disabled. I created state for this and is set to false upon the initial render (not sure if this is what I should be doing). I also created state for the all of the votes that have been successfully registered. Both are included below.
const [alreadyVoted, setAlreadyVoted] = useState(false);
const [userVotes, setUserVotes] = useState(upvotes) // upvotes is being passed via props
I'm using the useEffect hook (again, not sure if this is the best way) to check if each button should be enabled or disabled like so:
useEffect(() => {
hasVoted()
}, [userVotes])
Finally, my hasVoted function checks to see if the user has already voted for the issue before and determines the state. It looks like:
function hasVoted() {
userVotes.forEach(vote => {
if (vote.issue_id === issue.id) { // issue here is from props
setAlreadyVoted(true) // I want this to then disable my button
}
})
}
Right now, when I click the button to register the upvote, the page doesn't rerender upon clicking. However, if I refresh the page, the button is successfully disabled as it should be. It probably goes without saying, I'm still getting the hang of React, but any and all help is appreciated.
This code is meaningless at first place
userVotes.forEach(vote => {
if (vote.issue_id === issue.id) { // issue here is from props
setAlreadyVoted(true) // I want this to then disable my button
}
})
, it can be transformed into
if (userVotes.some(vote => vote.issue_id === issue.id)) setAlreadyVoted(true)
but still the logic of it is not clear
I think hasVoted function should be in the useEffect scope. If it's outside, it might introduce bugs, as documented here: https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies
You could try if this helps to resolve your problem
you seem to be somewhat on the right track here but your problem may lay elsewhere,provided the props are passed correctly,because the application does re-render when the state changes. if you want to just disable button you can just use simple html attribute to do that
{alreadyVoted ? (
<button class="btn btn-success" disabled="disabled">
Upvote
</button>
) : (
<button
class="btn btn-success"
onClick={() =>
// Add the id to the setUserVotes here
setAlreadyVoted(true)}
>
Upvote
</button>
)}
Take a look at this codesandbox that i just created and get back to me if you have any issues still

Preserve internal state on page refresh in React.js

It must be pretty regular issue.
I'm passing props down to the children and I'm using it there to request to the endpoint. More detailed: I'm clicking on the list item, I'm checking which item was clicked, I'm passing it to the child component and there basing on prop I passed I'd like to request certain data. All works fine and I'm getting what I need, but only for the first time, ie. when refreshing page incoming props are gone and I cannot construct proper URL where as a query I'd like to use the prop value. Is there a way to preserve the prop so when the page will be refresh it will preserve last prop.
Thank you!
(You might want to take a look at: https://github.com/rt2zz/redux-persist, it is one of my favorites)
Just like a normal web application if the user reloads the page you're going to have your code reloaded. The solution is you need to store the critical data somewhere other than the React state if you want it to survive.
Here's a "template" in pseudo code. I just used a "LocalStorage" class that doesn't exist. You could pick whatever method you wanted.
class Persist extends React.Component {
constuctor(props) {
this.state = {
criticalData = null
}
}
componentDidMount() {
//pseudo code
let criticalData = LocalStorage.get('criticalData')
this.setState({
criticalData: criticalData
})
}
_handleCriticalUpdate(update) {
const merge = {
...LocalStorage.get('criticalData')
...update
}
LocalStorage.put('criticalData', merge)
this.setState({
criticalData: merge
})
}
render() {
<div>
...
<button
onClick={e => {
let update = ...my business logic
this._handleCriticalUpdate(update) //instead of set state
}}
>
....
</div>
}
}
By offloading your critical data to a cookie or the local storage you are injecting persistence into the lifecycle of the component. This means when a user refreshes the page you keep your state.
I hope that helps!

React with Redux Update only state

I'm working on integrating Redux in an already finished SPA with ReactJS.
On my HomePage I have a list of the 4 newest collections added which on render, I fetch with axios from my database. These are then saved in Redux Store and displayed on the React UI.
My mapStateToProps look something like this:
const mapStateToProps = (state) => ({
credentials: credentials(state),
collections: collections(state)
});
Where credentials is irrelevant and collections is:
const collections = (state) => {
if (state.collectionsHomeViewReducer.fetching === true) {
return {
fetchingCollections: true
}
}
else if (state.collectionsHomeViewReducer.data) {
const response = state.collectionsHomeViewReducer.data;
return {
collections: response.collections,
fetchedCollections: true,
fetchingCollections: false
}
}
else if (state.collectionsHomeViewReducer.fetched === false) {
return {
fetchedCollections: false,
fetchingCollections: false
}
}
};
What is it I want to do:
Update the store state every time another client, or the current client, adds a new collection. Moreover, I do not wish for the UI to update immediately after I dispatch(action), I want it to update when a user refreshes the page or when he navigates to another view and returns ( I believe what I'm trying to say is when componentDidMount is called ).
What have I achieved so far:
By using socket.io, I
socket.emit("updateCollectionsStore")
socket.on("updateCollectionsStore")
and
socket.broadcast.emit("updateCollectionsStore")
in their respective places in the application. The final call of
socket.on("updateCollectionsStore")
after the broadcast, is in the main file of the page, app.jsx where the store is also located. The function there looks like this:
socket.on("updateCollectionsStore", () => {
store.dispatch(getCollectionsHomeView());
});
The store is updated and everything works fine, as viewed from the Redux Dev Tools.
What I can't seem to figure out is to tell the props not to change due to the fact that mapStateToProps is called every time an action is dispatched.
Why do I need this: The HomePage can deal with a continuous UI update and data fetching but I also have a page ReadAllPage where you can real all collections. The problem is if there will always be the newest post on the top, whenever a new one is added, the current one is pushed downwards. In case somebody had the intent to click the one that was pushed down, now he might have accidentally clicked the one that took its place, which is not wanted.
What else should I do different or further to achieve the result I want?
Thank you.
According to your needs, I would have two properties in the state. First is that is currently visible on the HomeView and the second is that is updated via sockets. Once a user navigates to the HomeView you can just replace the first collection with the second one.

React component not updating as expected on state

I am building a simple recipes list app and am running into a problem. I am probably misunderstanding something fundamental about how React renders.
Here's some of the code from my codepen
(scroll down to where I explain my code and my problem)
save(action, item){ //this is the save method of my App component
if (action == 'edit'){ // EDIT
...
} else if (action == 'delete'){ // DELETE
let newRecipes = this.state.recipes.filter( function(recipe){ return recipe.key != item.key } ) //this correctly removes the item I want to delete
this.state.recipes = newRecipes; //making extra sure the state is updated
this.setState({ recipes: newRecipes }); //shouldn't this make App render?
this.forceUpdate(); //cmon App, please re-render
console.log('newRecipes', newRecipes);
console.log('main state', this.state); //the state was updated as expected, but App does not show what the state contains
}
Later, in App's render()
<ul className="well list-group">
{
this.state.recipes.map( (recipe)=>{ //i expect the recipe items generated to reflect App's new state, but they dont :(
return(
<Recipe
save={this.save}
original={recipe}
id={recipe.key}
title={recipe.title}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
)
} )
}
</ul>
My app is like this:
I have an App component at the top that renders all the other components and whose state should contain all the data about the App overall.
state.recipes is an array with recipe objects. These are passed as properties to the Recipe component, in the App render method. (<Recipe title=... />)
The <Recipe /> component has methods and buttons to edit and delete the recipe. when a recipe is edited or deleted, the change is handled by the save() method in App, which is also passed to <Recipe /> as a prop.
From my understanding, that is all standard React usage.
This all seems to work as expected. When I edit or delete a recipe the change is properly reflected in the App state.
The Problem
The unexpected behavior happens when deleting a recipe. Even though the correct recipe is removed from App state, that change is not reflected in the rendered HTML. Instead, whatever the last recipe in the App.state.recipes array is removed and I am left staring at a console log of the state and rendered HTML that show different things.
What I've tried:
I expected that using setState({ recipes: newRecipes}) to update App state to the newly updated recipes list and show it on the screen. But this did not seem to work. I even used .forceUpdate() but that did not work.
I added some console logging to Recipe's constructor function and learned that it only runs once in the beginning. It doesn't run again as I would expect when the recipes array in App's state is changed.
You missed to put a key property on Recipe elements, because of that React does not know which items are changing and how to react to that change. If you just put the key property everything will work as expected.
<Recipe
save={this.save}
original={recipe}
id={recipe.key}
key={recipe.key} // <-- HERE
title={recipe.title}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
Remember to add a key property to all the components created from iterations.
See your example working here
http://codepen.io/damianfabian/pen/egeevR

Resources