I am creating a website. I am a beginner. I have an issue. I have an array of react components. I don’t know can I use React components as the array elements. They are images, imported from the folder of my project. Also, I have an array of names of news companies. The idea is to create blocks with the name and image above. I want to create blocks according to the my images array length. So if the length of this array is 4, the cards I have 4. The issue is I can't display images, I imported them to my project. Main code is in the main page component. Also, I have a component called Author Card. In it, I have a React component, that receives name and image as the props and put them in the card Html block.
Here is my main page component code:
import React from 'react';
import AuthorCard from "./MainPageComponents/AuthorCard";
import BBC_Logo from '../assets/images/BBC_Logo.png';
import FOX_Logo from '../assets/images/FOX_Logo.png';
import CNN_Logo from '../assets/images/CNN_logo.png';
import ForbesLogo from '../assets/images/forbes-logo.png';
function MainPage(props) {
const channels = [
{
name: 'BBC',
index: 1
},
{
name: 'FOX',
index: 2
},
{
name: 'CNN',
index: 3
},
{
name: 'FORBES',
index: 4
},
];
const logos = [
<BBC_Logo key={1} />,
<FOX_Logo key={2}/>,
<CNN_Logo key={3}/>,
<ForbesLogo key={4}/>
];
return (
<div className="main-page">
<div className="main-page_container">
<section className="main-page_channels">
{channels.map( (channel) => {
logos.map( (logo) => {
return <AuthorCard name={channel.name} img={logo} />
})
})}
</section>
</div>
</div>
);
}
export default MainPage;
Here is my Author Card component code:
import React from 'react';
function AuthorCard(props) {
return (
<div className="author-card">
<div className="author-img">
{props.img}
</div>
<div className="author-name">
{props.name}
</div>
</div>
);
}
export default AuthorCard;
Please, help!
I would handle this a bit differently. First thing the way you import your logos is not imported as a component. Rather you get the path/src of the image which you can then use in a component. Read more about that here: https://create-react-app.dev/docs/adding-images-fonts-and-files/
So the way I would do this is to put the logo img src into your channels array and then pass that img src to the AuthorCard component. Then in the AuthorCard component your use a component to render the image. Like this:
import React from "react";
import BBC_Logo from "../assets/images/BBC_Logo.png";
import FOX_Logo from "../assets/images/FOX_Logo.png";
import CNN_Logo from "../assets/images/CNN_logo.png";
import ForbesLogo from "../assets/images/forbes-logo.png";
export default function App() {
return (
<div className="App">
<MainPage />
</div>
);
}
const channels = [
{
name: "BBC",
index: 1,
img: BBC_Logo
},
{
name: "FOX",
index: 2,
img: FOX_Logo
},
{
name: "CNN",
index: 3,
img: CNN_Logo
},
{
name: "FORBES",
index: 4,
img: ForbesLogo
}
];
function MainPage(props) {
return (
<div className="main-page">
<div className="main-page_container">
<section className="main-page_channels">
{channels.map((channel) => {
return <AuthorCard name={channel.name} img={channel.img} />;
})}
</section>
</div>
</div>
);
}
function AuthorCard(props) {
return (
<div className="author-card">
<div className="author-img">
<img src={props.img} alt="author card" />
</div>
<div className="author-name">{props.name}</div>
</div>
);
}
Here, we are using the map function to iterate over the channels array and render an AuthorCard component for each channel. We pass the name property to the AuthorCard component, as well as the corresponding logo from the logos array.
Note that we are also passing a key prop to the AuthorCard component to help React identify each component uniquely. In this case, we're using the index property of each channel object.
Related
I've been trying to learn React over the past few weeks, and have decided to go through the LamaDev tutorial series. Yesterday I started with the portfolio tutorial (https://www.youtube.com/watch?v=hQjlM-8C4Ps&t=2798s) but have been stuck with trying to load images in my array.
I went ahead and built a component called 'Product' which the code can be found below. After that I followed the instructions and built the ProductList component which is suppose to show each of my products that are in my data.js file. I have gone and posted those below.
The problem I am running in to is that if I use a random img link from the internet the image gets imported into my product and show through my product list. However this is not what I am wanting to do since there are some images of my own I wanted to use.
When my Product.jsx tried to use a image I have saved in src/assets/img/ the img won't load. I tried using the require tag but it still is not working. I have also gone ahead and uploaded everything to my github page which can be found here and used as a reference.
I'm really not sure what I've done wrong here since everything looks right, but still know the issue is falling between the keyboard and the chair.
Thanks for any help
Product.jsx
import "./product.css";
const Product = ({ img, link }) => {
return (
<div className="p">
<div className="p-browser">
<div className="p-circle"></div>
<div className="p-circle"></div>
<div className="p-circle"></div>
</div>
<a href={link} target="_blank" rel="noreferrer">
<img src={img} alt="" className="p-img" />
</a>
</div>
);
};
export default Product;
ProductList.jsx
import Product from "../product/Product";
import "./productList.css";
import { products } from "../../data";
const ProductList = () => {
return (
<div className="pl">
<div className="pl-texts">
<h1 className="pl-title">Create & inspire. It's Jon</h1>
<p className="pl-desc">
Jon Snow is a creative portfolio that your work has been waiting for.
Beautiful homes, stunning portfolio styles & a whole lot more awaits
inside.
</p>
</div>
<div className="pl-list">
{products.map((item) => (
// console.log(item)
<Product key={item.id} img={item.img} link={item.link} />
))}
</div>
</div>
);
};
export default ProductList;
data.js
export const products = [
{
id: 1,
img: require("./assets/img/theBestTestSite.jpg"),
link: "http://google.com",
},
{
id: 2,
img: require("./assets/img/theBestTestSite.jpg"),
link: "http://google.com",
},
{
id: 3,
img: require("./assets/img/theBestTestSite.jpg"),
link: "http://google.com",
},
];
In data.js try to import images first instead of require
import img1 from "./assets/img/theBestTestSite.jpg"
export const products = [
{
id: 1,
img: img1,
link: "http://google.com",
},
// and same for others
];
I'm following a YouTube tutorial for next.js and i got stuck on a very beginning point. I'm trying to create a dynamic content and routing for my website with the codes below but there is an error:
import Link from 'next/Link';
const people = [
{v: 'car', name: 'bruno'},
{v: 'bike', name: 'john'},
{v: 'airplane', name: 'mick'}
]
export default function Details (){
return <div>
{people.map( e => (
<div>
<Link as={'/${e.v}/${e.name}'} href="/[vehicle]/[person]">
<a>Navigate to {e.name}'s {e.v}</a>
</Link>
</div>
))}
</div>
}
The dynamic routing for the code above is working fine actually. But when i go to my details page, the content is looking like "${e.name}'s ${e.v}" this. It doesn't look like according to my parameters. I believe the problem occurs because of this line:
<Link as={'/${e.v}/${e.name}'} href="/[vehicle]/[person]">
I tried to change " ' " this quotation mark to this " ´ " back quote mark but that also doesn't work. Could you please help me?
I also get this error in my Developer Tools console. I changed the fb.me link for a purpose.
Warning: Each child in a list should have a unique "key" prop.
Check the top-level render call using <div>. See blablafb.me/react-warning-keys for more information.
in Link (at Details.js:13)
in Details
in App
in Unknown
in Context.Provider
in Context.Provider
in Context.Provider
in Context.Provider
in AppContainer
In your /pages/index.js file you change single quotes ' with back ticks ` to permit string interpolation, and add a key attribute to the top-level div where you map over people's array:
import Link from "next/link";
const people = [
{ v: "car", name: "bruno" },
{ v: "bike", name: "john" },
{ v: "airplane", name: "mick" }
];
export default function Details() {
return (
<div>
{people.map((e, idx) => (
<div key={idx}>
<Link href="/[vehicle]/[person]" as={`/${e.v}/${e.name}`} passHref>
<a>
Navigate to {e.name}'s {e.v}
</a>
</Link>
</div>
))}
</div>
);
}
Then create /pages/[vehicle]/[person].js where you display different content based on the link you clicked on:
import { useRouter } from "next/router";
import Link from "next/link";
export default function Person() {
const router = useRouter();
const { vehicle, person } = router.query;
return (
<div>
<p>
Vehicle: {vehicle}, Person: {person}
</p>
Go back{" "}
<Link href="/" passHref>
<a>Home</a>
</Link>
</div>
);
}
Im importing an SVG file as a ReactComponent.
I then want to output this component dynamically based on data from a request.
i.e.
import { ReactComponent as S1 } from '../../assets/images/characteristics/S1.svg';
{characteristics.map(characteristic => (
<div className="characteristic" key={characteristic.key}>
<characteristic.key />
</div>
}
where characteristic.key holds the name of the SVG i.e. "S1" in my example
How can i output the component as this does not work?
Thanks
You can try to do it like this instead:
import { ReactComponent as S1 } from "./path1.svg";
import { ReactComponent as S2 } from "./path2.svg";
// ...
function App() {
// defining characteristics for demonstration purposes
const characteristics = [
{ component: S1, key: "S1" },
{ component: S2, key: "S2" }
];
return (
<div className="App">
{characteristics.map(characteristic => (
<div className="characteristic" key={characteristic.key}>
<characteristic.component />
</div>
))}
</div>
);
}
So the string "S1" would be a good value for a key and you use S1 from the import to actually render the svg component.
My JSX won't show up properly on my React webpage instead I get this output:
<div class='card'>NaNSasha<img src= NaN />Boddy Cane</div>.
The component:
import React, {Component} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component{
state = {
string : '',
}
componentDidMount(){
let posts = [
{
title: 'somebody toucha my spaghet',
author: 'Karen',
image:'https://media-cdn.tripadvisor.com/media/photo-s/11/69/c7/f9/spagetti.jpg',
location: 'Jimmy John',
description: 'This spagetti is amazing'
},
{
title: `I love food`,
author: 'Sasha',
image:'https://hoodline.imgix.net/uploads/story/image/610603/donuts2.jpg?auto=format',
location: 'Boddy Cane',
description: 'cjndwsijnjcinjw'
}
];
for(let i =0; i < posts.length; i ++){
const header = `<div class='card'>${+posts[i].title}`;
const body = posts[i].author;
const image = `<img src= ${+posts[i].image} />`;
const description = `${posts[i].location}</div>`;
const concatThis = header + body + image + description
this.setState({
string: concatThis
});
};
};
render(){
return(
<div className='container'>
{this.state.string}
</div>
)
}
}
export default App;
P.S I'm a student
This is what you're looking for. The expression +{} is evaluated as NaN. But please use list rendering.
const image = `<img src= ${+posts[i].image} />`;
^ here
It seems that you are trying to build a string which you then store in a state and render that string after it has been updated. This is unfortunately not how you should use React.
The state should only be raw data, like the posts array with objects. It holds the content and data of the component and should not concern itself on other tasks than that. You obviously can put any type of data in a state, like a string.
state = {
title: 'My food blog',
description: 'Awesome stuff about food',
posts: [
{
title: 'somebody toucha my spaghet',
author: 'Karen',
image:'https://media-cdn.tripadvisor.com/media/photo-s/11/69/c7/f9/spagetti.jpg',
location: 'Jimmy John',
description: 'This spagetti is amazing'
},
{
title: `I love food`,
author: 'Sasha',
image:'https://hoodline.imgix.net/uploads/story/image/610603/donuts2.jpg?auto=format',
location: 'Boddy Cane',
description: 'cjndwsijnjcinjw'
}
]
}
The componentDidMount method is triggered whenever the component has been placed on the page and is now working. In there you can do things like making a change to the data or downloading data from the server. It would make sense that you would do that there because then you would first show your component, maybe show it that it is loading and then fetch data from the server. After that is done, update the state of the component with the new data and the render method will be called with the new data. For example (for illustration purposes):
componentDidMount() {
fetch('urlofdatathatyouwant') // Uses AJAX to get data from anywhere you want with the Fetch API.
.then(response => response.json()) // Tells it to read turn the response from JSON into an usable JavaScript values.
.then(data => {
this.setState({
posts: data // Use the new data to replace the posts. This will trigger a new render.
});
});
}
The render method should primarely concern itself with the presentation of the data in your state. In this case it should loop over the elements in the posts state and create a React element for each post.
render() {
const { posts } = this.state;
return(
<div className='container'>
{posts.map(({ title, author, image, location, description }) => (
// Loop over each post and return a card element with the data inserted.
<div className="card">
<span>{title}</span>
<span>{author}</span>
<img src={image} alt={title}/>
<span>{location}</span>
<span>{description}</span>
</div>
))}
</div>
)
}
All put together it would look like this the example below. So the state only holds the data, componentDidMount is a place to do manipulation of your data after the component is on the page and render only outputs the HTML that you need to create with JSX.
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
class App extends Component{
state = {
posts: [
{
title: 'somebody toucha my spaghet',
author: 'Karen',
image:'https://media-cdn.tripadvisor.com/media/photo-s/11/69/c7/f9/spagetti.jpg',
location: 'Jimmy John',
description: 'This spagetti is amazing'
},
{
title: `I love food`,
author: 'Sasha',
image:'https://hoodline.imgix.net/uploads/story/image/610603/donuts2.jpg?auto=format',
location: 'Boddy Cane',
description: 'cjndwsijnjcinjw'
}
]
}
componentDidMount() {
// Do something here with the posts if you need to.
}
render() {
const { posts } = this.state;
return(
<div className='container'>
{posts.map(({ title, author, image, location, description }, index) => (
<div key={index} className="card">
<span>{title}</span>
<span>{author}</span>
<img src={image} alt={title}/>
<span>{location}</span>
<span>{description}</span>
</div>
))}
</div>
)
}
}
export default App;
You could even make it a bit nicer by making the card element a component as well. And since it does not have any functionality (yet) it only has to control the output.
const Card = ({ title, author, image, location }) => (
<div className="card">
<span>{title}</span>
<span>{author}</span>
<img src={image} alt={title}/>
<span>{location}</span>
<span>{description}</span>
</div>
)
And then import the card into your App component and use it in the render method.
// App.jsx render.
render() {
const { posts } = this.state;
return(
<div className='container'>
{ /* ...post taking all the properties of each post and passes them to the card element */ }
{posts.map((post, index) => <Card key={index} {...post} />)}
</div>
)
}
I have made a global component that will render the content we want.
This component is very simple
<template>
<section
id="help"
class="collapse"
>
<div class="container-fluid">
<slot />
</div>
</section>
</template>
<script>
export default {
name: 'VHelp',
};
</script>
I use it inside my base template with
<v-help />
I'm trying to add content to this component slot from another single file component using.
<v-help>
<p>esgssthsrthsrt</p>
</v-help>
But this logically create another instance of my comp, with the p tag inside. Not the correct thing I want to do.
So I tried with virtual DOM and rendering function, replacing slot by <v-elements-generator :elements="$store.state.help.helpElements" /> inside my VHelp comp.
The store helpElements is a simple array with objects inside.
{
type: 'a',
config: {
class: 'btn btn-default',
},
nestedElements: [
{
type: 'span',
value: 'example',
},
{
type: 'i',
},
],
},
Then inside my VElementsGenerator comp I have a render function that with render element inside virtual DOM from an object like
<script>
import {
cloneDeep,
isEmpty,
} from 'lodash';
export default {
name: 'VElementsGenerator',
props: {
elements: {
type: Array,
required: true,
},
},
methods: {
iterateThroughObject(object, createElement, isNestedElement = false) {
const generatedElement = [];
for (const entry of object) {
const nestedElements = [];
let elementConfig = {};
if (typeof entry.config !== 'undefined') {
elementConfig = cloneDeep(entry.config);
}
if (entry.nestedElements) {
nestedElements.push(this.iterateThroughObject(entry.nestedElements, createElement, true));
}
generatedElement.push(createElement(
entry.type,
isEmpty(elementConfig) ? entry.value : elementConfig,
nestedElements
));
if (typeof entry.parentValue !== 'undefined') {
generatedElement.push(entry.parentValue);
}
}
if (isNestedElement) {
return generatedElement.length === 1 ? generatedElement[0] : generatedElement;
}
return createElement('div', generatedElement);
},
},
render(createElement) {
if (this.elements) {
return this.iterateThroughObject(this.elements, createElement);
}
return false;
},
};
</script>
This second method is working well but if I want to render complex data, the object used inside the rendering function is very very long and complex to read.
So I'm trying to find another way to add content to a global component used inside a base layout only when I want it on a child component.
I can't use this VHelp component directly inside children comps because the HTML page architecture will be totally wrong.
I'm wondering if this is possible to add content (preferably HTML) to a component slot from a single file comp without re-creating a new instance of the component?
Furthermore I think this is very ugly to save HTML as string inside a Vuex store. So I don't even know if this is possible and if I need to completely change the way I'm trying to do this.
Any ideas ?
In the store, you should only store data and not an HTML structure. The way to go with this problem would be to store the current state of the content of the v-help component in the store. Then, you would have a single v-help component with a slot (like you already proposed). You should pass different contents according to the state in the store. Here is an abstract example:
<v-help>
<content-one v-if="$store.state.content === 'CONTENT_ONE' />
<content-two v-else-if="$store.state.content === 'CONTENT_TWO' />
<content-fallback v-else />
</v-help>
Child element somewhere else:
<div>
<button #click="$store.commit('setContentToOne')">Content 1</button>
</div>
Vuex Store:
state: {
content: null
},
mutations: {
setContentToOne(state) {
state.content = 'CONTENT_ONE';
}
}
Of course it depends on your requirements and especially on how many different scenarios are used if this is the best way to achieve this. If I understood you correctly, you are saving help elements to the store. You could also save an array of currently selected help elements in there and just display them directly in the v-help component.
EDIT:
Of course you can also just save the static component (or its name) in the store. Then, you could dynamically decide in the child components, which content is shown in v-help. Here is an example:
<v-help>
<component :is="$store.state.helpComponent" v-if="$store.state.helpComponent !== null" />
</v-help>
Test Component:
<template>
test component
</template>
<script>
export default {
name: 'test-component'
};
</script>
Child element somewhere else (variant 1, storing the name in Vuex):
<div>
<button #click="$store.commit('setHelpComponent', 'test-component')">Set v-help component to 'test-component'</button>
</div>
Child element somewhere else (variant 2, storing the whole component in Vuex):
<template>
<button #click="$store.commit('setHelpComponent', testComponent)">Set v-help component to testComponent (imported)</button>
</template>
<script>
import TestComponent from '#/components/TestComponent';
export default {
name: 'some-child-component',
computed: {
testComponent() {
return TestComponent;
}
}
};
</script>
Child element somewhere else (variant 3, storing the name, derived from the imported component, in Vuex; I would go with this variant):
<template>
<button #click="$store.commit('setHelpComponent', testComponentName)">Set v-help component to 'test-component'</button>
</template>
<script>
import TestComponent from '#/components/TestComponent';
export default {
name: 'some-child-component',
computed: {
testComponentName() {
return TestComponent.name;
}
}
};
</script>
Vuex Store:
state: {
helpComponent: null
},
mutations: {
setHelpComponent(state, value) {
state.helpComponent = value;
}
}
See also the documentation for dynamic components (<component :is=""> syntax): https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components