I am working with 2 APIs. The data of the Api's is almost identical except for the name of the theatre and the price to see a movie. I'm currently mapping through and sending the props to my MovieComponent, but the issue is that the prop names are the same and I need to get another value for the {Price} for one cinema. Currently it is providing data from filmWorld and I need to display the cinemaWorld {Price} also. I'm wondering how to separate these two props so I can get the data on cinemaWorld Price.
I can access this data from App.js by logging cinemaWorld.Movies[0].Price (Although I'll need to map through to get different Prices depending on the movie).
App.js:
function App() {
const [filmWorld, setFilmWorld] = useState([]);
const [cinemaWorld, setCinemaWorld] = useState([]);
useEffect(() => {
const theaters = ["cinema", "film"];
theaters.forEach((theater) => {
async function getTheater() {
const data = await api.getMoviesData(theater);
if (data.Provider === "Film World") {
setFilmWorld(data);
} else {
setCinemaWorld(data);
}
}
getTheater();
});
}, []);
const movies = useMemo(() => {
if (!filmWorld.Provider) return [];
return filmWorld.Movies.map((movie, index) => ({
...movie,
cinemaWorldPrice:
cinemaWorld.Provider && cinemaWorld.Movies[index]?.Price,
}));
}, [filmWorld, cinemaWorld]);
return (
<Container>
<Header>
<AppName>
<MovieImage src={movieicon1} />
Prince's Theatre
</AppName>
</Header>
<MovieListContainer>
{movies.map((movie, index) => (
<MovieComponent key={index} movie={movie} />
))}
</MovieListContainer>
</Container>
);
}
and my movieComponent:
const MovieComponent = (props) => {
const { Poster, Price, Title } = props.movie;
return (
<MovieContainer>
<CoverImage src={Poster} alt="" />
<MovieName>{Title}</MovieName>
<InfoColumn>
<MovieInfo>FilmWorld: ${Price}</MovieInfo>
<MovieInfo>CinemaWorld: ${Price}</MovieInfo>
</InfoColumn>
</MovieContainer>
);
};
Related
I have 2 components, the Favorites component, makes a request to the api and maps the data to Card.
I also have a BtnFav button, which receives an individual item, and renders a full or empty heart according to a boolean.
Clicking on the BtnFav render removes a certain item from the favorites database.
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The data flow for now would be something like this:
Favorites component fetches all the complete data and passes it to the Card component, the Card component passes individual data to the BtnFavs component.
Favorites Component:
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>Mis favoritos</h1>
<Card listWines={vinosFavs} />
</div>
);
BtnFavs:
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
What I need is that in the Favorites component, when I click on the BtnFavs component, the useEffect of Favorites is triggered again to bring the updated favorites.
How can i solve this? I have partially solved it with a global context(favoritesUser), but is there any other neater alternative?
The pattern you want is called a callback function, just like the onClick of a button. You pass a function to your components that get executed given a condition. If you want fetchWines to be called again, then just pass the function in as a prop.
Favorites Component:
<Card listWines={vinosFavs} refresh={fetchWines} />
Card Component
<BtnFavs onDelete={refresh} ... />
BtnFavs Component
onDelete();
You can name it whatever you want, but generally callbacks will be named like on<condition>.
If you really wanted useEffect to be triggered then you would pass a setState function that set one of the dependencies, but I don't see a point in this case.
I will share code, because this problem its normal for me, i really want to learn and improve that.
const Favorites = () => {
const { favoritesUser } = useFavoritesContext();
const user = useSelector((state) => state.user);
const id = user.id;
const [vinosFavs, setVinosFavs] = useState([]);
const fetchWines = async () => {
try {
const vinos = await axios.get(`/api/favoritos/${id}`);
const arrVinos = vinos.data.map((vino) => {
return vino.product;
});
setVinosFavs(arrVinos);
} catch (err) {
console.error(err);
}
};
useEffect(() => {
fetchWines();
}, [favoritesUser]);
return (
<div>
<h1>My favorits</h1>
<Grid listVinos={vinosFavs} />
</div>
);
};
export default Favorites
Grid
export default function Grid({ listVinos }) {
return (
<div>
<ul className={styles.layoutDeVinos}>
{listVinos?.map((element) => {
return <WineCard key={element.id} vino={element} />;
})}
</ul>
</div>
);
}
Card
export default function WineCard({ vino }) {
return (
<>
<div>
<Link to={`/products/${vino.id}`}>
<li>
<div className={styles.card}>
<div
className={styles.img1}
style={{
backgroundImage: `url(${vino.images})`,
}}
></div>
<div className={styles.text}>{vino.descripcion}</div>
<div className={styles.catagory}>
{vino.nombre}
<i className="fas fa-film"></i>
</div>
<div className={styles.views}>
{vino.bodega}
<i className="far fa-eye"></i>{" "}
</div>
</div>
</li>
</Link>
<div className="botonesUsuario">
<BtnFavs vino={vino} />
</div>
</div>
</>
);
}
BTN FAVS
export default function BtnFavs({ vino }) {
const { setFavoritesUser } = useFavoritesContext();
const [boton, setBoton] = useState(false);
const user = useSelector((state) => state.user);
const userId = user.id;
const productId = vino.id;
useEffect(() => {
axios
.post("/api/favoritos/verify", { userId, productId })
.then((bool) => setBoton(bool.data));
}, []);
const handleClickFav = (e) => {
if (!boton) {
axios.post("/api/favoritos/add", { userId, productId }).then((data) => {
setBoton(true);
return;
});
}
axios.put("/api/favoritos/delete ", { userId, productId }).then((data) => {
setBoton(false);
setFavoritesUser(data);
});
};
return (
<>
{!user.id ? (
<div></div>
) : boton ? (
<span
class="favIcons material-symbols-rounded"
onClick={handleClickFav}
>
favorite
</span>
) : (
<span className="material-symbols-rounded" onClick={handleClickFav}>
favorite
</span>
)}
</>
);
}
I have got a parent file and 2 child files,
the Parent file(App.js). it contains the showGraph state.
function App() {
const [data, setData] = useState([]);
const [showGraph, setShowGraph] = useState(false);
return (
<div className="App">
<Search setData={setData} setShowGraph={setShowGraph}/>
<Api data={data} showGraph={showGraph}/>
the Api.js file. It contains the chart, which I wanna show on search Item click.
const Api = ({ data: records }, { showGraph }) => {return (
<>
{showGraph? (
<div>
<div className="show">
<Areachart width="50%" height={400}>
...
</Areachart>
</div>
</div>
) : (
false
)}
</>
);
};
And The Search.js file. It's the search file where the searched data shown, and by clicking them, the state should change.
const Search = ({ setData }, { setShowGraph }) => {
searchTerm.length !== 0 && (
<div className="dataResult">
{searchTerm.map((record, key) => {
return (
<h3
key={key}
onClick={() => {
setShowGraph(true)
}}
>
{record.symbol}
</h3>
);}
So, the thing is, I want to implement that, Initially there will be no chart displayed. But whenever I click on the searced Item, the chart should display. what have I done wrong?
You are destructuring the props wrong.
The
const Api = ({ data: records }, { showGraph }) => {return (
should be
const Api = ({ data: records, showGraph }) => {return (
And the
const Search = ({ setData }, { setShowGraph }) => {
should be
const Search = ({ setData, setShowGraph }) => {
I've a problem with my react app.The navbar worked correctly before the ItemDetailContainer, and i want to show in the DOM an specific product. When adding the ItemDetailContainer to app.js, the page doesn't show anything (including the navbar) . Here's my code:
ItemDetailContainer.jsx:
const {products} = require('../utils/data');
const ItemDetailContainer = () => {
const [arrayList, SetArrayList] = useState({});
useEffect(() => {
customFetch(2000, products[0])
.then(result => SetArrayList(result))
.catch(err => console.log(err))
}, [])
return (
<div>
<ItemDetail products={arrayList}/>
</div>
);
}
here's my array in data.js that i want to access:
const products = [
{
id: 1,
image: "https://baltimore.com.ar/img/articulos/4188.png",
title: "Vino Abras Malbec 750cc",
price: 2.500,
stock: 8,
initial: 1
}
ItemDetail.jsx:
const ItemDetail = ({product}) => {
return(
<>
{product.image}
{product.title}
</>
)
}
CustomFetch.js:
let is_ok = true
let customFetch = (time, array) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (is_ok) {
resolve(array)
} else {
reject("error")
}
}, time)
})
}
and my app.js:
function App() {
return (
<div>
<Navbar/>
<ItemDetailContainer/>
</div>
);
}
I've done all the imports and exports, but I dont' know what's the problem.
Since you are passing a product prop in <ItemDetail product={arrayList}/> within ItemDetailContainer make sure you destructure your props correctly in ItemDetail component,like so :
const ItemDetail = ({product}) => {
return(
<>
{product.image}
{product.title}
</>
)
}
Which is basically like this :
const ItemDetail = (props) => {
return (
<>
{props.product.image}
{props.product.title}
</>
);
};
check it out in code sandbox
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 have a list of users on the page.
Each student has an input filed where user can add tags to their profile. There's a search bar on top of the all the students, searchStudentByTags. I am trying to implement this function, but have not been able to solve it yet. Any help would be appreciated.
This is the StudentContainer component where has the searchStudnetByTags function I write so far but not working
import React, { useState, useMemo } from "react";
import Student from "./Student";
import Input from "./Input";
import "../stylesheets/StudentsContainer.scss";
const StudentsContainer = ({ students }) => {
const [searchByName, setSearchByName] = useState("");
const [searchByTags, setSearchByTags] = useState("");
const filteredStudents = useMemo(
() =>
students.filter(
({ firstName, lastName }) =>
searchByName.length < 2 ||
(firstName + " " + lastName)
.toLowerCase()
.includes(searchByName.toLowerCase())
),
[students, searchByName]
);
const renderStudentsByTagSearch = ({ target }) => {
setSearchByTags(target.value);
const studentsContainer = document.querySelector(".students-container");
const allStudents = studentsContainer.getElementsByClassName("student");
const nameTags = document.querySelectorAll(".tag");
for (let i = 0; i < allStudents.length; i++) {
const student = allStudents[i];
const tag = nameTags[i];
if (
searchByTags.length > 1 &&
student.contains(tag) &&
tag.innerHTML.includes(searchByTags)
) {
student.style.display = "";
} else if (
searchByTags.length > 1 &&
student.contains(tag) &&
!tag.innerHTML.includes(searchByTags)
) {
student.style.display = "none";
} else if (searchByTags.length > 1 && !student.contains(tag)) {
student.style.display = "none";
} else if (searchByTags.length === 0 || !student.contains(tag)) {
student.style.display = "";
}
}
};
return (
<section className="students-container">
<Input
value={searchByName}
placeholder="Search by name"
onChange={({ target }) => setSearchByName(target.value)}
/>
<Input
className="tag-input"
value={searchByTags}
placeholder="Search by tag"
onChange={renderStudentsByTagSearch}
/>
{filteredStudents.map((student) => (
<Student
key={student.id}
student={student}
/>
))}
</section>
);
};
export default StudentsContainer;
This is the Student component
import React, { useState } from "react";
import "../stylesheets/Student.scss";
import AddTag from "./AddTag";
const Student = ({ student, addTagClick }) => {
const averageGrade =
student.grades.reduce((acc, grade) => {
return parseInt(acc) + parseInt(grade);
}) / student.grades.length;
const [isViewScores, setIsViewScores] = useState(false);
const viewScoreClick = () => {
setIsViewScores((prev) => !prev);
};
return (
<article className="student">
<figure>
<img src={student.pic} alt="student" />
</figure>
<aside>
<h2>
{student.firstName} {student.lastName}
</h2>
<ul>
<li>Email: {student.email}</li>
<li>Company: {student.company}</li>
<li>Skill: {student.skill}</li>
<li>
Average: {averageGrade}%
{isViewScores && (
<ul className="scores">
{student.grades.map((grade, index) => {
return (
<li key={index}>
Test {index + 1}: {grade}%
</li>
);
})}
</ul>
)}
</li>
</ul>
<AddTag studentId={student.id} addTagClick={addTagClick}/>
</aside>
<button onClick={viewScoreClick} className="view-scores-btn">
{isViewScores ? "-" : "+"}
</button>
</article>
);
};
export default Student;
This is the AddTag component
import React, { useState } from "react";
import { generateId } from "../helper";
import Input from "./Input";
const AddTag = ({ studentId }) => {
const [tag, setTag] = useState("");
const [tags, setTags] = useState([]);
const handleInputChange = ({ target }) => {
setTag(target.value);
};
const onSubmitClick = (e) => {
e.preventDefault();
const newTag = {
tag: tag,
id: generateId(),
studentId: studentId,
};
setTags((prev) => {
if (tag) {
return [newTag, ...prev];
} else {
return [...prev];
}
});
setTag("");
};
return (
<>
<div className="tags-container">
{tags.map((tag) => (
<button className="tag" key={tag.id}>
{tag.tag}
</button>
))}
</div>
<form onSubmit={onSubmitClick}>
<Input
className="add-tag-input"
placeholder="Add a tag"
type="text"
value={tag}
onChange={handleInputChange}
/>
</form>
</>
);
};
export default AddTag;
You need to approach this differently.. where the array of tags are available at the top level component - rather than doing DOM manipulation. Move
const [tags, setTags] = useState([]);
Into the StudentsContainer, and pass it down through Students and Add Tag as props, then refactor your search to use tags.
I've added a code sandbox here, with a basic gist of how I'd approach it.
https://codesandbox.io/s/frosty-ishizaka-hui8j
Theres quite a bit going in this question so we should focus on simplifying the problem by removing everything that is of no concern.
So how do we only render those students who have the tag that we currently are searching for? By using Array.prototype.filter() before we map over students and return a <Student /> for each array item.
import React, { useState } from "react";
const data = [
{id:1,firstName:"Mickey",lastName:"Mouse",tags:[{id:1,label:"mouse"}]},
{id:2,firstName:"Donald",lastName:"Duck",tags:[{id:1,label:"duck"}]},
{id:3,firstName:"Minnie",lastName:"Mouse",tags:[{id:1,label:"mouse"},{id:2,label:"cool"}]}
];
const StudentsContainer = ({ students = data }) => {
const [searchByTagsValue, setSearchByTagsValue] = useState("");
return (
<>
<input
value={searchByTagsValue}
placeholder="Search by tag"
onChange={(e) => setSearchByTagsValue(e.target.value)}
/>
{students.length &&
students
.filter((student) => shouldStudentDisplay(student.tags, searchByTagsValue))
.map((student) => <Student key={student.id} student={student} />)}
</>
);
};
const Student = ({ student, style }) => (
<div style={style}>
<h5>
{student.firstName} {student.lastName}
</h5>
<Tags tags={student.tags} />
<hr />
</div>
);
const Tags = ({ tags }) => (
<ul>
{tags.map((tag) => (
<li key={tag.id}>{tag.label}</li>
))}
</ul>
);
const shouldStudentDisplay = (tags, searchByTagsValue) => {
if (!searchByTagsValue) {
return true;
}
return tags.findIndex(({ label }) => label === searchByTagsValue) !== -1;
};
export default StudentsContainer;
Once you can filter your data in place like above, you need an updater function in StudentsContainer that will take a student id, and a new tag name, and update (a localised version of) the students data.
Pass this updater function all the way from StudentsContainer down to Tags so it can update the data in the ancestor component (commonly referred to as prop drilling).
const [localStudents, setLocalStudents] = useState(students);
const onSubmitTag = (label, id) => {
const index = localStudents.findIndex((student) => student.id === id);
if (index !== -1) {
const newStudents = [...localStudents];
newStudents[index] = {
...newStudents[index],
tags: [...newStudents[index].tags, { id: Date.now(), label }]
};
setLocalStudents(newStudents);
}
};
As you can see, we aren't really searching through the HTML to hide and show things in an imperative way.
In react, we are encouraged to update the source data, and allow the rendered UI to react in a declarative way.
React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.
Declarative views make your code more predictable and easier to debug.