In Summary: {
How can I merge those 2 arrays into one. As in, instead of having it like this:
[1st,new1st,...] [2nd,new2nd,...]
I want it to be like this:
[1st,2nd,new1st,new2nd,...]
}
I have this note app that I am creating. I am trying to render the two components so that each note rendered is kind of the last element of an array. So, in short, I want each component to be below the previous added note (think of it like a list where each added input is added after the previous list items).
So, this is how it looks before adding anything.
and this is how it looks after adding one note on each create area.
and this is what I am trying to avoid after adding the new notes from each note create area.
What I want is
-1st -2nd -new1st - new2nd
As in no matter which create area I use, it gets rendered after all the previous ones.
Here's my code
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [notes, setNotes] = useState([]);
const [notes2, setNotes2] = useState([]);
function addNote(newNote) {
setNotes(prevNotes => {
return [...prevNotes, newNote];
});
}
function addNote2(newNote) {
setNotes2(prevNotes => {
return [...prevNotes, newNote];
});
}
function deleteNote(id) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem, index) => {
return index !== id;
});
});
}
function deleteNote2(id) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem, index) => {
return index !== id;
});
});
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<CreateArea onAdd={addNote2} />
{notes.map((noteItem, index1) => {
return (
<Note
key={index1}
id={index1}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
{notes2.map((noteItem, index2) => {
return (
<Note
key={index2}
id={index2}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote2}
/>
);
})}
<Footer />
</div>
);
}
export default App;
You can test the app by copying the above code instead of App.jsx at CodeSandbox.
I need to do something like that:
-item1
=nested item 1
=nested item 2
=nested item 3
-item 2
so I need the second create area to eventually be used for nested items (children). and the 1st create area to be for 'item1' or 'item2' or ... (parent). But with the way it functions from my code, it gets rendered like that:
-item1
-item2
=nested item 1
=nested item 2
=nested item 3
I don't understand a reason why you would want to do that. You need to either have one list or two. If for rendering, you want it to be one list, you can have that in a single state. Also if it's just about having two input fields to add note, both fields can push to same state. Here is how it could be:
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [notes, setNotes] = useState([]);
function addNote(newNote) {
setNotes(prevNotes => {
return [...prevNotes, newNote];
});
}
function deleteNote(id) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem, index) => {
return index !== id;
});
});
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<CreateArea onAdd={addNote} />
{notes.map((noteItem, index1) => {
return (
<Note
key={index1}
id={index1}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
<Footer />
</div>
);
}
export default App;
Well, if you still want it :D then here is a thing you can do:
import React, { useState } from "react";
import Header from "./Header";
import Footer from "./Footer";
import Note from "./Note";
import CreateArea from "./CreateArea";
function App() {
const [notes, setNotes] = useState([]);
const [notes2, setNotes2] = useState([]);
const [combinedNotes, setCombinedNotes] = useState([]);
useEffect(() => {
const notesList = [...notes, ...notes2].sort((note1, note2) => note1.timestamp - note2.timestamp);
setCombinedNotes(notesList);
}, [notes, notes2]);
function addNote(newNote) {
setNotes(prevNotes => {
return [...prevNotes, { ...newNote, timestamp: new Date().getTime() }];
});
}
function addNote2(newNote) {
setNotes2(prevNotes => {
return [...prevNotes, { ...newNote, timestamp: new Date().getTime() }];
});
}
function deleteNote(id) {
const isFirstNote = notes.find((note) => note.timestamp === id);
if (isFirstNote) {
setNotes(prevNotes => {
return prevNotes.filter((noteItem) => {
return noteItem.timestamp !== id;
});
});
} else {
setNotes2(prevNotes => {
return prevNotes.filter((noteItem) => {
return noteItem.timestamp !== id;
});
});
}
}
return (
<div>
<Header />
<CreateArea onAdd={addNote} />
<CreateArea onAdd={addNote2} />
{combinedNotes((noteItem, index) => {
return (
<Note
key={index}
id={noteItem.timestamp}
title={noteItem.title}
content={noteItem.content}
onDelete={deleteNote}
/>
);
})}
<Footer />
</div>
);
}
export default App;
Related
I already added the Id property on the array of objects from the API, but when I added oclick method and tried to console the id of a single element all elements inside the array take the same ID, so how to give every element inside the Array of object it's unique id when I click the element.
here is my code and screenshots from the console
app.js
import React from "react";
import Question from "./components/Question";
import { useState, useEffect } from "react";
import { nanoid } from "nanoid";
export default function App() {
const [data, setData] = useState([]);
const fetchData = () => {
fetch("https://opentdb.com/api.php?amount=1&type=multiple")
.then((response) => response.json())
.then((json) => {
let allTasks = json.results;
allTasks = allTasks.map((currentTask) => {
const id = nanoid();
return { ...currentTask, isHeld: false, id: id };
});
setData(allTasks);
});
};
function hold(id) {
console.log(id);
}
useEffect(() => {
fetchData();
}, []);
useEffect(() => {
console.log(data);
}, [data]);
const Elements = data.map((quiz, index) => {
return (
<Question
key={quiz.id}
question={quiz.question}
correct_answer={quiz.correct_answer}
incorrect_answers={quiz.incorrect_answers}
hold={() => hold(quiz.id)}
isHeld={quiz.isHeld}
/>
);
});
return (
<div className="app">
<div className="allnow">{Elements}</div>
</div>
);
}
Question.jsx
import React from "react";
export default function Question(props) {
return (
<div className="question">
<h2 className="question-title"> {props.question} </h2>
<div className="wrong-answers">
<button className="correct-answer" onClick={props.hold}>
{props.correct_answer}
</button>
<button className="incorrect-answer" onClick={props.hold}>
{props.incorrect_answers}
</button>
<button className="incorrect-answer" onClick={props.hold}>
{props.incorrect_answers}
</button>
</div>
</div>
);
}
The key appears to be also using map to iterate over the incorrect_answers. When you do that you can call nanoid (I've had to use a random number generator - rnd() - here because nanoid wouldn't work in the snippet), and then apply that id to each answer.
Note 1) I've used data attributes rather than applying the random id to an HTML id - personal preference. You can access them through the element's dataset property.
Note 2) I have swapped things around a little just to understand what you wrote, but the important code is in the Question component, and in the hold function.
const { useEffect, useState } = React;
function rnd() {
return Math.floor(Math.random() * 1000);
}
function App() {
const [data, setData] = useState([]);
function addIds(data) {
return data.map((currentTask) => {
const id = rnd();
return { id, ...currentTask, isHeld: false };
});
}
function fetchData(endpoint) {
fetch(endpoint)
.then(response => response.json())
.then(data => {
const tasks = addIds(data.results);
setData(tasks);
});
};
useEffect(() => {
const endpoint = 'https://opentdb.com/api.php?amount=1&type=multiple';
fetchData(endpoint);
}, []);
function hold(e) {
console.log(e.target.dataset.id);
}
function buildElements(data) {
return data.map(quiz => {
const {
question,
correct_answer,
incorrect_answers,
isHeld
} = quiz;
return (
<Question
question={question}
correct_answer={correct_answer}
incorrect_answers={incorrect_answers}
hold={hold}
isHeld={isHeld}
/>
);
});
}
if (!data.length) return <div />;
return (
<div className="app">
{buildElements(data)}
</div>
);
}
function Question(props) {
const {
hold,
question,
correct_answer,
incorrect_answers,
isHeld
} = props;
return (
<div>
<h2 className="question-title">{question}</h2>
<button
data-id={rnd()}
className="correct-answer"
onClick={hold}
>{correct_answer}
</button>
<div className="wrong-answers">
{incorrect_answers.map(ia => {
const id = rnd();
return (
<button
key={id}
data-id={id}
className="incorrect-answer"
onClick={hold}
>{ia}
</button>
);
})}
</div>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I am a beginner in react js programming. I'm trying to do the todo project, which is a classic project. When I delete or add an element from the list, the newly formed list appears on the screen by combining with the previous one, I will show it with a picture below. I did not understand the source of the eror so wanted to post it here to get some advices suggestions about why it is happening.Thank you.(I am getting and storing data in firebase firestore database)
Before Adding an element initial array state
After adding an element to the array.
I am using useState for array and using useEffect to get initial data
MainPage.js that contains form and the list components.
const MainPage = () => {
const [isLoading, setLoding] = useState(true);
const [array, setArray] = useState([]);
const sub = async (email) => {
var result = [];
await onSnapshot(doc(db, "users", email), (doc) => {
var data = doc.data().todos;
data.forEach((element) => {
Object.keys(element).map(() => {
result.push(element["title"]);
});
});
setArray(result);
setLoding(false);
});
};
useEffect(() => {
sub(auth.currentUser.email);
}, []);
const onAddToDo = (todoTitle) => {
setArray((prevAray) => {
return [...prevAray, todoTitle];
});
};
const onRemove = (title) => {
setArray((prevAray) => {
return [array.pop(array.indexOf(title))];
});
};
return (
<div>
{isLoading && <h1>Loading</h1>}
{!isLoading && (
<div>
<section>
<NavBar></NavBar>
<ToDoForm passData={onAddToDo} />
</section>
<section>
<CardList removeCards={onRemove} array={array} />
</section>
</div>
)}
</div>
);
};
export default MainPage;
Firebase.js that stores the firebase update methods
export const deleteItem = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayRemove({ title: title }),
});
};
export const addnewTodo = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayUnion({ title: title }),
});
};
TodoForm.js component
const ToDoForm = (props) => {
const [todoTitle, setTitle] = useState("");
const titleChangeHandler = (event) => {
setTitle(event.target.value);
};
const newTodoAdder = (event) => {
event.preventDefault();
addnewTodo(todoTitle);
props.passData(todoTitle);
};
return (
<div className="form_holder">
<div className="form_container">
<form onSubmit={newTodoAdder}>
<h3>Add Events</h3>
<label>Title</label>
<input
onChange={titleChangeHandler}
type="text"
placeholder="Title"
id="title"
></input>
<div className="holder">
<button type="sumbit">Add</button>
</div>
</form>
</div>
</div>
);
};
export default ToDoForm;
CardList.js component
const CardList = (props) => {
const array = props.array;
if (array.length === 0) {
return (
<div className="grid_container">
<h2>Found no todos</h2>
</div>
);
}
return (
<div className="grid_container">
{array.map((element, index) => {
return (
<Card
removeSelf={() => {
props.removeCards(element);
}}
key={index}
title={element}
/>
);
})}
</div>
);
};
export default CardList;
Card.js component
const Card = (props) => {
const handleRemove = (event) => {
event.preventDefault();
deleteItem(props.title);
props.removeSelf();
};
return (
<div className="card">
<h2 className="card__title">{props.title}</h2>
<button type="button" onClick={handleRemove}>
Delete
</button>
</div>
);
};
export default Card;
EDIT ;
Index.js file
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
SOLUTION
I fixed the issue by changing the add and remove functions that were inside of MainPage.js file You can see the new versions bellow. Hope someday it will help somebody.
Use effect was called once all I had to do get the data again after a change...
New Remove and Add functions
const onAddToDo = (todoTitle) => {
console.log(todoTitle + " Added");
sub(auth.currentUser.email);
};
const onRemove = (title) => {
console.log(title + " Deleted");
sub(auth.currentUser.email);
};
I have connected to an api and have pulled some data into my project with the name of 'data'. This data is being rendered dynamically into a card component. I am now trying to arrange the order from highest price to lowest price on the click of a button with useState but cannot figure it out. Below is what i have so far:
import React, { useState } from "react";
import "./App.scss";
import { useQuery } from "#apollo/react-hooks";
import GET_PRODUCTS_IN_COLLECTION from "./gql/getCollection";
import ProductCard from "./components/ProductCard/ProductCard";
const App = (props) => {
const { data, loading, error } = useQuery(GET_PRODUCTS_IN_COLLECTION, {
variables: {
count: 10,
handle: "skateboard",
},
});
// console.log(data)
const [reversed, setReversed] = useState(false);
const [highLow, setHighLow] = useState(false);
const [lowHigh, setLowHigh] = useState(false);
const [remove, setRemove] = useState(false);
const reverseOrder = () => {
setReversed(!reversed);
};
const highToLow = () => {
setHighLow(!highLow);
};
const lowToHigh = () => {
setLowHigh(!lowHigh);
};
const removeLast = () => {
setRemove(!remove);
};
if (loading) {
// Data is still loading....
return <div className="App">Loading....</div>;
}
return (
<div className="App">
<header className="App-header"></header>
<main>
<div className="buttonGroup">
<button onClick={reverseOrder}>Reverse Product Order</button>
<button onClick={highToLow}>Price High to Low</button>
<button onClick={lowToHigh}>Price Low to High</button>
<button onClick={removeLast}>Display 9 products</button>
</div>
{/*
Your render components go here
*/}
<div className="ProductList">
{reversed
? data.collectionByHandle.products.edges
.slice()
.reverse()
.map((product) => {
return <ProductCard productData={product} />;
})
: highLow
? data.collectionByHandle.products.edges
.slice()
.sort((a,b) => (a.node.vendor - b.node.vendor))
.map((product) => {
return <ProductCard productData={product} />;
})
: lowHigh
? data.collectionByHandle.products.edges
.slice()
.map((product) => {
return <ProductCard productData={product} />;
})
.splice(1)
: remove
? data.collectionByHandle.products.edges
.slice()
.map((product) => {
return <ProductCard productData={product} />;
})
.splice(1)
: data.collectionByHandle.products.edges.map((product) => {
return <ProductCard productData={product} />;
})}
</div>
</main>
</div>
);
};
export default App;
image of array
You can change your code like the following example:
Some points to keep in mind :
Try to avoid if statment in JSX .
Put your events in seprated functions to make it easy for you to manage .
import React, { useState ,useEffect} from "react";
import "./App.scss";
import { useQuery } from "#apollo/react-hooks";
import GET_PRODUCTS_IN_COLLECTION from "./gql/getCollection";
import ProductCard from "./components/ProductCard/ProductCard";
const App = (props) => {
const [myData, setMyData] = useState(data);
useEffect (() => {
const { data, loading, error } = useQuery(GET_PRODUCTS_IN_COLLECTION,
{
variables: {
count: 10,
handle: "skateboard",
},
});
setMyData(data);
},[]);
const reverseOrder = () => {
let newData = myData.reverse();
setMyData([...newData]);
};
const highToLow = () => {
let newData = myData.sort((a, b) => b.node.vendor- a.node.vendor);
setMyData([...newData]);
};
const lowToHigh = () => {
let newData = myData.sort((a, b) => a.node.vendor- b.node.vendor);
setMyData([...newData]);
};
const removeLast = () => {
myData.splice(-1, 1);
setMyData([...myData]);
};
if (loading) {
// Data is still loading....
return <div className="App">Loading....</div>;
}
return (
<div className="App">
<header className="App-header"></header>
<main>
<div className="buttonGroup">
<button onClick={reverseOrder}>Reverse Product Order</button>
<button onClick={highToLow}>Price High to Low</button>
<button onClick={lowToHigh}>Price Low to High</button>
<button onClick={removeLast}>Display 9 products</button>
</div>
{
myData.map((product) => {
return <ProductCard productData={product} />;
});
}
</div>
</main>
</div>
);
};
export default App;
Assuming the values are alphanumerical javascript has built in function "sort" to do that. Even if they are not numerical there has to be a way to read their value that you can use!
Then its pretty straight forward (modified from w3schools):
const fruits = [2,1,"Banana", "Orange", "Apple", "Mango"];
fruits.sort();
will create array [1,2,Apple,Banana,Mango,Orange]
You should be able to do something along these lines in your program.
(just droping: if you want to reverse the order simply use reverse() method on array)
I don't know what your data looks like but this should work.
https://www.w3schools.com/js/js_array_sort.asp
I am trying to toggle view between list of meals and meal details. I have placed a button in the child component Meal.js to the Meals.js which is meant to be the list and the details view.
Can you please help me fix this issue. Seems like its not working even with the conditional rendering method I've used in the code below.
Meal.js
import { useState } from 'react'
import './Meal.css'
const Meal = (props) => {
const [isToggled, setIsToggled] = useState(false);
const sendIdHandler = () => {
if (isToggled === true) {
setIsToggled(false);
}
else {
setIsToggled(true);
}
props.onSaveIdHandler(props.id, isToggled)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Meals.js
import Meal from './Meal/Meal'
const Meals = (props) => {
let toggleCondition = false;
const saveIdHandler = (data, isToggled) => {
toggleCondition = isToggled;
const mealDetails = props.mealsMenuData.findIndex(i =>
i.id === data
)
console.log(mealDetails, toggleCondition)
}
return (
<div>
{toggleCondition === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{toggleCondition === true &&
<div>Horray!</div>
}
</div>
);
}
export default Meals;
UPDATE
Finally figured how to do this properly. I put the condition true/false useState in the parent instead and have Meal.js only send the id I need to view the item
Code is below..
Meals.js
import { useState } from 'react'
import Meal from './Meal/Meal'
import MealDetails from './MealDetails/MealDetails'
const Meals = (props) => {
const [show, setShow] = useState(false);
const [mealId, setMealId] = useState(0);
const saveIdHandler = (data) => {
setShow(true);
setMealId(props.mealsMenuData.findIndex(i =>
i.id === data)
)
console.log(props.mealsMenuData[mealId].ingridients)
}
const backHandler = () => {
setShow(false)
}
return (
<div>
{show === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{show === true &&
<div>
<MealDetails data={props.mealsMenuData[mealId]} />
<button onClick={backHandler}>Back</button>
</div>
}
</div>
);
}
export default Meals;
Meal.js
import './Meal.css'
const Meal = (props) => {
const sendIdHandler = () => {
props.onSaveIdHandler(props.id)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Your problem in sendIdHandler: You can update like this:
const sendIdHandler = () => {
const newIsToggled = !isToggled;
setIsToggled(newIsToggled)
props.onSaveIdHandler(props.id, newIsToggled)
}
I made My code.
When Click the button A, appear AAA.
Or Click the button B, appear BBB, Click the button C, appear CCC.
// Main > RightMain.js
import React, { useState } from 'react'
function RightMain() {
const [screen, setScreen] = useState('');
const A = () => {
setScreen('A')
}
const B = () => {
setScreen('B')
}
const C = () => {
setScreen('C')
}
return (
<div>
<button onClick={A}>A</button>
<button onClick={B}>B</button>
<button onClick={C}>C</button>
{screen === 'A' && <div>AAA</div>}
{screen === 'B' && <div>BBB</div>}
{screen === 'C' && <div>CCC</div>}
</div>
)
}
export default RightMain
And I wanna separate My Code(RightMain.js).
When I Click the Button on the RightMain.js.
The Result appear's on the Formations.js like the image below.
But I don kno how to bring value(RightMain.js's screen) to the Formations.js.
// Main > LeftMain.js
import React from 'react'
import RadioBtn from './LeftMain/RadioBtn';
import Formation from './LeftMain/Formation';
function LeftMain() {
return (
<div>
<div>
<RadioBtn />
</div>
<div>
<Formation />
</div>
</div>
)
}
export default LeftMain
//Main > LeftMain > Formation.js
import React, { useState } from 'react'
import RightMain from '../RightMain';
function Formation() {
return (
<div>
</div>
)
}
export default Formation
Thx
If I understand correctly, LeftMain and RightMain are sibilings, and Formation is a child of LeftMain.
One possible approach is to use Context API.
Something like this should work:
// Define the default value
// or return null and take that into consideration when using "useContext"
export const MyCurrentScreenContext = React.createContext({
setScreen: () => void 0,
screen: ''
});
export const MyCurrentScreenProvider = props => {
const [screen, setScreen] = useState('');
const value = useMemo(() => ({ screen, setScreen }), [screen, setScreen]);
return (
<MyCurrentScreenContext.Provider value={value}>
{props.children}
</MyCurrentScreenContext.Provider>
);
}
const Main = () => {
...
return (
<MyCurrentScreenProvider>
<LeftMain />
<RightMain />
...
</MyCurrentScreenProvider>
);
}
const RightMain() {
const { setScreen } = useContext(MyCurrentScreenContext);
....
};
const Formation() {
const { screen } = useContext(MyCurrentScreenContext);
....
};
Read more about context api at the official docs
From what I understand, you want to pass the values down to the child components. If that is correct then you could pass them as parameters when calling it and using props to receive them inside the child component. Something like this.
<div>
<RadioBtn randomVal="value" />
</div>