Add new user on button click in reactJS - javascript

I have following code
my code
I have a button "add", and when I am clicking "new user" was added in my array.
But it works only the first time when I am clicking second, third.... times nothing was happening.
how many times I click so many times "new user" should be added into an array.
like this.
I am using functional components and useState hook.
Please help me resolve that problem

Put the <NewUser /> inside of the setAddNew([...addNew])array. Check the following code:
function add() {
setAddNew([...addNew, <NewUser />]);
}

Try this code in your Add.jsx
import React, { useState } from "react";
function Add() {
const [addNew, setAddNew] = useState([]);
function add() {
// HERE is the change, you need to keep the previous values
setAddNew([...addNew, <NewUser />]);
}
return (
<div>
<button onClick={add}>Add </button>
<div> {addNew} </div>
</div>
);
}
const NewUser = () => {
return (
<p>
<div>New User </div>
</p>
);
};
export default Add;
addNew - is the state, in this case, an array.
setAddNew - is the function to handle the State, but you need to handle the logic

This is happening because you are resetting the value of array for pushing new value to it you have to use
setAddNew((old) => [...old, <NewUser />]);
this will copy your old array and will also insert the new element to it

In the Add.jsx what needs to change is:
function add() {
setAddNew(<NewUser />);
}
into...
function add() {
setAddNew([...addNew, <NewUser />]);
}
The previous function would keep resetting <NewUser/ > with itself, thus being only 1.
When adding the previous addNew state via ...addNew within an array [], thus being [...addNew, <NewUser />], this will take the previous state of addNew and add a <NewUser /> on top of that.

Related

Can't understand how reactivity and stores work

I'm trying to understand how reactivity should work, but I cannot figure out why some things work how they work.
Svelte REPL with the code: https://svelte.dev/repl/aa0821cd95c54708b2d12a05bf74577e?version=3.49.0
I created a simple app:
App.svelte:
<script>
import { numbers } from './store.js'
function copy() {
$numbers.copied = $numbers.selected
}
function select(i) {
console.log("Selected: " + i)
$numbers.selected = i
}
</script>
<button on:click={select(1)}>
1
</button>
<button on:click={select(2)}>
2
</button>
<button on:click={select(3)}>
3
</button>
<button on:click={copy()}>
Copy
</button>
<h1>
Selected: {$numbers.selected}
</h1>
<h1>
Copied: {$numbers.copied}
</h1>
store.js:
import { writable } from 'svelte/store'
export let numbers = writable({
selected: null,
copied: null
})
From this, I was expecting:
On launch, store values stay as null
On every button click, $store.selected changes to proper value
$store.copied updates its value only when Copy button is clicked
Instead:
On launch, select function is called 3 times, once for every button with its argument
$store.selected and $store.copied both have value 3 which cannot be changed by clicking buttons
When clicking buttons, select function is not called
Your event handlers are just wrong. You are calling the functions instead of passing them.
You need something like on:click={() => select(1)}. For copy you could also pass it as is because it has no arguments: on:click={copy}.

How can I assign modal form component opening function to any another button component

I have 2 components: ModalForm and BookCard. The ModalForm is supposed to be put inside of the BookCard. When you put the ModalForm - it creates new button and functionates as it has to. But what if instead of creating new button I want to assign ModalForm's functionality to already existing button?
BookCard.js
const BookCard = ({id, name, cover, description, price, count, loadResponse}) => {
return (
<Card>
//some code
<ModalForm /*some props*/ />
<button className="edit-button"> // <- the button, I want to be assigned to be opening modal
<box-icon name='edit-alt'/>
</button>
</Card>
);
}
ModalForm.js
const ModalForm = ({/*some props*/}) => {
//some code
return (
<div>
<Button onClick={handleClickOpen} /> // <- the button that is being shown instead of the wanted one
//some code
</div>
);
}
Its looks like you need to handle the logic in the most up component in your case BookCard.
So ModalForm has some props like isOpen in BookCard you need state like isModalOpened and then add callback onClick that change state (isModalOpened) and then react pass that prop(isOpen) to your ModalForm.

React - problems with useState and Firebase

I was trying to resolve this problem, but I have no luck...
I'm using React and 'react-bootstrap'. Getting data from firebase with useState, as you can see in the next code. But also I'm calling a modal as a component, and this modal use useState to show and hide the modal.
export const Models = () => {
const [models, setModels] = useState(null);
useEffect(() => {
firebase.database().ref('Models').on('value', (snapshot) => {
setModels(snapshot.val())
});
}, []);
return models;
}
the problem result when I click on the url to access the modal, this one is shown and the main component goes to firebase and tries to get the data again. So, if I click 3 times on the modal, I will get my data from firebase 3 times.
How can I fix this? to get my data from firebase only one time, regardless of the times that you open the modal window?
The other part of the code
const Gallery = () => {
const [fireBaseDate, setFireBaseDate] = useState(null);
axios.post('https://us-central1-models-gallery-puq.cloudfunctions.net/date',{format:'DD/MM/YYYY'})
.then((response) => {
setFireBaseDate(response.data)
});
let content = Models();
let models = [];
const [imageModalShow, setImageModalShow] = useState(false);
const [selectedModel, setSelectedModel] = useState('');
if(content){
Object.keys(content).map((key, index) =>
models[index] = content[key]
);
models = shuffleArray(models);
console.log(models)
return(
<div className="appContentBody">
<Jumbo />
<Promotion models={models}/>
<div className="Gallery">
<h1>Galería - Under Patagonia</h1>
<Filter />
<div className="img-area">
{models.map((model, key) =>{
let myDate = new Date(model.creationDate);
let modelEndDate = new Date(myDate.setDate(myDate.getDate() + 30)).toLocaleDateString('en-GB')
if(fireBaseDate !== modelEndDate && model.active === true){
return (
<div className="img-card filterCard" key={key}>
<div className="flip-img">
<div className="flip-img-inner">
<div className="flip-img-front">
<img className="single-img card-img-top" src={model.thumbnail} alt="Model"/>
</div>
<div className="flip-img-back">
<h2>{model.certified ? 'Verificada!' : 'No Verificada'}</h2>
<p>Número: {model.contact_number}</p>
<p>Ciudad: {model.city}</p>
<p>Servicios: {model.services}</p>
</div>
</div>
</div>
<h5>{model.name}</h5>
<Button variant="danger" onClick={() => {
setImageModalShow(true)
setSelectedModel(model)}
}>
Ver
</Button>
</div>
);
}
return 0})}
</div>
<Image
show={imageModalShow}
onHide={() => setImageModalShow(false)}
model={selectedModel}
/>
</div>
<Footer />
</div>
)} else {
return (
<div className="loading">
<h1>Loading...</h1>
</div>
)}
}
export default Gallery;
Thanks for your time!
Models is a regular javascript function, not a functional component. So this is not a valid use of hooks, and will not work as expected. See docs on rules of hooks.
A functional component receives props and returns JSX or another React element.
Since it does not, it is basically restarting and calling your effect each time its called by the parent.
Looking at your edit, you should probably just remove the Models function and put the logic in the Gallery component.
The way I read your above component makes it seem like you've defined a custom hook for getting data from firebase.
So first off, I would rename it to useFbData and treat it as a custom hook, so that you can make use of the ESLint Plugin for Hooks and make sure you're following the rules of hooks.
The way you have this above, if it's a function within a component, your function will fire on every render, so the behaviour you are describing is what I would expect.
Depending on how expensive your request is/how often that component renders, this might be what you want, as you probably don't want to return stale data to your component. However, if you feel like the response from the DB should be cached and you have some logic to invalidate that data you could try something like this:
import { useEffect, useRef } from 'react';
const useFbData = invalidationFlag => {
const data = useRef(null);
useEffect(() => {
if (!data.current || invalidationFlag) {
firebase.database().ref('Data').on('value', (snapshot) => {
data.current = snapshot.val();
});
}
}, [invalidationFlag]);
return data.current;
};
export default useFbData;
This way, on the initial run and every time you changed the value of invalidationFlag, your effect inside the useFbData hook would run. Provided you keep track of invalidationFlag and set it as required, this could work out for you.
The reason I used a ref here instead of state, is so that the effect hook doesn't take the data in the dependency array (which would cause it to loop indefinitely if we used state).
This will persist the result of the db response between each call and prevent the call being made multiple times until you invalidate. Remember though, this will mean the data you're using is stale until you invalidate.

Binding setState() to a global function only affects one instance of a component

Note: I have edited the question after the changes I have made according to Nicholas Tower's answer.
I have a global function which bound to a component and changes it's state.
I want to build a form builder system. There is a global function named setLabel which is bound to a component named InputBox and changes it's state. This global function is triggered via another component named ModalPanel which controls the editable properties on the bound component InputBox. I have simplified the function and component class for simplicity of this question.
Here is the global function:
function setLabel(postID, attributeName ){
var text = 'example';
if(text !== ''){
this.setState({ [attributeName] : text});
}
}
And here is the component which is bound to the setLabel function. Notice how setLabel function is passed from parent InputBox component to child ModalPanel component function as a property.
class InputBox extends Component{
constructor(props){
super(props);
this.state = {
placeholder : '',
maxlength: '',
type: '',
}
this.setLabel = setLabel.bind(this); // Binding this to the global function.
}
render(){
let elementId = "inputElement-" + this.props.idCounter;
let mainElement = <Form.Control
id = {elementId}
type = {this.state.type}
placeholder = {this.state.placeholder}
maxLength = {this.state.maxlength}
/>
return <Row>
<ModalPanel
handleHide = {this.props.handleHide}
handleShow = {this.props.handleShow}
setLabel = {this.setLabel}
/>
</Row>
}
}
Lastly, below is the ModalPanel component function where the setLabel function is triggered.
function ModalPanel(props){
return(
......................
......................
......................
<Button variant="primary" onClick = {() => props.setLabel()}>Save changes</Button>
......................
......................
......................)
}
setLabel function which is aimed to set the state of InputBox must be triggered when a button is clicked in the ModalPanel component. The problem is, there are multiple rendered <InputBox /> components on the window and when I try to use this functionality, "the state change" only affect the first instance of <InputBox /> component. What I want to do is that, every instance should have their own internal state and setLabel() function should be bound to the specific component from where it is called. So that, this function can be able to set the state of different component instances. How could I do that?
Addition:
Please check the link below to see a gif image showing how my system works wrong. As you can see, even though I choose the third input box element to edit it's properties (in this case, set it's placeholder text), the change is being made to the first one.
Go to gif
Add a this. to the beginning, as in:
this.setLabel = setLabel.bind(this);
Now you're setting a property on the instance of the InputBox. Make sure to refer to it using this.setLabel when you reference it later in the component.
Is setLabel acting on a specific postID? Is the problem that <Button /> of every <ModalPanel /> acting on the same postID? Because you aren't using setLabel correctly inside <ModalPanel />. setLabel takes in 2 arguments and right now your implementation isn't using any. This is your click handler.
onClick = {() => props.setLabel()}
Try console.logging inside setLabel and see what values you're getting when you click on each button
function setLabel(postID, attributeName){
console.log(postID, attributeName)
var text = 'example';
if(text !== ''){
this.setState({ [attributeName] : text});
}
}
Since the React components only updated from props or state changes, you need to pair the global state with a local state to update the component. See the code below in a sandbox environment.
let value = 0;
function updateStuff () {
console.log("this from update", this.name);
value++;
this.setState({name: "Hakan " + value});
}
class Test extends React.Component {
constructor(props){
super(props);
this.state = {
name: 'notchanged',
counter: 1
}
this.localFunc = this.localFunc.bind(this)
updateStuff = updateStuff.bind(this)
}
localFunc(){
let {counter} = this.state;
this.setState({counter: counter + 1});
updateStuff();
}
render () {
return (
<div>
<div>Test 2</div>;
<div>Counter: {this.state.counter}</div>
<div>Name: {this.state.name}</div>
<button onClick={this.localFunc}>Increment</button>
</div>
);
}
}
ReactDOM.render(
<Test/>,
document.getElementById('root')
);
Think, you are using React in incorrect way
The preferred way for me looks like:
Have a dumb/presentational InputBox which accepts label as a property (in props, not in state)
Have a smart/container component which contains state of multiple InputBoxes and passes the correct label into InputBox
If you are trying to implement InputBox PropertyEditor as a separate component - consider adding event bus, shared between them for example via React Context (or even use full flux/redux concept)
Add this to your function calls after binding them or use arrow functions !

Array.map item to a state

How do I get the values of array.map outside of it?
For example:
array.map(index, item) => { return ( <Text>{item.ID}</Text> ) }
Ouside of it I have something like:
<Button onPress={() => this.editItem(CURRENT_ITEM_ID)}></Button>
Where CURRENT_ITEM_ID is the current item on a Card, for example.
Like Tinder I have an array of Cards to Swipe, that I'm mapping, but the Like button is not part of the Card. This way I'm not moving the Like button with the Card, instead it is static, I'm just moving the content of the card. So I need a way to access the current item from outside of the map. The Card has Text and ID. The component below have a Button that I need to click passing the item.ID of the current Card.
Is there a way to set this item to a state?
Thank you.
One solution is to create a state property that holds the id of the card that is showing, and then when the button is clicked, you grab that state and do something with it. Here is an example with onClicks and divs.
const arr = ['card1', 'card2', 'card3', 'card4'];
class App extends React.Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this);
this.handleButtonClick = this.handleButtonClick.bind(this);
}
handleClick(index) {
this.setState({ visibleCard: index });
}
handleButtonClick() {
console.log('this.state.visibleCard', this.state.visibleCard);
console.log('visibleCard', arr[this.state.visibleCard]);
}
render() {
return (
<div>
{arr.map((card, i) => <div onClick={() => this.handleClick(i)}>{card}</div>)}
<button onClick={this.handleButtonClick}>test</button>
</div>
)
}
}
The basic idea is that you tie the index to the card. A handler then sets that state to visible(not sure what this would be in your case since it seems like a mobile app). Then in your button, you use the state to grab the visible card and pass the data to whatever other function you need.
What I normally do is bind the event in the map function.
array.map( (item, key) => {
return (
<div>
<Text>{item.ID}</Text>
<button onClick={this.editItem.bind(this, item.ID)}>Edit</button>
</div>
)
}
Not sure what your HTML looks like, but you need to bind the function params in the map.

Categories