How to pass Image object as props in React? - javascript

I need to pass in an Image object from the page down to a component which calls a component for each image.
Home() -> ThreePanel() -> Panel()
From my understanding of props, this should be possible with the passing of props from ThreePanel to Panel.
But there is an error:
Server Error
Error: Image is missing required "src" property. Make sure you pass "src" in props to the next/image component. Received: {"height":"170em"}
This is what is the source of the error is. I need to specify a specific object. But as a template, I'd like to use a variable.
<Image src={props.cardData.icon} // <-- Doesn't let me specify generic variable
height="170em"/>
<Image src={props.cardData.icon.firstImage}
height="170em"/>
I'm using Next JS.
Here's the full code below:
Home.js
import ThreePanel from '../components/threePanel'
import firstImage from '../public/images/one.png'
import secondImage from '../public/images/two.png'
import thirdImage from '../public/images/three.png'
export default function Home() {
return (
<ThreePanel
panelOne = {{ icon: {firstImage}, name: "First" }}
panelTwo = {{ icon: {secondImage}, content: "Second" }}
panelThree = {{ icon: {thirdImage}, content: "Third" }}>
</ThreePanel>
)
}
threePanel.js
import Panel from '../components/panel'
export default function ThreePanel(props) {
return (
<Grid container>
<Grid item>
<Panel cardData={props.firstImage.icon}></Panel>
</Grid>
<Grid item>
<Panel cardData={props.secondImage.icon}></Panel>
</Grid>
<Grid item>
<Panel cardData={props.thirdImage.icon}></Panel>
</Grid>
</Grid>
)
}
panel.js
import Image from 'next/image'
export default function Panel(props) {
return (
<Image src={props.cardData} // <-- Doesn't let me specify generic variable
height="170em"/>
)
}

Issue
props.firstImage.icon isn't defined in the ThreePanel component as the image was passed as panelOne={{ icon: {firstImage}, name: "First" }}. This means the child component would need to access props.panelOne.icon.firstImage to get to the image. This may work if each Panel component is passed the correctly nested property.
<Panel cardData={props.panelOne.icon.firstImage} />
<Panel cardData={props.panelTwo.icon.secondImage} />
...etc...
As you can see, each child panel needs to know which panel it is, but also needs to know what the image property was named. This isn't ideal.
Solution
You should strive to pass consistently named props down to "repeated" children as each "instance" won't be aware which dynamic prop they should access. In this case it's the value passed that is dynamic, not the prop key.
Home
Pass the dynamic image values on a standard prop, like icon.
import firstImage from '../public/images/one.png';
import secondImage from '../public/images/two.png';
import thirdImage from '../public/images/three.png';
export default function Home() {
return (
<ThreePanel
panelOne={{ icon: firstImage, name: "First" }}
panelTwo={{ icon: secondImage, content: "Second" }}
panelThree={{ icon: thirdImage, content: "Third" }}
/>
)
}
ThreePanel
Access the "dynamic" panel prop to pass the specific panel prop object to each Panel component. Now the intermediate ThreePanel component doesn't need to know much of the deeper nested values, it knows to pass 1 of 3 panel prop objects to it's 3 panel children.
export default function ThreePanel(props) {
return (
<Grid container>
<Grid item>
<Panel cardData={props.panelOne} />
</Grid>
<Grid item>
<Panel cardData={props.panelTwo} />
</Grid>
<Grid item>
<Panel cardData={props.panelThree} />
</Grid>
</Grid>
)
}
Panel
Now the passed carData prop will be the specific image that was passed from grandparent/ancestor component. Notice here that each individual panel doesn't know which of the 3 panels it is, but it knows it has an image icon prop to access.
export default function Panel(props) {
return (
<Image
src={props.cardData.icon}
height="170em"
/>
)
}

In threePanel.js the panel props should be cardData=props.panelOne, isnt it? Instead of props.cardOne

Related

React - how to change properties of a component in another component

function DatTable() {
return (
<DataTable
theme="dark"
columns={columns}
selectableRows
data={FakeData}
/>
);
};
I've called this component from another file in a dashboard
</Box>
<DatTable></DatTable>
</Box>
Everything works properly if I change the properties in the original function. What I'm trying to achieve is set a useTheme hook for the component and for that I want to edit the theme inside of the dashboard like so :
</Box>
<DatTable
theme="dark"
></DatTable>
</Box>
I've tried changing the properties inside of the dashboard component but it has no effects. I'm also unsure how to turn regular components into React components since the webpage goes white and breaks if I try it that way.
// default data
let columns={};
let data={}
function DatTable(theme="dark", columns=columns, selectableRows='somedefaultdata',data=FakeData) {
return (
<DataTable
theme=theme
columns={columns}
selectableRows
data={FakeData}
/>
);
};
Now
<DatTable
theme="white"
/>
If you this now:
you don't need to send props at all.
If you send props those props will overwrite those default values
Since you pass a theme prop to <DatTable> you need to extract it and then use it as a prop again in <DataTable>
function DatTable({theme}) {
return (
<DataTable
theme={theme}
columns={columns}
selectableRows
data={FakeData}
/>
);
};

React controlled MUI form field components

I feel like I am missing the obvious but I can't seem to find another posts that addresses the issue I am having. I am trying to allow an end user the ability to set a location for an object but either entering the information into a form or by clicking a location on a map. I have a react page with three components.
A Parent container component that holds the other two components.
import {useState} from 'react';
import Box from '#mui/material/Box';
import ObjSetLocationForm from './ObjSetLocationForm';
import ObjSetLocationMap from './ObjSetLocationMap';
export default function LoadSetLocationSet( props ) {
const [prevLat, setPrevLat] = useState("43.5666694");
const [prevLng, setPrevLng] = useState("-101.0716746");
const [newLat, setNewLat] = useState();
const [newLng, setNewLng] = useState();
function setCoords(coords){
setNewLat(coords.lat);
setNewLng(coords.lng);
}
return(
<Box>
<ObjSetLocationForm prevLat={prevLat} prevLng={prevLng} newLat={newLat} newLng={newLng} setCoordsFx={setCoords} />
<ObjSetLocationMap prevLat={prevLat} prevLng={prevLng} newLat={newLat} newLng={newLng} setCoordsFx={setCoords} />
</Box>
);
}
A Map component that shows a single point on a map A and allows the end user to click on the map to set the point to a new location (lat/lng) and is a child of the Parent container. I will not post all the code to this component because it is pretty big but the important part is that when a user clicks on the map the new lat/lng values are passed back up to the parent component by calling the setCoords function like so.
props.setCoordsFx({lat: e.lngLat.lat, lng: e.lngLat.lng});
I have verified that this is working correctly and is passing the values correctly.
A Form component that is the second child of the parent container and allows the end user to enter a Lat/Lng to change the position of the single point on the map. This component is where I am having issues. I have two MUI TextFields in an html form that I want to set to the lat/lng values when the user clicks on the map. When I run through the debugger I can see the values getting passed down to this component from the parent and I can even see that the state values that control the components are getting set but the TextFields values never change.
import {useState, useEffect, useContext} from 'react';
import Box from '#mui/material/Box';
import Stack from '#mui/material/Stack';
import Typography from '#mui/material/Typography';
import Button from '#mui/material/Button';
import TextField from '#mui/material/TextField';
export default function LoadSetLocationSet( props ) {
const [newLat, setNewLat] = useState(props.newLat);
const [newLng, setNewLng] = useState(props.newLng);
function handleSubmit(e) {
e.preventDefault();
// Save values to DB.
}
return(
<Box>
<form id="SelLocationForm" onSubmit={handleSubmit}>
<Box sx={{textAlign:'center'}}>
<Typography variant="h6" sx={{display:'inline'}}>Current Location</Typography>
<Box sx={{display:'flex', alignItems:'center', justifyContent:'center'}}>
<Stack direction="row" spacing={3}>
<Box>
<Box>
<Typography>Latitude</Typography>
</Box>
<Box>
<Typography>{props.prevLat}</Typography>
</Box>
</Box>
<Box>
<Box>
<Typography>Longitude</Typography>
</Box>
<Box>
<Typography>{props.prevLng}</Typography>
</Box>
</Box>
</Stack>
</Box>
</Box>
<Box sx={{textAlign:'center', pt:2}}>
<Typography variant="h6" sx={{mb:2}}>Enter the Latitude and Longitude or click the new location on the map</Typography>
<Typography variant="h6" sx={{display:'inline'}}>New Location</Typography>
<Box sx={{display:'flex', alignItems:'center', justifyContent:'center'}}>
<Stack direction="row" spacing={3}>
<Box>
<TextField
id="tbLatitude"
label="Latitude"
type="text"
size="small"
value={newLat}
onChange={(e) => {setNewLat(e.target.value);}}
/>
</Box>
<Box>
<TextField
id="tbLongitude"
label="Longitude"
type="text"
size="small"
value={newLng}
onChange={(e) => {setNewLng(e.target.value);}}
/>
</Box>
<Box sx={{display:'flex', alignItems:'center', justifyItems:'center'}}>
<Button variant="contained" type="submit">Set</Button>
</Box>
</Stack>
</Box>
</Box>
</form>
</Box>
);
}
As you can see I am attempting to use controlled TextFields. So here are my questions/problems:
If setting the default value to a prop value is "anti pattern" how am I supposed to set the default value for form fields if they are a controlled form field?
As I stated earlier when the user clicks on a location on the map it should refresh the form child component and set the values for my controlled form fields to the values passed in but this is not working. How can I accomplish this?
I thought I understood things as I have been doing react for a little bit now but I seem to be lost. Sorry for the newbie question.
In your LoadSetLocationSet you are passing in newLat and newLng as props. You should also pass in setNewLat and setNewLng as props.
The problem is that you are defining newLat and newLng in two places:
export default function LoadSetLocationSet( props ) {
const [prevLat, setPrevLat] = useState("43.5666694");
const [prevLng, setPrevLng] = useState("-101.0716746");
const [newLat, setNewLat] = useState();
const [newLng, setNewLng] = useState();
and
export default function LoadSetLocationSet( props ) {
const [newLat, setNewLat] = useState(props.newLat);
const [newLng, setNewLng] = useState(props.newLng);
That is going to cause issues since you really only want one source of truth for that data, and things get messy and confusing if you try to keep track of that state in two separate places. Instead, only keep track of newLat and newLng in a single place. In React, the flow of data goes downward from parents to children, so we often want to lift state up
Remove these lines from the child form:
const [newLat, setNewLat] = useState(props.newLat);
const [newLng, setNewLng] = useState(props.newLng);
Then in the parent we will pass down setNewLat and setNewLng as props to the Form:
<ObjSetLocationForm prevLat={prevLat} prevLng={prevLng} newLat={newLat} newLng={newLng} setCoordsFx={setCoords} setNewLat={setNewLat} setNewLng={setNewLng} />
<ObjSetLocationMap prevLat={prevLat} prevLng={prevLng} newLat={newLat} newLng={newLng} setCoordsFx={setCoords} />
Then you just have to adjust the onChange functions in your TextFields to use the setNewLat and setNewLng functions that are passed in as props. You will also need to change value to use the data passed from props in order to set the default/initial value of those fields:
<TextField
id="tbLatitude"
label="Latitude"
type="text"
size="small"
value={props.newLat}
onChange={(e) => {props.setNewLat(e.target.value);}}
/>
</Box>
<Box>
<TextField
id="tbLongitude"
label="Longitude"
type="text"
size="small"
value={props.newLng}
onChange={(e) => {props.setNewLng(e.target.value);}}
/>
The main idea is to keep track of data in only one place, often a parent component. Even though we store data in the parent, we can still update state from a child by passing down a callback to the child as a prop.

array.map() does not render items horizontally in a grid container in React

I'm using React and material-ui.
My goal is to render a grid container, starting from an external javascript file, that exports its own array. This grid must have 3 items per row, but at the moment it just renders the items on a single column.
Here is the code:
import React from "react";
import CoffeeCard from "./CoffeeCard";
import { Grid } from "#material-ui/core";
import files from "./constants";
function Content() {
return (
<Grid direction="rows" container spacing={2}>
<Grid item xs={12} sm={4}>
{files.map((obj) => {
return (
<CoffeeCard
title={obj.title}
price={obj.price}
description={obj.description}
avatarUrl={obj.avatarUrl}
imgSrc={obj.imgSrc}
/>
);
})}
</Grid>
</Grid>
);
}
export default Content;
You need to move the nested Grid component for individual items inside the map function. Right now your code is only rendering 1 Grid item component with children , you need to render one for each row:
<Grid direction="rows" container spacing={2}>
{files.map((obj) => {
return (
<Grid item xs={12} sm={4}>
<CoffeeCard
title={obj.title}
price={obj.price}
description={obj.description}
avatarUrl={obj.avatarUrl}
imgSrc={obj.imgSrc}
/>
</Grid>
);
})}
</Grid>
</Grid>
The issue is how you are using the <Grid /> component from Material UI. Please refer to its documentation on Grid for more info.
Notably, you want your item to wrap each individual item. As you have it, you have a single item that wraps your content.
So just move your <Grid item> into your .map return value:
import React from 'react';
import CoffeeCard from './CoffeeCard';
import { Grid } from '#material-ui/core';
import files from './constants';
function Content() {
return (
<Grid direction="rows" container spacing={2}>
{files.map((obj) => {
return (
<Grid item xs={12} sm={4}>
<CoffeeCard
title={obj.title}
price={obj.price}
description={obj.description}
avatarUrl={obj.avatarUrl}
imgSrc={obj.imgSrc}
/>
</Grid>
);
})}
</Grid>
);
}
export default Content;

How to change size of tabs components in Materials-UI?

I am working on a simple app to display heart rate, blood glucose levels and several other measurements. I am working in React and Redux, and using Materials-UI for UI.
To display these two numbers, I am creating a component that will be on each half of the screen. Under each number, I will have a set of tabs to switch within views of the component.
I want to resize the tabs to be the correct size, but it is too large currently and takes up too much space. How can I resize the tabs to be smaller. I have the code below for creating tabs and have read through this and this but am unsure on how to do this.
I attempted use withStyles(styles) as shown below, but it doesn't work properly.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import { withStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
const BottomTab = ({}) => (
<Grid item direction="column" alignItems="center" justify="center">
<Tabs
value={0}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
>
<Tab label="Current" />
<Tab label="Set New" />
<Tab label="Alarm" />
</Tabs>
</Grid>
);
const styles = {
tab: {
width: '10', // a number of your choice
}
};
export default withStyles(styles)(BottomTab);
and the following code block is where I call the BottomTab.
interface Props {
labelId: string,
value: number
}
const Knob = ({labelId, value}: Props) => (
<Grid item container direction="row" alignItems="center" justify="center">
<Grid item xs>
<ValueDisplay labelId={labelId} value={value} />
</Grid>
<Grid item container direction="column" alignItems="center" justify="space-evenly">
<BottomTab />
</Grid>
</Grid>
)
export default Knob
In BottomTab, you should create a class in with a property minWidth: <your tab size>, and then use this class to style Tab component, like this:
const useStyles = makeStyles((theme) => ({
...
tabs: {
'& button': {
minWidth: 50
}
}
}));
const BottomTab = ({}) => {
const classes = useStyles()
return (
<Grid item direction="column" alignItems="center" justify="center">
<Tabs
value={0}
indicatorColor="primary"
textColor="primary"
variant="fullWidth"
className={classes.tabs}
>
<Tab label="Current" />
<Tab label="Set New" />
<Tab label="Alarm" />
</Tabs>
</Grid>
)
};
Have you tried adding component to its own grid and then changing the size.
Might not work, but worth a try.
Note: sorry for answering, but I can't comment yet due to reputation restriction.

React: Cannot read property 'map' of undefined

Code is here.
What I'm trying to do:
Trying to have the list of movies display, when fetched with Axios, to the area on the right of the app.
I'm not sure if this is a passing prop issue, or if it's something else. Any hints as to what's going on?
import React from "react";
import Card from "#material-ui/core/Card";
import Grid from "#material-ui/core/Grid";
import "../Movie/_movie.scss";
const Movie = props => {
return (
<div>
<Grid container>
<Grid item xs={12} md={12}>
<Card>
<h3>Movies should show up here</h3>
<ul>
{props.movies.map(movie => (
<li key={movie.id}>
<img
src={`https://image.tmdb.org/t/p/w185/${movie.poster_path}`}
/>
<h3>{movie.original_title}</h3>
<p>Date: {movie.release_date}</p>{" "}
<p>Rating: {movie.vote_average}/10</p>
<p>{movie.overview}</p>
</li>
))}
</ul>
</Card>
</Grid>
</Grid>
</div>
);
};
export default Movie;
What's most likely happening is your render function is running before the api call has had time to complete, thus props.movies is undefined at the time when render needs it. What you can do is either give props.movies a default value of
an empty array [] or you can first check to see if props.movies is defined before mapping by doing props.movies && props.movies.map.
While accessing movies object add check props.movies && props.movies.map(movie=>

Categories

Resources