How pass API info from a react component to another? - javascript

i am fighting with a react app with a movie API (https://developers.themoviedb.org) hahaha. I have a list component with movie and tv cards. I map a list state and put the info in my cards (id, title and poster). When i click in my card, the site must show movie information. So, i have another component named movies.jsx and there i have another three components: hero.jsx (who contains a img from the movie), menuHero.jsx (a menu) and movieInfo.jsx (that contains the title, a little desc from the movie and blabla). I need fill this three components with the api info.
My component movies.jsx show me the id (i put that in a < h2> just for see if its working) but i don't find a way to give them the api info to my another three child components.
Also, the movieData its empty. So, i dont know what im doing wrong.
Here is my code:
const Movies = props => {
const [movieData, setMovieData] = useState([]);
const { match } = props;
const movieId = match.params.id;
useEffect(() => {
axios.get(`https://api.themoviedb.org/3/movie/${movieId}?api_key=${api-key}`)
.then(res => {
setMovieData(res.data);
console.log(movieData)
}).catch(error => console.log(error))
}, []);
return(
<div className="container-section">
<h2>{match.params.id}</h2>
<Hero />
<MenuHero />
<MovieInfo />
</div>
)
}
export default Movies;
enter code here

You can pass that data as props:
return movieData && (
<div className="container-section">
<h2>{match.params.id}</h2>
<Hero movieData={movieData} />
<MenuHero movieData={movieData} />
<MovieInfo movieData={movieData} />
</div>
)
More info: https://reactjs.org/docs/components-and-props.html

You need to get an API key and movie ID from themoviedb, ${movieId} ${api-key} should be your key which should be stored as an environment variable. (.env) file and to access that key you'll need to use process.env.(VARIABLE_NAME) to get the desired value. That's why you're not getting any data from your get requests.

Related

How to display "no records found" after running a search in React

I have been trying to add "no records found" message after running a search for worker names. But I have not been successful. I either get 20 "no records found" messages or none at all. I am not sure what I am doing wrong, but I have been trying for last 4 hours various methods and work arounds.
I know that this should be simple to implement, but it has been difficult.
Here is a link to my code on codesandbox: https://codesandbox.io/s/fe-hatc-ass-search-n62kw?file=/src/App.js
Any insights would be helpful....
Things I tried were, if else statements, logical operators... etc...
In my opinion the first thing you need to think about is what data do you need and when do you need it. To display no results like you want you are going to need the workers name in the component that is doing the filtering. So you would need it in the orders component. I would merge the worker data with the order data and then you can just filter and manipulate the data after that. That would also stop you from making an api request every time someone changes the input and all you need to do is filter the already fetched data. Then you can check the array length and if it is greater than 0 you can display results else display a no results statement.
So something like the following:
Orders component
import React, { useEffect, useState } from "react";
import "./Orders.css";
import Order from "./Worker";
import axios from "axios";
const Orders = () => {
const [orders, setOrders] = useState([]);
const [results, setResults] = useState([]);
const [searchedWorker, setSearchedWorker] = useState("");
const getOrders = async () => {
const workOrders = await axios.get(
"https://api.hatchways.io/assessment/work_orders"
);
const mappedOrders = await Promise.all(workOrders.data.orders.map(async order => {
const worker = await axios.get(
`https://api.hatchways.io/assessment/workers/${order.workerId}`
);
return {...order, worker: worker.data.worker}
}))
setOrders(mappedOrders);
};
useEffect(() => {
getOrders();
}, []);
useEffect(() => {
const filtered = orders.filter(order => order.worker.name.toLowerCase().includes(searchedWorker));
setResults(filtered)
}, [searchedWorker, orders])
return (
<div>
<h1>Orders</h1>
<input
type="text"
name="workerName"
id="workerName"
placeholder="Filter by workername..."
value={searchedWorker} //property specifies the value associated with the input
onChange={(e) => setSearchedWorker(e.target.value.toLowerCase())}
//onChange captures the entered values and stores it inside our state hooks
//then we pass the searched values as props into the component
/>
<p>Results: {results.length}</p>
{results.length > 0 ? results.map((order) => (
<Order key={order.id} lemon={order} />
)) : <p>No results found</p> }
</div>
);
};
//(if this part is true) && (this part will execute)
//is short for: if(condition){(this part will execute)}
export default Orders;
Then you can simplify your single order component
import React from "react";
const Order = ({ lemon }) => {
return (
<div>
<div className="order">
<p>Work order {lemon.id}</p>
<p>{lemon.description}</p>
<img src={`${lemon.worker.image}`} alt="worker" />
<p>{lemon.worker.name}</p>
<p>{lemon.worker.company}</p>
<p>{lemon.worker.email}</p>
<p>{new Date(lemon.deadline).toLocaleString()}</p>
</div>
</
div>
);
};
export default Order;
Looking at your code, the problem is because you're doing the filtering in each individual <Order> component. The filtering should be done in the parent Orders component and you should only render an <Order> component if a match is found.
Currently, your <Order> component is rendering, even if there's no match.
You could add an state in the Orders.js to count how many items are being presented. However, since each Worker depends on an api call, you would need to have the response (getWorker, in Workers.js) wait for the response in order to make the count. Every time the input value changes, you should reset the counter to 0.
https://codesandbox.io/s/fe-hatc-ass-search-forked-elyjz?file=/src/Worker.js:267-276
Also, as a comment, it is safer to put the functions that are run in useEffect, inside the useEffect, this way it is easier to see if you are missing a dependency.

React: Persisting state or data on browser back/ between pages

I'm making a small SNS app using React. (Gatsby.js)
What I want to do is to persist state in previous pages even when you go back with the browser. Like twitter or instagram, when you go to follow or follower page and visit more pages, you do not lose data when you go back. I can't find a single clue.
Is it something to do with history api or routing?
There is also a User page and it contains some links to following/follower pages in my app. When a user reaches on a page, I am fetching API in useEffect hook using url params then store it to global state(recoil), show on the component.
Here is a problem.
When I visit user page and move forward to follower page, then visit another user page from there and move to his follower page, when I go back with the browser, they don't remember the global state (of course) and it'll get fetch data again which shows loading. Tried to clean data when unmounting because it shows previous data when you go to other page. Couldn't find the bast practice.
Probably it's nothing to do with global states (Recoil), somehow window is remembering what was in the previous pages (and scroll position too)?
I'd appreciate any advice. Thank you.
React/GatsbyJS
Router/Reach-router
Route
...
<PrivateRoute path="/user/:userId" component={UserPage} />
<PrivateRoute path="/:userId/follower" component={FollowerPage} />
...
UserPage
const UserPage = () => {
const { userId } = useParams()
const user = useRecoilValue(userProfileViewData)
const loading = useRecoilValue(loadingUserPage)
...
useEffect(() => {
... // fetch api to get user info
...// store it in global state -> userProfileViewData
}, [])
if(loading) return <div>Loading</div>
return (
<div>
<div>{user.name}</div>
...
<div onClick={() => navigate('/app/' + userId + '/follower')}>Follower</div>
...
</div>
)
}
export default UserPage
FollowerPage
const FollowerPage = () => {
const { userId } = useParams()
const [loading, setLoading] = useState(true)
const followerData = useRecoilValue(followers)
...
useEffect(() => {
...// api calls to get followers
...// store it in global state -> followers
}, [])
useEffect(() => {
return () => {
.. // I have a function to clean data here
}
}, [])
if(loading) {
return <div>loading....</div>
}
return (
<div>
<div>Follower</div>
<div>
{followerData.map(user => (
<div key={`user.id}>
<div onClick={() => navigate(`/app/user/` + user.id)}>
{user.name}
</div>
</div>
))}
</div>
</div>
)
}
export default FollowerPage
Maybe you can use Redux to globally remember state of values

useSelector returns initial state in one page and correct state in other pages

I have the following situation: In component <Home /> I have a component made my me, <AutocompleteSearch /> which displays a list of books, and when we click one book, it does an api call to fetch the details about clicked book then and dispatch that saves in the books state, the current book. My problem is that I have an listener in the <Home /> component that when is triggered it checks if some value is equal with the current book id, and for that I use useSelector to get the current book in the <Home />, but I get an error saying that currentBook is undefined, I printed the books state and it prints an empty object that is the initial state. The strange thing is that I have another component in my <Home />, called to which I pass as props the current book, and in BookProfile the current book prop is a fine object, fetched from server, so the state updates correct, but when I try to access the current book in the <Home /> it look like it never changed, and as well I wanna say, I tried to use the same useSelector in another child component of home, and it works here too, it logs in console a fine object from server, representing the last clicked book in the AutocompleteSearch.
I'll insert some code snippets:
const currentUser = useSelector(state => state.users.currentUser)
const currentBook = useSelector(state => state.book.currentBook)
return (
...........
{openSearchBar && (
<AutoCompleteSearch
suggestionsList={bookListForSearchBar}
elementClickAction={setMainContent}
/>
)}
...........
<BookProfile
book={currentBook}
user={currentUser}
/>
...
}
const bookListClicked = async book=> {
await dispatch(getCurrentBook(book.id))
}
const currentBookReceived = book=> ({
type: actions.CURRENT_BOOK_RECEIVED,
anime,
})
export default function News() {
const currentUser = useSelector(state => state.users.currentUser)
const currentBook = useSelector(state => state.book.currentBook)
return (
<>
<div
onClick={() => {
console.log(currentUser)
console.log(currentBook)
}}>
News
</div>
</>
)
}
In News components state works fine, it logs the correct book, but in Home it logs undefined.
let initialState={}
export default (state = initialState, action) => {
switch (action.type) {
case actions.CURRENT_BOOK_RECEIVED:
return { ...state, currentBook: action.book }
default:
return state
}
}
Also, currentUser is fetched from server after succes at login then saved in users state with dispatch, and I printed it in console in Home and it is as it should be, not undefined like currentBook.
I am new in front-end development and I don't understand very well how the things works, I don't understand why in one component the state is seen well and in another it is not.

How to get running Gatsby page query data with React Context API?

I'm working on a site where I have a gallery and custom build lightbox. Currently, I'm querying my data with a page query, however, I also use them in other components to display the right images and changing states. It is easier for me to store states in Context API as my data flow both-ways (I need global state) and to avoid props drilling as well.
I've setup my context.provider in gatsby-ssr.js and gatsby-browser.js like this:
const React = require("react");
const { PhotosContextProvider } = require("./src/contexts/photosContext");
exports.wrapRootElement = ({ element }) => {
return <PhotosContextProvider>{element}</PhotosContextProvider>;
};
I've followed official gatsby documentation for wrapping my root component into context provider.
Gallery.js here I fetch my data and set them into global state:
import { usePhotosContext } from "../contexts/photosContext";
const Test = ({ data }) => {
const { contextData, setContextData } = usePhotosContext();
useEffect(() => {
setContextData(data);
}, [data]);
return (
<div>
<h1>hey from test site</h1>
{contextData.allStrapiCategory.allCategories.map((item) => (
<p>{item.name}</p>
))}
<OtherNestedComponents />
</div>
);
};
export const getData = graphql`
query TestQuery {
allStrapiCategory(sort: { fields: name }) {
allCategories: nodes {
name
}
}
}
`;
export default Test;
NOTE: This is just a test query for simplicity
I've double-checked if I get the data and for typos, and everything works, but the problem occurs when I try to render them out. I get type error undefined. I think it's because it takes a moment to setState so on my first render the contextData array is empty, and after the state is set then the component could render.
Do you have any idea how to work around this or am I missing something? Should I use a different type of query? I'm querying all photos so I don't need to set any variables.
EDIT: I've found a solution for this kinda, basically I check if the data exits and I render my component conditionally.
return testData.length === 0 ? (
<div className="hidden">
<h2>Hey from test</h2>
<p>Nothing to render</p>
</div>
) : (
<div>
<h2>Hey from test</h2>
{testData.allStrapiCategory.allCategories.map((item) => (
<p>{item.name}</p>
))}
</div>
);
However, I find this hacky, and kinda repetitive as I'd have to use this in every component that I use that data at. So I'm still looking for other solutions.
Passing this [page queried] data to root provider doesn't make a sense [neither in gatsby nor in apollo] - data duplication, not required in all pages/etc.
... this data is fetched at build time then no need to check length/loading/etc
... you can render provider in page component to pass data to child components using context (without props drilling).

Updating post count the reactive way

I am new to react. I have created a news component that consumes a json url then spits out some news articles. In the client side UI if the clients changes the json url it will update without refreshing the page using this code:
componentDidUpdate(prevProps) {
if (prevProps.jsonUrl !== this.props.jsonUrl) {
this.getPosts();
}
}
However I also need the the news feed to update reactively if the postCount: this.props.postCount is changed in the client side UI. The post count is used in the render method below to choose how many posts to display.
posts
.slice(0, postCount)
.map(post => {
// Variables to use
let { id, name, summary, url, imgUrl} = post;
// Stripping html tags from summary
//let strippedSummary = summary.replace(/(<([^>]+)>)/ig,"");
// What we actually render
return (
<div key={id} className={ styles.post}>
<p>{name}</p>
{/* <p>{summary}</p> */}
<a href={url}>{url}</a>
<img className={ styles.postImage} src={imgUrl} />
</div>
);
})
Any help is much appreciated! - I was thinking something like this inside componentDidUpdate:
if (prevProps.postCount !== this.props.postCount) {
this.setState( this.state.postCount; );
}
EDIT:
I am now using the postCount from the props instead of a state and it updates instantly! :D
// Grabbing objects to use from state
const { posts, isLoading } = this.state;
const { postCount } = this.props;
The components are going to react automatically to the changes in their props, so there's no need to transfer any props to a state. In this case, if postCount is a prop, when it changes it should affect the piece of code that you shared to render the component. However, I don't know if posts is part of the state, in your case it should be and your method getPosts should setState with the new posts.

Categories