ReactJS MUI Component not rendering inside map function - reactjs

I am using ReactJS along with Material UI Components. I am trying to render a custom element using the below code. The options array is never empty and the console logs are showing as expected - "Adding element to grid ..". However, the element is not rendered on the browser(I checked the browser Inspector to confirm).
What am I missing?
import React from "react";
import Container from "#mui/material/Container";
import Grid from "#mui/material/Grid";
const Element = (props) => {
const {options} = props;
return(
// If I simply pass a JSX component at this location, it renders fine.
// But not inside the map function
{options.map((opt) => {
opt.elements.length > 0 && (
<Grid container spacing={4}>
{opt.elements.map((el) => (
<Grid item xs={12} sm={6}>
{console.log("Adding element to grid ..", el.element_name)}
<h1>{el.element_name}</h1>
</Grid>
))}
</Grid>
);
})}
)
}

You should use parentheses instead of curly brackets inside the first map in the return()
{options.map((opt) => (
opt.elements.length > 0 && (
<Grid container spacing={4}>
{opt.elements.map((el) => (
<Grid item xs={12} sm={6}>
{console.log("Adding element to grid ..", el.element_name)}
<h1>{el.element_name}</h1>
</Grid>
))}
</Grid>
);
))}

Related

Uncaught TypeError: user.toLowerCase is not a function

okay guys so new update. so i did jus that . i moved all the elements to the rendering and tried to just get the state to be alone. when i tried that it told me that map wasnt able to read properties of undefined. so what i did was added the object keys . the page rendered ... kinda. it rendered without all the data. i attached the updated code in here but now im kinda confused because its not showing an error this time .its just not displaying the info. can someone tell me what im doing wrong here ?
The error message tells you that user.toLowerCase is not a function, indicating that user is not a string as toLowerCase only works with strings. Instead, user is the ul element you create in the setInfo(json.students.map()) call in the useEffect. It's not ideal to store DOM elements in state for this reason as it's not easy to work with. Instead, leave your info state variable as an array of objects, and move the logic for creating DOM elements in your return.
In addition, I would not change your state when you filter as you would not be able to easily get the original, unfiltered state values back. Keep track of your filters in state and do conditional rendering based on the filters in your return.
You setting your user info as a ul html list. Instead set the userInfo the json response and then map the ul list when you render the component.
I'm not sure what your user json looks like, so you might have to play around with that.
import React, { useEffect, useState } from "react";
import Card from "#material-ui/core/Card";
import CardContent from '#material-ui/core/CardContent';
import Grid from "#material-ui/core/Grid";
import { Input } from "#material-ui/core";
function StudentProfiles() {
const [info, setInfo] = useState();
const [search, setSearch] = useState('');
useEffect(() => {
fetch("https://api.hatchways.io/assessment/students")
.then(response => response.json())
.then(json => setInfo(json))
},[]);
const average = (array) => array.reduce((a,b) => a + b )/ array.length;
const filter = (e) => {
const keyword = e.target.value;
if(keyword !== '') {
const results = info.filter((user) => {
return user.students.toLowerCase().startsWith(keyword.toLowerCase());
});
setInfo(results);
} else {
setInfo(info)
}
}
return (
<div>
<Card className="card">
<CardContent className="scrollbar scrollbar-primary mt-5 mx-auto">
<Input
className="searchBar"
icon="search"
placeholder="Search by name"
onChange={filter}
/>
{ user.students.map((name) => (
<ul className = "border" key={name.id}>
<Grid item xs={3} sm={6} md={12} style={{display: "flex", gap:"3.5rem", paddingBottom:"8px"}}>
<img alt ="" src={name.pic} className="picture"></img>
<Grid container style={{display: "inline"}} align="left" justify="flex-end" alignItems="flex-start">
<Grid className="studentNames">
<span>{name.firstName + " " + name.lastName}</span>
</Grid>
<span>{name.email}</span>
<br/>
<span>{name.company}</span>
<br/>
<span>{name.skill}</span>
<br/>
<span>Average: {average(name.grades).toFixed(3)}%</span>
</Grid>
</Grid>
</ul>
)))}
</CardContent>
</Card>
</div>
)
}
export default StudentProfiles;

set images in grid of react dropzone uploaders

I am confused in setting custom preview as like
image set in grid
I want to do this in react-dropzone-uploader custom preview, where I can upload multiple files.
Current scenario: my normal uploaded images
Code snippet:
const Preview = ({ meta }: IPreviewProps) => {
const { name, percent, status, previewUrl } = meta ;
return (
<Grid item xs={6}>
<div className="preview-box">
<img src={previewUrl} /> <span className="name">{name}</span> - <span className="status">{status}</span>{status !== "done" && <span className="percent"> ({Math.round(percent)}%)</span>}
</div>
</Grid>
)
}
<Dropzone
getUploadParams={getUploadParams}
onSubmit={handleSubmit}
PreviewComponent={Preview}
inputContent="Drop Files"
/>
I am using MUI V4
We don't have any index for images, how can I set images in grid view of 2-2 images using material UI?
You need to wrap the Grid items inside the Grid container.
Please have a look at the guide. https://mui.com/components/grid/
// Parent PreviewContainer.js
...
<Grid container spacing={2}>
{
images.map((image, i) => (
<PreviewItem key={i} meta={meta} />
))
}
</Grid>
...
// Child PreviewItem.js
...
<Grid item xs={4}>
// You can update the item size as you expect, but it seems like it should be 4 regarding the image attached
// Your preview component
</Grid>
...

React Material UI Grid Item doesn`t render after data update

I'm unable to make following code render grid items when props.data changes from the top component.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Grid from '#material-ui/core/Grid';
import ProductCard from './ProductCard';
const useStyles = makeStyles((theme) => ({
grid: {
padding: "8px",
},
}));
export default function CenteredGrid(props) {
const classes = useStyles();
const visibleProductData = props.data === null ? {} : props.data;
return (
<Grid container >
{console.log("This is from the product card grid")}
{console.log(visibleProductData)}
{Object.entries(visibleProductData).map(productData => (
<Grid key={productData[0]} className={classes.grid} item md={3} sm={6} xs={12}>
<ProductCard data={productData[1]}/>
</Grid>
))}
</Grid>
);
}
When I run this, after the data updates, the console logs visibleProductData which is a dictionary consisting of three products, as expected. However these products are not visible, in fact when I inspect I see no children for Grid container. What is weird is that, even after small changes in code, when a fast refresh occurs products become visible. What might be the issue here ?
PS: I'm using nextjs along with material ui.
Edit / Update - Parent Component
const classes = useStyles();
const { buyer, categoryData, filterData, visibleProductData } = useContext(BuyerContext);
if (!buyer) {
return (
<AuthRequired/>
)} else {
return (
<>
<HeaderBar/>
<Grid className={classes.breadcrumb} container>
<Breadcrumb />
</Grid>
<Divider variant="middle" />
<main className={classes.main}>
<Grid container>
<Grid item xs={2}>
<Box display={{ xs: 'none', sm: 'block' }}>
<CategoryList data={categoryData}/>
</Box>
</Grid>
<Grid item sm={10} xs={12}>
<FilterGrid data={filterData}/>
<ProductCardGrid data={visibleProductData}/>
</Grid>
</Grid>
</main>
<Footer/>
</>
)
}
}
Try the following line,
const visibleProductData = props.data === null ? {} : {...props.data};
It might be because your visibleProductData variable is always getting the same reference object. You need to create a new reference object each time props.data changes. If the issue still persists, then we need to see your parent component. The issue might be there.
Writing const visibleProductData = props.data === null ? {} : props.data; in React functional component body is not the correct "React way". You should:
define a local state variable called, for example, visibleProductData:
const [visibleProductData, setVisibleProductData] = useState({});
use useEffect hook to "listen" new values comes from parent object. Something like:
useEffect(() => {
setVisibleProductData(props.data === null ? {} : {...props.data});
}, [props.data]);
In this way, every time props.data changes, useEffect will be fired and it will update local visibleProductData.
I could at last solve the problem, it was a small typo that gives no error and therefore hard to debug. Instead of putting another "(" within map like so, {array.map(element => (...))} I should have done without it like this {array.map(element => ...)}.

React useCallback and useEffect at the same time messed the state

I created a SignaturePad for my application that will pass the value later on to Formik.
Problem 1:
I have some issue with using the useEffect and useCallback function of react. Previously before I add useEffect, handleClear function works just fine. However after I added useEffect, sigPad refs always returns null in handleClear.
I'm not sure if my mental model is correct, can anyone please explain why is this happening?
Problem 2:
I realised however, after I removed the [sigPad] at handleClear I am able to receive the ref again. Which part of my code re-renders and how does useCallback not realise that sigPad is changing from null to the correct ref?
Problematic code:
export function Signature() {
let sigPad = useRef(null);
const [sig, setSig] = useState("");
const classes = formStyles();
useEffect(() => {
console.log(sigPad);
setSig(sigPad.toData());
console.log(sigPad);
}, [sig]);
const handleClear = useCallback(() => {
console.log(sigPad);
if (sigPad) sigPad.clear();
}, [sigPad]);
return (
<div className="row">
<Grid spacing={3} container>
<Grid item xs={12}>
<h3 className="text-bold">Signature</h3>
</Grid>
<Grid item xs={12}>
<div className="sigCanvas">
<SignatureCanvas
penColor="black"
canvasProps={{ className: "sigPad" }}
ref={ref => {
sigPad = ref;
}}
/>
</div>
</Grid>
<Grid item xs={3}>
<Button
variant="contained"
component="label"
className={classes.instructions}
onClick={handleClear}
>
Clear
</Button>
</Grid>
<Grid item xs={3}>
<Button
component="label"
variant="contained"
className={classes.instructions}
>
Save
</Button>
</Grid>
</Grid>
</div>
);
}
Current Fix:
const handleClear = useCallback(() => {
console.log(sigPad);
if (sigPad) sigPad.clear();
});
Note: Before I add useEffect I don't have to remove the [sigPad] at my callback
Hooks are executed in the order they are called, this means that your useEffect takes precedence over your useCallback function. Also, this [] is called the dependency array and is used to create new instances of the underlying structure. When using useRef you don't need to do this
ref={ref => { sigPad = ref; }}
You can just do
ref={sigPad}
And try moving your useCallback above useEffect.

How to access child nodes of a React components to add a wrapper components to them

I have a layout component GridLayout that receives a react component WigdetsList. That component is a functional component that returns a list of components.
I would like to be able to add a wrapper component to each of this component's child nodes in the list.
If I use {WigdetsList()} instead <WigdetsList /> it would work since it becomes a function that returns an array. But I would like to be able to use a react component there if possible. Therefore it becomes a single child.
I do not wish to put <Col key={i} xs={12} lg={4}> in the WidgetsList since I want to separate concerns. Layout components to do layouting and functionality components that are not coupled to specific layouts components.
Here is the codesandbox: https://codesandbox.io/s/suspicious-brown-u9yr1
App:
function App() {
return (
<GridLayout>
<WigdetsList />
</GridLayout>
);
}
WidgetsList:
function WidgetsList() {
return widgets.map(widget => (
<StyledWidget key={widget.id}>widget {widget.id}</StyledWidget>
));
}
GridLayout:
function GridLayout({ children }) {
return (
<Grid>
<Row>
{React.Children.map(children, (child, i) => (
<Col key={i} xs={12} lg={4}>
{child}
</Col>
))}
</Row>
</Grid>
);
}
Thanks for your time and help.
WidgetsList does not need to be a React component. It returns a list of React components, without any render logic. So, it should be a function or inline within App itself:
function App() {
return (
<GridLayout>
{widgets.map(widget => (
<StyledWidget key={widget.id}>widget {widget.id}</StyledWidget>
))}
</GridLayout>
);
}
If WidgetsList was rendered as a table--for example--then the Col's should be created inside of it, since the component now cares about its own layout.

Resources