Currently this two components are child components, so what I want to achieve here is to pass the props from Search component to Images component. Because I wrote everything in a function component, I thought this might be a great place for me
When user enters something in the input field it will automatically search for new images. In order to give you a better understanding of my issue, I will put a link to Codesandbox here. So I want to pass the props to the sibling component, so it will output the filtered search result.
https://codesandbox.io/s/patient-dawn-khouk
So only files you need to look at are: Search.js, Images.js
Ensure your images prop is actually an array. Can you do some debugging to figure out what your images object actually is?
Using propTypes is a way to have react help, and if it is nullable (i.e. not isRequired), then use a guard on it:
import React from "react";
import PropTypes from 'prop-types';
import Image from "./Image";
const propTypes = {
images: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired, // used as key
src: PropTypes.string.isRequired, // used for image source
<... other object properties ...>
})
),
};
const Images = ({ images }) => {
const images = images && images.map((image) => {
return <Image key={image.id} image={image}/>
});
return <div className="images">{images}</div>;
}
Images.propTypes = propTypes;
export default Images;
Related
PLEASE SEE UPDATE BELOW
This is my first time posting on stack overflow because usually I can find answers but I simply can't this time. I'm using React for the first time in a few months and just trying to show some images on the page. The imports are not working at all.
Here is where I put them
filestructure
I created an index.js inside in order to export all of them at once:
const images =
{
image1: require("./001.jpg").default,
image2: require("./002.jpg").default,
image3: require("./003.jpg").default,
image4: require("./004.jpg").default,
image5: require("./005.jpg").default,
image6: require("./006.jpg").default,
image7: require("./007.jpg").default,
image8: require("./008.jpg").default,
image9: require("./009.jpg").default
}
export default images;
I also tried similar things with an array (which is what I wanted) and with ES6 imports in case that was the issue.
I then have attempted to use them here:
import React from 'react'
import images from "../photos/index.js"
import Photo from "./Photo";
function PhotoList() {
const photoComponents = Object.values(images).map(
image => {
return <Photo photo={image}/>
}
)
return(
<div>
{photoComponents}
</div>
)
}
export default PhotoList
I have also tried to display just a single image using directly here and that also is broken, so it's not the Photo component that's broken, and apparently not the mapping either. It's just the imports.
And here we have the page. Everything else shows:
import React from 'react'
import Navbar from './Navbar';
import "./main.css";
import PhotoList from "./Photo";
function mainPage() {
return (
<div className="mainPage">
<h1>Jei Ganiyeva</h1>
<Navbar/>
<PhotoList/>
</div>
)
}
export default mainPage;
And it shows up like this:
broken image on site
What is up with this? I can't seem to find any answers apart from people importing things wrong which I am not doing. Well, I assume I'm doing something wrong, but not in the way they are.
Thank you in advance for your help.
UPDATE:
It appears the Photo component is not receiving any props no matter what it is. See here, I have replaced the mapping with passing a simple integer variable and simply showing it as a h1 in Photo:
import React from 'react'
import {images} from "../resources/photoInfo.js"
import Photo from "./Photo";
function PhotoList() {
// const photoComponents = images
// .map(image => {
// return(
// <Photo className="photo" source={image}/>
// )
// });
const image = 100;
return <div><Photo source={image}/></div>
}
export default PhotoList
import React from 'react'
function Photo({source}) {
return (
<div>
<h1>{`The props are ${source}`}</h1>
</div>
)
}
export default Photo
This results in the following screen:
Okay, I have found the solution to this and am answering myself just for a reference for anyone who googles this. However, I doubt anyone will have exactly the same problem.
Basically, in the main page, you can see above that I'm actually importing PhotoList from "./Photo" not "./PhotoList" as intended. This means that the logic held within PhotoList that passed the props to Photo was not being used. This also explains why I never saw anything in the console when I tried to console.log from PhotoList. So, if anyone else does have a mystery problem like this, check whether all parts of your code are being reached at all!
I have a functional component below which has props coming from parent component and I added propTypes for the whole props object. However, the lint fails with the below error message.
9:16 error 'data' is missing in props validation react/prop-types
9:22 error 'data.text' is missing in props validation
Excerpt from code
import PropTypes from 'prop-types'
const Child = (props) => <div>{props.data.text}</div>
Child.propTypes = {
props: PropTypes.object
}
Could anyone please help?
As discussed in the comments, you have a few issues:
PropTypes.object is a very vague type declaration, and most linters will ding you for that
You are referencing properties of that object inside of your functional component that are not declared in your proptypes
You are using your prop declaration to attempt to refer to the props argument as a whole, rather than the properties within.
A more correct way to write all of this would be like so:
import PropTypes from 'prop-types';
/* Note we destructure `data` directly from props in the args */
const Child = ({ data }) => (<div>{data.text}</div>);
Child.propTypes = {
data: PropTypes.shape({ // use `shape` to allow us to declare types of properties within
text: PropTypes.string, // define the text property's type
}),
}
Additionally, you may want to define some of these items as required, or provide default entries.
From Alexander Nied solution, I made it work writing the below way without using shape, I think it is more generic.
const Child = ({ data }) => (<div>{data.text}</div>);
Child.propTypes = {
data: PropTypes.object
}
Trying next with layout pattern:
https://github.com/zeit/next.js/tree/canary/examples/layout-component
And the problem is that Layout component get remounted on every page change. I need to use layout component as a Container so it'll fetch data from server on every mount. How can I prevent layout to get re-mounted? Or am I missing something there?
This helped me for persistent layouts. The author puts together a function that wraps your page components in your Layout component and then passes that fetch function to your _app.js. This way the _app.js is actually the components that renders the Layout but you get to specify which pages use which layout (in case you have multiple layouts).
So you have the flexibility of having multiple layouts throughout your site but those pages that share the same layout will actually share the same layout component and it will not have to be remounted on navigation.
Here is the link to the full article
Persistent Layout Patterns in Next.js
Here are the important code snippets. A page and then _app.js
// /pages/account-settings/basic-information.js
import SiteLayout from '../../components/SiteLayout'
import AccountSettingsLayout from '../../components/AccountSettingsLayout'
const AccountSettingsBasicInformation = () => (
<div>{/* ... */}</div>
)
AccountSettingsBasicInformation.getLayout = page => (
<SiteLayout>
<AccountSettingsLayout>{page}</AccountSettingsLayout>
</SiteLayout>
)
export default AccountSettingsBasicInformation
// /pages/_app.js
import React from 'react'
import App from 'next/app'
class MyApp extends App {
render() {
const { Component, pageProps, router } = this.props
const getLayout = Component.getLayout || (page => page)
return getLayout(<Component {...pageProps}></Component>)
}
}
export default MyApp
If you put your Layout component inside page component it will be re-remounted on page navigation (page switch).
You can wrap your page component with your Layout component inside _app.js, it should prevent it from re-mounting.
Something like this:
// _app.js
import Layout from '../components/Layout';
class MyApp extends App {
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext);
return {
...appProps,
};
}
render() {
const { Component, pageProps } = this.props;
return (
<Layout>
<Component {...pageProps} />
<Layout />
);
}
}
export default MyApp;
Also, make sure you replace all the to <Link href=""></Link>, notice that only have change the Html tag to link.
I struggled because with this for many days, although I was doing everything else correctly, these <a> tags were the culprit that was causing the _app.js remount on page change
Even though this is the topic Layout being mounted again and again, the root cause of this problem is that you have some data loaded in some child component which is getting fetched again and again.
After some fooling around, I found none of these problem is actually what Next.Js or SWR solves. The question, back to square one, is how to streamline a single copy of data to some child component.
Context
Use context as a example.
Config.js
import { createContext } from 'react'
export default createContext({})
_App.js
import Config from '../Config'
export default function App({ Component, pageProps }) {
return (
<Config.Provider value={{ user: { name: 'John' }}}>
<Component {...pageProps} />
</Config.Provider>
)
}
Avatar.js
import { useContext } from 'react'
import Config from '../Config'
function Avatar() {
const { user } = useContext(Config)
return (
<span>
{user.name}
</span>
)
}
export default Avatar
No matter how you mount and dismount, you won't end up with re-render, as long as the _app doesn't.
Writable
The above example is only dealing with readable. If it's writable, you can try to pass a state into context. setUser will take care the set in consumer.
<Provider value={useState({})} />
const [user, setUser] = useContext(Config)
setUser is "cached" and won't be updated. So we can use this function to reset the user anytime in child consumer.
There're other ways, ex. React Recoil. But more or less you are dealing with a state management system to send a copy (either value or function) to somewhere else without touching other nodes. I'll leave this as an answer, since even we solved Layout issue, this problem won't disappear. And if we solve this problem, we don't need to deal with Layout at all.
I want to define a bunch of data in a constants file which will be used to render a series of React components, including some HTML, which I'd like to be able to write in JSX. Below is a simplified example of what I'd like to do:
// constants.ts
export interface ItemInfo {
title: string;
description: React.ReactElement; // Or whatever this type should be. Just trying to get it working for now, can figure out correct typing later.
}
export const DATA: ItemInfo[] = [
{
title: 'Foo',
// Pseudo code below, how can I get this working?
description: (
<>
<p>Some JSX.</p>
<p>To be rendered in a React component.</p>
</>
),
},
{
title: 'Bar',
description: (
<>
<p>More JSX.</p>
<p>To be rendered in a React component.</p>
</>
),
},
// etc
];
// ItemComponent.tsx
import React from 'react';
import { ItemInfo } from './constants';
const ItemComponent: FC<ItemInfo> = ({title, description}) => (
<div>
<h2>{title}</h2>
<div>{description}</div>
</div>
);
// ListComponent.tsx
import React from 'react';
import { ItemInfo } from './constants';
const ListComponent: FC<ItemInfo[]> = ({items}) => (
<div>
{items.map((item) => <ItemComponent {...item} />)}
</div>
);
I'm using TypeScript, so I've done the simplified example above in TS as well, though I don't think it should matter. I've tried importing React in the constants.ts file, and using React.createElement() on the JSX, but to no avail. I can just move the DATA constant inside of the ListComponent, in which case everything works, but I want to decouple the data from the component, so that it can be used to render different lists of data in different places.
I'm open to suggestions about avoiding using this pattern (in which case please offer reasons why and alternatives), but if it is possible to do this I'd still also like to know how in addition to knowing why I shouldn't and what I should do instead :)
Any insights appreciated, thanks!
Oh, actually I just figured it out. All I had to do was change the constants.ts file to a constants.tsx file.
I'll leave this question up in case it's helpful to anyone else since I didn't find great results when Googling this question (probably because it was such an obvious mistake haha).
If anyone does have comments on whether and why this pattern should or shouldn't be used, I am also still interested.
I'm using react-i18next and react-navigation.
Currently I wrap all my components with withNamespaces individually, when there's a need. The issue is that I can't keep the title in static navigationOptions up to date. It just doesn't update, no matter how I assign it: as a function or as properties object. The navigation.setParams does not update it as well.
I tried using withNamespaces on Navigators themselves to make use of screenProps, as it's done here, but in this case my dispatched NavigationActions have no effect. The navigation just doesn't happen.
I assume that i18next HOC somehow prevents its children from receiving params update events. Do I need to initialise the i18next in some other way to resolve this? Or is there a way to force the title in navigationOptions to update?
Ok, I came up with a simple way to solve this. I made a component that just returns the required string, and I wrapped it with withNamespaces and put it into title prop of navigationProperties. Works fine.
Here's an example code.
Title Component
import React from 'react'
import PropTypes from 'prop-types'
import { withNamespaces } from 'react-i18next'
import { Text } from 'react-native'
const ScreenTitle = ({ path, t }) => <Text>{t(path)}</Text>
ScreenTitle.propTypes = {
path: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
}
export default withNamespaces()(ScreenTitle)
Usage
static navigationOptions = () => {
return {
title: <ScreenTitle path="privacyPolicy:title" />,
}
}