Push Object to Array UseState - reactjs

I am learning ReactJs, and building a sample project. I am facing a problem while pushing the items to the array. My application has a list(10 items) and I am trying to push props to an array; But I am facing the below issues:
On click of "Choose" button, first time item does not push into the array, but it pushes on the second click.
When I click on the other li item the first item pushed to the array is deleted and the second item pushes to it.
I am trying to solve this for the last few hours, I tried other solutions on this platform but nothing works. What I am doing wrong.
import React, {useState} from 'react';
const MItem = (props)=>{
const [nameList, setName] = useState([]);
const NameHandler = ()=>{
setName(nameList => [...nameList, props]);
}
return (
<div>
<div>
<h3>{props.title}</h3>
<img src={props.pic} />
<p>{props.year}</p>
</div>
<div>
<button onClick={NameHandler}> Choose </button>
</div>
</div>
)
}
export default MItem;

Lets assume, you sent test using the attribute value as below
<MItem value="test">
Now you should use props.value not props
setName(nameList => [...nameList, props.value]);
Here is your example:
import React, {useState} from 'react';
const MItem = (props)=>{
const [nameList, setName] = useState([]);
const NameHandler = ()=>{
setName(nameList => [...nameList, props.value]);
}
return (
<button onClick={NameHandler}> Choose </button>
)
}
export default MItem;
More detail

you don't need to pass a function to setName you can just do.
Based on your comments
const NameHandler = ()=>{
const item = {title: props.title, pic: props.pic}
setName([...nameList, item]);
}

Related

Button don't passing data to another component. React

I have a component which is a button. Then in another component i am looping trough concerts and using this button to redirect to booking page but after clicking my data is not passed.
This is my button component:
import React from "react";
export const BookBtn = (props) => {
return (
<div>
<button
className="bookBtn"
style={{ backgroundColor: props.color }}
// onClick={props.func}
>
{props.text}
</button>
</div>
);
};
BookBtn.defaultProps = {
text: "Unavailable",
};
export default BookBtn;
Here is the button in my main component where I try to click
<a href={"/concert/" + concert.id} data={concert}>
<BookBtn text="Book a ticket" />
</a>
Here is my component where i try to redirect to and retrive my data.
import React from "react";
import { useEffect, useState } from "react";
import axios from "axios";
export const Book = (data) => {
const [concerts, setConcerts] = useState([]);
const [tickets, setTickets] = useState([]);
useEffect(() => {
const loadConcerts = async () => {
const resConcerts = await axios.get("data/concerts");
const tickets = await axios.get("/data/tickets");
};
});
return (
<div>
Booking page
<h1>{data.name}</h1>
</div>
);
};
UPDATE:
I wrapped my button in anchor tag and now i am able to redirect but still can't pass data.
Final Update
Allright, i managed to pass my data using useLocation hook.
Problem is solved.
I'd suggest using react-router to do the redirection or routing instead of anchor tags as they cause a refresh.
Use the Link tag from react-router and pass the concert state along with it!
Have a look at this https://reactrouter.com/en/main/components/link.

React input onChange not rerendering state when useState has default value

So this is working, input changes when I type.
const [editfield, setEdit_field] = React.useState();
function updateField(event) {
setEdit_field(event.target.value);
}
function editPost() {
setPostbody(<div><input onChange={updateField} value={editfield}></input></div>)
}
But when a put a default value in the useState it doesnt work anymore
const [editfield, setEdit_field] = React.useState(post.body);
function updateField(event) {
setEdit_field(event.target.value);
}
function editPost() {
setPostbody(<div><input onChange={updateField} value={editfield}></input></div>)
}
Access code here: https://codesandbox.io/s/brt7ok
You were setting JSX inside the state, that might be the issue, I have created a codesandbox demo which will help you in conditional Rendering
DEMO
You are rendering that function so when state will updated it re-run that function and resetting the value.
You can use below approach.
Take one state which represents the field is editable or not. And add condition with input component that it should only render when field is editable.
For example,
const [isEditable, setIsEditable] = useState(false);
<button onClick={() => setIsEditable(!isEditable)}>edit</button>
isEditable && (
<div>
<input onChange={updateField} value={editfield} />
</div>
)
For more idea, just put console before return. You will get idea.
Hope this helps :)
import React, { useState } from "react";
export default function App() {
let body = "hello";
const [editfield, setEditfield] = useState(body);
const [visible, setVisible] = useState(false);
function updateField(event) {
setEditfield(event.target.value);
}
function editPost() {
setVisible(true);
}
return (
<div>
<div>{visible?<div>
<input onChange={updateField} value={editfield}/>
</div>:body}</div>
<div>
<button onClick={editPost}>edit</button>
</div>
</div>
);
}

How can i make this reset button to reset this Questions?

I have an application with the parent component
Parent component
function App() {
const [text, setText] = useState(' ');
const outputRef = useRef();
const resetRef = useRef()
const resetBtn = ()=>{
// Reset the output field
setText('');
// Reset the button states
}
return (
<div className="resetIcon" onClick={resetBtn}>
<img src={resetIcon} alt="Reset icon for reset the game"
className='resetToggle' ref={resetRef}/>
</div>
<input type="text"
value={text}
className='display_text'
ref={outputRef}
readOnly
/>
<div className="flex">
<PaperLetter handleInnerText={handleInnerText}>teeth.</PaperLetter>
<PaperLetter handleInnerText={handleInnerText}>brush</PaperLetter>
<PaperLetter handleInnerText={handleInnerText}>my</PaperLetter>
<PaperLetter handleInnerText={handleInnerText}>i</PaperLetter>
</div>
Child Components
I want if I click on the reset button, then it should be able to change the clicked state triggering the addition and subtraction of the className
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import { useState,useRef } from 'react';
export default function PaperLetter(props) {
const [click, setClick] = useState(false);
const letterRef = useRef();
const btnClicked =()=>{
//Change the className of the btn click to active
setClick(true);
//Get the value of the letter btns click
const getValue = letterRef.current.innerText;
props.handleInnerText(getValue);
}
return (
<p className={click ? 'letter clicked': 'letter'} onClick={btnClicked} ref={letterRef} >{props.children}
</p>
)
}
I want to toggle the clicked state in the child component even though the resetBtn is in the parent component
Link to the app :
I solved it by doing this.
In the App component:
Use a new useState hook for saving the clicked words:
const [clickedWords, setClickedWords] = useState([]);
Create a new method for adding a single word:
const setClickedWord = clickedWord => setClickedWords(prevState => [...prevState, clickedWord]);
Pass clickedWords and setClickedWord as props to PaperLetter.
In the PaperLetter Component:
Get the new props: const { children, clickedWords, setClickedWord } = props;
Update the btnClicked method:
const btnClicked =()=>{
setClickedWord(children)
const getValue = letterRef.current.innerText;
props.handleInnerText(getValue);
}
Update the className:
className={clickedWords.includes(props.children) ? 'letter clicked': 'letter'}
Of course there are alternatives for this, you could use a Context, you could create an array of words with ids to prevent duplicated selected words, you may create a new Intermediate component to extract this from the App components itself, etc.

Re-Rendering a component

I'm doing a simple todo list using React. What I fail to do is to remove an item once I click on the button.
However, if I click delete and then add a new item, it's working, but only if I add a new todo.
Edit:I've edited the post and added the parent componenet of AddMission.
import React,{useState}from 'react';
import { Button } from '../UI/Button/Button';
import Card from '../UI/Card/Card';
import classes from '../toDo/AddMission.module.css'
const AddMission = (props) => {
const [done,setDone]=useState(true);
const doneHandler=(m)=>{
m.isDeleted=true;
}
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li className={mission.isDeleted?classes.done:''} key={mission.id}>
{mission.mission1}
<div className={classes.btn2}>
<Button onClick={()=>{
doneHandler(mission)
}} className={classes.btn}>Done</Button>
</div>
</li>
)) }
</ul>
</Card>
);
};
export default AddMission;
import './App.css';
import React,{useState} from 'react';
import { Mission } from './components/toDo/Mission';
import AddMission from './components/toDo/AddMission';
function App() {
const [mission,setMission]=useState([]);
const [isEmpty,setIsEmpty]=useState(true);
const addMissionHandler = (miss) =>{
setIsEmpty(false);
setMission((prevMission)=>{
return[
...prevMission,
{mission1:miss,isDeleted:false,id:Math.random().toString()},
];
});
};
return (
<div className="">
<div className="App">
<Mission onAddMission={addMissionHandler}/>
{isEmpty?<h1 className="header-title">Start Your Day!</h1>:(<AddMission isVisible={mission.isDeleted} missions={mission}/>)}
</div>
</div>
);
}
const doneHandler=(m)=>{
m.isDeleted=true;
}
This is what is causing your issue, you are mutating an object directly instead of moving this edit up into the parent. In react we don't directly mutate objects because it causes side-effects such as the issue you are having, a component should only re-render when its props change and in your case you aren't changing missions, you are only changing a single object you passed in to your handler.
Because you haven't included the code which is passing in the missions props, I can't give you a very specific solution, but you need to pass something like an onChange prop into <AddMission /> so that you can pass your edited mission back.
You will also need to change your function to something like this...
const doneHandler = (m) =>{
props.onChange({
...m,
isDeleted: true,
});
}
And in your parent component you'll then need to edit the missions variable so when it is passed back in a proper re-render is called with the changed data.
Like others have mentioned it is because you are not changing any state, react will only re-render once state has been modified.
Perhaps you could do something like the below and create an array that logs all of the ids of the done missions?
I'm suggesting that way as it looks like you are styling the list items to look done, rather than filtering them out before mapping.
import React, { useState } from "react";
import { Button } from "../UI/Button/Button";
import Card from "../UI/Card/Card";
import classes from "../toDo/AddMission.module.css";
const AddMission = (props) => {
const [doneMissions, setDoneMissions] = useState([]);
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li
className={
doneMissions.includes(mission.id)
? classes.done
: ""
}
key={mission.id}
>
{mission.mission1}
<div className={classes.btn2}>
<Button
onClick={() => {
setDoneMissions((prevState) => {
return [...prevState, mission.id];
});
}}
className={classes.btn}
>
Done
</Button>
</div>
</li>
))}
</ul>
</Card>
);
};
export default AddMission;
Hope that helps a bit!
m.isDeleted = true;
m is mutated, so React has no way of knowing that the state has changed.
Pass a function as a prop from the parent component that allows you to update the missions state.
<Button
onClick={() => {
props.deleteMission(mission.id);
}}
className={classes.btn}
>
Done
</Button>;
In the parent component:
const deleteMission = (missionId) => {
setMissions(prevMissions => prevMissions.map(mission => mission.id === missionId ? {...mission, isDeleted: true} : mission))
}
<AddMission missions={mission} deleteMission={deleteMission} />

Add functional component dynamical in react with out using class component

I am trying to make a list of skills that are supposed to build in UI and I didn't have any problem in this step.
But when I add a new item to the list by writing it in input and clicking on a button to add it, the console sends a massage to tell: skills is not iterable.
this is a code in the App.js file:
import './App.css';
import Skills from './Components/Details'
import React,{useState} from 'react';
function App() {
const [numApp,reNumApp] = useState(0)
const [skill,reSkill] = useState('')
const [skills,reSkills] = useState(['HTML'])
const addSkill = (value)=>{
let newList = skills.push(value);
reSkills(newList);
console.log(skills);
}
console.log(skills)
return(
<>
<h1>Num Of applecation:{numApp}</h1>
<button onClick={()=>reNumApp(numApp + 1)}>Other Application</button>
<hr/>
<input type='text' value={skill} onChange={(e)=>reSkill(e.target.value)}/>
<button onClick={()=>{addSkill(skill)}}>Add</button>
<Skills skills = {skills}/>
</>
)
}
export default App;
And this is the code in the Details file:
import React,{useState,Fragment} from 'react';
function Skill({key,value})
{
const [count,reCount] = useState(0)
return(
<>
<span><p key={key}>{value} : {count}</p></span>
<button onClick={()=>reCount(count + 1)}>+</button>
</>
)
}
function Skills({skills}) {
return(
<Fragment>
{
[...skills].map((v,i)=>{return <Skill key={i} value={v}/>})
}
</Fragment>
)
}
export default Skills
I need to know how can I solve this problem without using the class component.
Try not to push on the current state, so change this:
let newList = skills.push(value);
reSkills(newList);
to this:
reSkills([...skills, value]);

Resources