React: image src is not taking path via API. image is undefined - javascript

I'm new to Reactjs. I'm unable to extract image url from the API. I'm really sorry if similar type of threads already exist in stackoverflow. Full code is below.
The API I used is: https://api.thecatapi.com/v1/breeds
import React, { Component } from "react";
import ReactDOM from "react-dom";
const Cat = ({
cat: {name, image},
}) => {
return(
<div className='catMain'>
<div className='catImage'>
<img src={image.url} alt={name} />
</div>
</div>
)
}
class App extends Component {
state = {
data: [],
};
componentDidMount() {
this.fetchCatData();
}
fetchCatData = async () => {
const url1 = "https://api.thecatapi.com/v1/breeds"
const response = await fetch(url1)
const data = await response.json()
this.setState({
data,
})
}
render() {
return (
<div className='main'>
<div className="cats">
<div className="catsInfo">
{this.state.data.map((cat) => (
<Cat cat={cat}/>
))}
</div>
</div>
</div>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

If you want to display every cat even though they might not have an image, you can do this:
const Cat = ({ cat: { name, image } }) => {
return (
<div className="catMain">
{image && (
<div className="catImage">
<img src={image.url} alt={name} />
</div>
)}
</div>
);
};
Then you can just display whatever else you want about the cat, without having to worry about image issues.

You have an assumption that every cat has an image, that's not the case, therefore you can filter empty images beforehand (there are tons of solutions, conditional rendering can be done too):
// cat.image is an object, which is truthy value therefore you can filter by it.
this.state.data.filter(cat => cat.image).map(cat => <Cat cat={cat} />)
// OR, conditional rendering
this.state.data.map(cat => cat.image && <Cat cat={cat} />)
// Or in the component itself, etc.
const Cat = ({ cat: { name, image } }) => {
return image ? (
<div className="catMain">
<div className="catImage">
<img src={image.url} alt={name} />
</div>
</div>
) : (
<Placeholder />
);
};

Related

How to modify react button "More"?

I have the following React component:
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const onClickButtonChange = () => {
let cardMore = document.querySelector(".card_more");
let cardMain = document.querySelector(".card_main");
cardMore.style.display = "block";
cardMain.style.display = "none";
};
return (
<div>
{data ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
<button onClick={onClickButtonChange}>More</button>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
My implementation of the More button needs to display additional features (the card_more block). Right now this function only works on the very first element. I understand that in React this can most likely be done more correctly, but I don’t know how, so I use CSS styles.
P.S Edited:
I tried to use React features, maybe someone can tell me or does it make sense?
import React from "react";
import { useState, useEffect } from "react";
import { TailSpin } from "react-loader-spinner";
function Pokemon({ name, url }) {
const [data, setData] = useState(null);
const [show, setShow] = useState(false);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
const handleMore = async () => {
if (show === true) {
setShow(false);
} else if (show === false || !data) {
const r = await fetch(url);
const newData = await r.json();
setData(newData);
setShow(true);
}
};
return (
<div>
{data && show ? (
<div>
<div className="card card_main">
<div className="animate__animated animate__bounceInUp">
<div className="card-image">
<img src={data.sprites.front_default} alt="pokemon_img" />
<span className="card-title">{name}</span>
</div>
<div className="card-content">
{data.abilities.map((n, index) => (
<p key={index}>{n.ability.name}</p>
))}
</div>
</div>
<button onClick={handleMore}>More</button>
</div>
<div className="card card_more">
<p>{data.height}</p>
<p>{data.weight}</p>
</div>
</div>
) : (
<div>
<TailSpin type="Puff" color="purple" height={100} width={100} />
</div>
)}
</div>
);
}
export { Pokemon };
Youre right, this isn't the way you should do it in React. But your problem in your onClickButtonChange-Function is that youre only getting one element with document.querySelector(".card_more") and everytime you call it you get the same element back (No matter on which card you call it)
What you need to do is: Identify the single component elements. Thats most likely solved by passing a id/key value down via props and then putting this id on a parent-element (e.g. div.card) and you give it an id:
<div className="card card_main" id={props.keyvalue}>
....
</div>
And then in your onClickButtonChange-Function you call:
let cardMore = document.querySelector(`#${props.keyvalue} .card_more`);
...
This should give you the right element.

Filter through posts via OnClick function

I'm trying to filter through some posts based on their category if a button is clicked. For example I have a button that when clicked the only posts that show up are related to software projects.
I have set up a function called searchHandler that I've passed through to my SidebarOptions component, which has the onclick event. But when I pass it through nothing happens.
Here is the code in the (parent) Home Component where the searchHandler is:
function Home() {
const [posts, setPosts] = useState([]);
const [filteredPosts, setFilteredPosts] = useState(null);
const searchHandler = (event) => {
const { value } = event.target;
setFilteredPosts(
value
? posts.filter(
(post) =>
post.question.question.includes(value)
)
: null
);
};
useEffect(() => {
db.collection("questions")
.orderBy("timestamp", "desc")
.onSnapshot((snapshot) =>
setPosts(
snapshot.docs.map((doc) => ({
id: doc.id,
question: doc.data(),
}))
)
);
}, []);
return (
<div className="home">
<div></div>
<Header searchHandler={searchHandler} />
<div className="home__content">
<Sidebar searchHandler={searchHandler} />
<Feed posts={filteredPosts || posts} />
<Widget />
</div>
</div>
);
}
Here is the (child) Sidebar component that receives it:
import React from "react";
import "../Style/Sidebar.css";
import SidebarOptions from "./SidebarOptions";
function Sidebar({ searchHandler }) {
return (
<div className="sidebar">
<SidebarOptions searchHandler={searchHandler} />
</div>
);
}
export default Sidebar;
And here is the (grandchild)SidebarOptions that the function is finally sent to:
function SidebarOptions({ searchHandler }) {
return (
<div className="sidebarOptions">
<div className="sidebarOption" onChange={() => searchHandler}>
<img
src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
</div>
);
};
I think you need to revisit your SideBarOptions component. I wonder if the onChange handler makes sense on a div. I think it should be input rather than a div if you want your user to type. Also, you need to call your handler with the value that is typed, here you are not calling the handler (notice the missing () after searchHandler in your code for SideBarOptions). Also, it will be better to add something like a debounce so that the filter is not triggered for every character that a user types. It should ideally be triggered once a user stops typing, debounce is precisely that.
Putting some code snippet below based on my guess about how it might work.
const SideBarOptions = ({ searchHandler }) => {
const [filterText, setFilterText] = useState("");
const handleFilter = () => {
searchHandler(filterText);
}
return (
<div className="sidebarOptions">
<input name="filterText" value={filterText} onChange={(e) => setFilterText(e.target.value)} />
<div className="sidebarOption" onChange={() => searchHandler}>
<img src="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d" srcset="https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d"
alt="Software Projects"
/>
<p>Software Projects</p>
<button onClick={handleFilter}>Filter</button>
</div>
</div>
);
}
So I was able to solve this by making a new function called categoryfilter in the Home component that went through the options and looked for the category of the posts in the database:
const categoryFilter = (category = "All") => {
const filtered =
category === "All"
? posts
: posts.filter(({ question }) => question.option === category);
setFilteredPosts(filtered);
};
I then passed that code as a prop to the sidebarOptions div after cleaning up the code a bit and used it to filter the posts based on the category name:
function SidebarOptions({ categoryFilter }) {
const categories = [
//Add all projects
{
name: "All",
imgUrl: "",
},
{
name: "Software Project",
imgUrl:
"https://c.pxhere.com/photos/7b/1a/code_coding_computer_developer_developing_development_macbook_notebook-913320.jpg!d",
},
{
name: "Engineering Project",
imgUrl:
"https://c.pxhere.com/photos/a7/72/gears_cogs_machine_machinery_mechanical_printing_press_gears_and_cogs_technology-818429.jpg!d",
},
];
return (
<div className="sidebarOptions">
{categories.map((category) => (
<div
className="sidebarOption"
onClick={() => categoryFilter(category.name)}
>
{category.imgUrl && (
<img
src={category.imgUrl}
srcSet={category.imgUrl}
alt={category.name}
/>
)}
<p>{category.name}</p>
</div>
))}
<div className="sidebarOption">
<Add />
<p>Suggest Project Space</p>
</div>
</div>
);
}
export default SidebarOptions;

How to make a react js element by using props?

I have a functional element in react js like this,
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => (
<div
onMouseEnter={() => setIsShown(true)}
onMouseLeave={() => setIsShown(false)}
className="filter__options__container"
>
<div className="filter__options__button">
{ourOption}
</div>
{isShown && <div className="filter__options__content"> Here I want to return the element using props </div>}
</div>
))}
</div>
);
}
I have created a files called, Category.js, Design.js, Size.js, Style.js.
Now I want to use the props so that I can concatenate like this <{ourOption}> <{ourOption}/> so that this will return element.
Any idea how to do this guys?
Choosing the Type at Runtime
First: Import the components used and create a lookup object
import Category from 'Category';
import Design from 'Design';
import Size from 'Size';
import Style from 'Style';
// ... other imports
const components = {
Category,
Design,
Size,
Style,
// ... other mappings
};
Second: Lookup the component to be rendered
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{["Category", "Design", "Size", "Style"].map((ourOption) => {
const Component = components[ourOption];
return (
...
<div className="filter__options__button">
<Component />
</div>
...
))}}
</div>
);
}
Alternatively you can just import and specify them directly in the array to be mapped.
function FilterOptions() {
const [isShown, setIsShown] = useState(false);
return (
<div className="filter__options">
{[Category, Design, Size, Style].map((Component) => (
...
<div className="filter__options__button">
<Component />
</div>
...
))}
</div>
);
}
Instead of strings you could iterate over Array of Components
{[Category, Design, Size, Style].map((Component) => (
<Component/>
);
Ill do this as react document
//create components array
const components = {
photo: Category,
video: Design
.....
};
{
Object.keys(components).map((compName) => {
const SpecificSection = components[compName];
return <SpecificSection />;
})
}
Here is a small sample code that you can work with. Use direct component instead of trying to determine by strings.
const Comp1 = () => {
return <p>Comp1 Here</p>
}
const Comp2 = () => {
return <p>Comp 2 Here</p>
}
export default function App() {
return (
<div className="App">
{[Comp1, Comp2].map(Komponent => {
// use Komponent to prevent overriding Component
return <Komponent></Komponent>
})}
</div>
);
}

How can I assign a new ref to every iteration inside a map function?

I'm not sure how to ask this question, because I'm still unable to accurately frame the problem.
I've created a useHover function. Below, you'll see that I am mapping over data and rendering a bunch of photos. However, the useHover only works on the first iteration.
I suspect that it's because of my ref. How does this work? Should I creating a new ref inside of each iteration -- or is that erroneous thinking..?
How can I do this?
Here's my useHover function.
const useHover = () => {
const ref = useRef();
const [hovered, setHovered] = useState(false);
const enter = () => setHovered(true);
const leave = () => setHovered(false);
useEffect(() => {
ref.current.addEventListener("mouseenter", enter);
ref.current.addEventListener("mouseleave", leave);
return () => {
ref.current.removeEventListener("mouseenter", enter);
ref.current.removeEventListener("mouseleave", leave);
};
}, [ref]);
return [ref, hovered];
};
And here's my map function. As you can see I've assigned the ref to the image.
The problem: Only one of the images works when hovered.
const [ref, hovered] = useHover();
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map(item => (
<div className="row imageSpace">
{hovered && <h1>{item.fields.name}</h1>}
<img
ref={ref}
className="image"
key={item.sys.id}
alt="fall"
src={item.fields.image.file.url}
/>
</div>
))}
</Div>
I'd handle this by using CSS if at all possible, rather than handling hovering in my JavaScript code.
If doing it in JavaScript code, I'd handle this by creating a component for the things that are hovered:
function MyImage({src, header}) {
const [ref, hovered] = useHover();
return (
<div className="row imageSpace">
{hovered && <h1>{header}</h1>}
<img
ref={ref}
className="image"
alt="fall"
src={src}
/>
</div>
);
}
and then use that component:
return (
<Wrapper>
<Styles className="row">
<Div className="col-xs-4">
{data.map(item =>
<MyImage
key={item.sys.id}
src={item.fields.image.file.url}
header={item.fields.name}
/>
)}
</Div>
(Obviously, make more of the props configurable if you like.)
As a general rule when you have a parent item with Array.map(), and functionality for each array item, refactor the items to a separate component (ImageRow in my code).
In this case you don't need to use refs for event handling, since React can handle that for you. Instead of return a ref from useHover, return an object with event handlers, and spread it on the component.
const { useState, useMemo } = React;
const useHover = () => {
const [hovered, setHovered] = useState(false);
const eventHandlers = useMemo(() => ({
onMouseEnter: () => setHovered(true),
onMouseLeave: () => setHovered(false)
}), [setHovered]);
return [hovered, eventHandlers];
};
const ImageRow = ({ name, url }) => {
const [hovered, eventHandlers] = useHover();
return (
<div className="row imageSpace">
{hovered && <h1>{name}</h1>}
<img
className="image"
alt="fall"
src={url}
{...eventHandlers}
/>
</div>
);
};
const images = [{ id: 1, name: 'random1', url: 'https://picsum.photos/200?1' }, { id: 2, name: 'random2', url: 'https://picsum.photos/200?2' }, { id: 3, name: 'random3', url: 'https://picsum.photos/200?3' }];
const Wrapper = ({ images }) => (
<div style={{ display: 'flex' }}>
{images.map(({ id, ...props }) => <ImageRow key={id} {...props} />)}
</div>
);
ReactDOM.render(
<Wrapper images={images} />,
root
);
h1 {
position: absolute;
pointer-events: none;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

About route(link) and asynchronous fetch data in react

First of all, good evening. I'm trying to improve myself at React. So I'm working on a Starwars project πŸ‘¨β€πŸ’».
I have two problems.
First of all, I listed different characters at the bottom of my character detail page. Again, I want it to be directed to different characters through the same component. But even if the link changes, my component is not refreshed. But the picture is changing πŸ€”.
Note:
sandbox link : https://codesandbox.io/s/github/kasim444/Javascript-Camp-2019/tree/master/challenges/star-wars-app/
my project github link : https://github.com/kasim444/Javascript-Camp-2019/tree/master/challenges/star-wars-app/
// component that I redirect in.
class CharacterDetail extends Component {
render () {
const characterId = this.props.match.params.id;
const {
name,
height,
mass,
hair_color,
skin_color,
eye_color,
birthday_year,
gender,
homeworld,
loading,
} = this.state;
return loading
? <Loading />
: (
<div>
<main className="characterBg">
<DetailHeader imgLink={characterAvatarLink[characterId - 1]} />
<CharacterContent
imgLink={characterAvatarLink[characterId- 1]}
characterInfo={this.state}
/>
</main>
<FeaturedCharacters />
</div>
);
}
}
// feautered character component
function FeaturedCharacters () {
const [characters, setCharacters] = useState ([]);
const [loading, setLoading] = useState (true);
const fetchCharacters = async () => {
const data = await fetch ('https://swapi.co/api/people/');
const fetchPeople = await data.json ();
const feauteredCharacter = fetchPeople.results.filter (
(character, index) => index < 4
);
setCharacters (feauteredCharacter);
setLoading (false);
};
useEffect (() => {
fetchCharacters ();
}, []);
return (
<div>
<h2>Popular Characters</h2>
<div className="d-flex-row container">
{loading
? <PlaceholderDiv />
: characters.map ((character, index) => (
<CharacterCard
key={character.name}
chaId={index + 1}
chaDet={character.name}
imgLink={characterAvatarLink[index]}
/>
))}
</div>
</div>
);
}
// character card link component
const CharacterCard = props => {
const name = props.chaDet;
return (
<Link className="profile_card" to={`/character/${props.chaId}`}>
<div className="profile_image">
<img src={props.imgLink} alt={name} />
</div>
<div className="profile_content">
<h3>{name}</h3>
<div className="read_more d-flex-row">
<img src={showIcon} alt="Show Icon" />
Show More
</div>
</div>
</Link>
);
};
// main component
const App = () => {
return (
<Router>
<div className="st-container d-flex-column">
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/movie/:title" component={MovieDetails} />
<Route path="/character/:id" component={CharacterDetail} />
<Route
path="/githubProfile"
component={() => {
window.location.href = 'https://github.com/kasim444/Javascript-Camp-2019/tree/master/challenges/star-wars-app';
return null;
}}
/>
</Switch>
<Footer />
</div>
</Router>
);
};
My second problem is that I draw a data back from the data from Api. I can reach outlines of the character. It's working now. But I feel that there are some things that don't feel right.🀨 How can I improve Fetch operations in Axios?
async componentDidMount () {
const characterId = this.props.match.params.id;
const filmSeries = [];
const characterDetail = await axios.get (
`https://swapi.co/api/people/${characterId}/`
);
const filmsFetchLinks = characterDetail.data.films;
const promisesData = await filmsFetchLinks.map(link => axios.get(link));
axios.all (promisesData).then(value => {
value.map (val => filmSeries.push (val.data.title));
let {
name,
height,
mass,
hair_color,
skin_color,
eye_color,
birthday_year,
gender,
homeworld,
films,
} = characterDetail.data;
fetch(homeworld).then(home => home.json()).then(val => this.setState({homeworld: val.name}));
this.setState ({
name,
height,
mass,
hair_color,
skin_color,
eye_color,
birthday_year,
gender,
films: filmSeries,
loading: false,
});
});
}
I'm sorry if I bored you. It seems a little long because the components are interconnected. Thank you in advance for your interest. πŸ–– πŸ™
You can use componentDidUpdate and compare the the current parameter id to the previous one (you would have to save it to state) and you fetch data again IFF the two are different. componentDidUpdate will go every time the route changes.
A better approach would be to use useEffect and depend on the parameter id. In the use effect, you do all your data fetching. Something like this:
const { id: characterId } = props.match.params;
React.useEffect(() => {
async function getData() {
// fetch all you data here
}
getData();
}, [characterId]);
You can see a crude version of this here:
https://codesandbox.io/s/star-wars-app-sx8hk

Categories

Resources