How to make a custom search input reusable? - javascript

In my application, one of the most used elements is the search input, which makes it possible to search for products, topics, categories, etc.
I have already created the search input element, see below.
How to make the navbar search input reusable so that I can use it in other places?
import React from 'react'
import { Box, FormControl, InputBase } from '#mui/material'
import { styled } from '#mui/material/styles';
import { ContainedButton } from '../../ui-kit';
const StyledInput = styled(InputBase)(({ theme }) => ({
color: 'inherit',
border: 'none',
outline: 'none',
'& .MuiInputBase-input': {
maxHeight: '30px',
padding: '4px 16px',
maxWidth: '100%',
borderLeft: `1px solid ${theme.palette.grey[100]}`,
fontSize: 16,
lineHeight: 1.125,
color: theme.palette.text.primary,
},
width: '100%',
}));
const CONTAINED_BUTTON_STYLED = {
minWidth: '111px',
borderRadius: '0 2px 2px 0',
};
export const SearchInput = () => {
return (
<Box>
<FormControl sx={{ flexDirection: 'row', ml: '16px', width: '100%' }}>
<StyledInput placeholder="Search on Website" />
<ContainedButton sx={CONTAINED_BUTTON_STYLED} title="Search" />
</FormControl>
</Box>
)
}

You can simply expose all of the InputBase props as props of your custom SearchInput component, so that you maintain the same full API, and apply these props on the underlying InputBase component (possibly styled), typically using the JSX spread attributes syntax:
If you already have props as an object, and you want to pass it in JSX, you can use ... as a “spread” syntax to pass the whole props object.
export const SearchInput = (props: React.ComponentProps<typeof InputBase>) => {
return (
<Box>
<FormControl sx={{ flexDirection: 'row', ml: '16px', width: '100%' }}>
<StyledInput placeholder="Search on Website" {...props} />
<Button sx={CONTAINED_BUTTON_STYLED} title="Search" />
</FormControl>
</Box>
)
}
Then you can use it as a "normal" InputBase:
function ParentComponent() {
return (
<SearchInput value="hello" onChange={(event) => console.log(event)} />
)
}
Playground Link

Related

Object is possibly "null" in event.target.files in typescript

I want to upload a csv file and later parse it in json format, I have created a state in which i am setting the file using the HTMLEvent Property.I am using typescript I have defined the possible types while defining the state but I am still getting the error.
import {
Box,
Button,
IconButton,
Modal,
Stack,
LinearProgress,
Typography
} from '#mui/material';
import React, { useState } from 'react';
import UploadIcon from '#mui/icons-material/Upload';
import CancelIcon from '#mui/icons-material/Cancel';
import { csvFileParser } from '../../../utils/csvFileParser';
type Props = {};
const BulkServiceUpload = (props: Props) => {
const [openModal, setOpenModal] = useState(false);
const [filename, setFileName] = useState<File | undefined | null>();
console.log(filename);
const style = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 600,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
borderRadius: '1rem',
borderWidth: 0,
p: 4
};
const serviceMasterParser = async () => {
if (filename === null) {
alert('Please select a file');
} else {
const parsedCSVData: object[] = csvFileParser(filename);
console.log(parsedCSVData);
}
};
return (
<>
<Box>
<Button
variant="contained"
color="success"
onClick={() => setOpenModal(true)}
>
<UploadIcon />
Add Service
</Button>
</Box>
<Modal open={openModal} onClose={() => setOpenModal(false)}>
<Stack padding={3} sx={style} spacing={3}>
<Typography marginY={3} variant="h5" fontWeight={600}>
Upload Service in Bulk
</Typography>
<Box
sx={{
border: 'dashed 1px black',
borderRadius: '1rem',
position: 'relative',
background: '#f0f0f0',
'&:hover': {
backgroundColor: '#EFFAF5'
}
}}
>
<Box position="absolute" width="100%" height="100%" p={3}>
<Box display="flex" justifyContent="center" alignItems="center">
<UploadIcon />
<Typography>Bulk Upload Service Master</Typography>
</Box>
<Typography fontSize="10px" textAlign="center">
only .csv file is supported
</Typography>
</Box>
<input
type="file"
accept=".csv"
onChange={(e: React.FormEvent<HTMLInputElement>) =>
setFileName((e.target as HTMLInputElement).files[0]!)
}
style={{
width: '100%',
height: '150px',
cursor: 'pointer',
opacity: '0'
}}
/>
<Box
p={1}
display={`${filename === null ? 'none' : 'flex'}`}
justifyContent="space-between"
alignItems="center"
sx={{
background: '#f5f5f5',
borderRadius: '15px'
}}
margin={2}
>
<Typography fontSize={14}>{filename?.name!}</Typography>
<IconButton
color="inherit"
aria-label="delete file"
component="label"
onClick={() => {
setFileName(undefined);
}}
>
<CancelIcon fontSize="small" />
</IconButton>
</Box>
{/* <LinearProgress variant="determinate" value={50} /> */}
</Box>
<Button
variant="contained"
color="success"
onClick={() => serviceMasterParser()}
>
<UploadIcon />
Bulk Upload
</Button>
<Button color="success" fullWidth variant="contained">
Add Single Service
</Button>
</Stack>
</Modal>
</>
);
};
export default BulkServiceUpload;
I don't want any initial value for my file.But If i will take useState value empty it will be by default an undefined value. Which is creating this problem. Please let me know how can I solve this .This is my first question so if I have made any mistake my appologies in advance.
Make sure files exists in the target
onChange={(e: React.FormEvent<HTMLInputElement>) =>
setFileName((e.target as HTMLInputElement)?.files?.[0])
}
Hi Everyone I think I have made a small typo error while setting the value of a file
<input
type="file"
accept=".csv"
onChange={(e: React.FormEvent<HTMLInputElement>) =>
setFileName((e.target as HTMLInputElement).files[0]!)
}
style={{
width: '100%',
height: '150px',
cursor: 'pointer',
opacity: '0'
}}
/>
I should have used the ! after files and it resolved the issue
<input
type="file"
accept=".csv"
onChange={(e: React.FormEvent<HTMLInputElement>) =>
setFileName((e.target as HTMLInputElement).files![0])
}
style={{
width: '100%',
height: '150px',
cursor: 'pointer',
opacity: '0'
}}
/>

Required Details not showing

I am trying to display a large version of my post in the postDetail.jsx file but it shows blank when i Open it on my web browser. the application is being fetched properly as shown on my network tab in developer option and no form of error is printed on the console.
import React, { useEffect } from 'react';
import { Paper, Typography, CircularProgress, Divider } from '#material-ui/core/';
import { useDispatch, useSelector } from 'react-redux';
import moment from 'moment'; //js library for time
import { useParams, useHistory } from 'react-router-dom';
import { getPost } from '../../actions/posts';
import useStyles from './styles'
const PostDetails = () => {
const { post, posts, isLoading } = useSelector((state) => state.posts);
const dispatch = useDispatch();
const history = useHistory();
const classes = useStyles();
const { id } = useParams();
useEffect(() => {
dispatch(getPost(id));
}, [id]);
if(!post) return null;
//if this return this else return jsx
if(isLoading) {
return (
<Paper elevation={6} className={classes.loadingPaper}>
<CircularProgress size="7em" />
</Paper>
);
}
return (
<Paper style={{ padding: '20px', borderRadius: '15px' }} elevation={6}>
<div className= {classes.card}>
<div className={classes.section}>
<Typography variant="h3" component="h2">{post.title}</Typography>
<Typography gutterBottom variant="h6" color="textSecondary" component="h2">{ post.tags }</Typography>
<Typography gutterBottom variant="body 1" component="p"> {post.message} </Typography>
<Typography variant="h6"> Created by:{post.name} </Typography>
<Typography variant="body 1">{moment(post.createdAt).fromNow()}</Typography>
<Divider style={{ margin: "20px 0" }} />
<Typography variant="body1"><strong>Realtime Chat - coming soon !</strong></Typography>
<Divider style={{ margin: '20px 0px'}}/>
<Typography variant="body 1"><strong>Comments - coming soon !</strong></Typography>
<Divider style={{ margin: '20px 0px' }}/>
</div>
<div className={ classes.imageSection }>
<img className={ classes.media } src={post.selectedFile} alt={post.title} />
</div>
</div>
</Paper>
)
}
export default PostDetails;
This is the styles used for the code.. I used Material-Ui
import { makeStyles } from '#material-ui/core/styles';
export default makeStyles((theme) => ({
media: {
borderRadius: '20px',
objectFit: 'cover',
width: '100%',
maxHeight: '600px',
},
card: {
display: 'flex',
width: '100%',
[theme.breakpoints.down('sm')]: {
flexWrap: 'wrap',
flexDirection: 'column',
},
},
section: {
borderRadius: '20px',
margin: '10px',
flex: 1,
},
imageSection: {
marginLeft: '20px',
[theme.breakpoints.down('sm')]: {
marginLeft: 0,
},
},
recommendedPosts: {
display: 'flex',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
},
},
loadingPaper: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '20px',
borderRadius: '15px',
height: '39vh',
},
commentsOuterContainer: {
display: 'flex',
justifyContent: 'space-between',
},
commentsInnerContainer: {
height: '200px',
overflowY: 'auto',
marginRight: '30px',
},
}));
Original image Containing the post before a specific id is clicked
[Image of the postDetails when clicked on the original post which contains the specific id i.e /posts/:1d]

useRef takes full control of an outer component instead of the component being used in

I am creating a Appbar with material UI. When I use a custom hook. to control the child component it also controls the whole parent component...
My purpose is to use this hook to disappear the component whenever a click is made outside of the component
Here is my custom hook
useListener.js
import { useEffect, useState, useRef } from "react";
export const useListener = (active, ref) => {
ref = useRef();
const [open, setOpen] = useState((active = false));
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
setOpen(!open);
return;
}
}
// Bind the event listener
document.body.addEventListener("click", handleClickOutside);
return () => {
// Unbind the event listener on clean up
document.body.removeEventListener("click", handleClickOutside);
};
}, [open, ref]);
return [open, ref];
};
when I use it in the component by destructing it with
const [open, ref] = useListener()
and use it inside the component, I set open to appear and disappear the component, with CSS..
display: open ? 'block' : 'none'
Now when I use this component in which this hook is being used, When I click on this component it appears, and when I click outside of the component (any where else in the body) it disappears, which is totally fine...
But the issue is when I click on the parent component in with this component is being used it also makes appear this child component...
Here is my child component...
Profile.js
import React from "react";
import Paper from "#mui/material/Paper";
import Box from "#mui/material/Box";
import cover from "../assets/cover.webp";
import profilepic from "../assets/profilepic.webp";
import Button from "#mui/material/Button";
import Typography from "#mui/material/Typography";
import { LogoutIcon } from "../assets/LogoutIcon";
import { ChangePasswordIcon } from "../assets/ChangePasswordIcon";
import { useListener } from "../../../hooks/useListener";
const logout = () => {
localStorage.removeItem("authorization");
localStorage.removeItem("user");
localStorage.removeItem("permissions");
window.location.reload(false);
};
const Profile = () => {
const [open, profileRef] = useListener();
console.log(profileRef.current);
return (
<Box
ref={profileRef}
sx={{
display: open ? "inline" : "none",
width: "190px",
}}
>
<Paper elevation={2}>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img src={cover} alt="Cover" style={{ width: "190px" }}></img>
<img
src={profilepic}
alt="Profile"
style={{ height: "62px", width: "62px", borderRadius: "50%" }}
></img>
<Typography variant="button" display="block" gutterBottom>
Username
</Typography>
<Button>
<Box sx={{ display: "flex", flexDirection: "row" }}>
<ChangePasswordIcon sx={{ height: "18px", width: "18px" }} />
<Typography variant="body1" gutterBottom>
Change password
</Typography>
</Box>
</Button>
<Button onClick={logout}>
<Box sx={{ display: "flex", flexDirection: "row" }}>
<LogoutIcon sx={{ width: "16px", height: "16px" }} />
<Typography variant="body1" gutterBottom>
Logout
</Typography>
</Box>
</Button>
</Box>
</Paper>
</Box>
);
};
export default Profile;
And this is the parent component...
TopbarContents.js
import React, { useState } from "react";
import Box from "#mui/material/Box";
import Button from "#mui/material/Button";
import Typography from "#mui/material/Typography";
import profilepic from "../assets/profilepic.webp";
import Profile from "./Profile";
export const TopbarContents = () => {
const [profileActive, toggleProfile] = useState(false);
const openProfile = () => {
toggleProfile(true);
};
return (
<Box sx={{ width: "100%" }}>
<Box
sx={{
width: "100%",
display: "flex",
alignItems: "center",
}}
>
<Box sx={{ flexGrow: 1 }}>
<Typography
sx={{ color: "#494343" }}
variant="h6"
noWrap
component="div"
>
Dashboard
</Typography>
</Box>
<Box
sx={{
height: "45px",
width: "160px",
borderRadius: 2,
display: "flex",
flexDirection: "column",
justifyContent: "center",
border: "1px solid #e0e0e0",
pl: "8px",
}}
>
<Typography sx={{ color: "#2E4299", pt: 1 }} variant="caption">
Case study
</Typography>
<Typography sx={{ color: "#494343" }} variant="overline">
Trial ID: NC48023194
</Typography>
</Box>
<Button
color="guava"
variant="contained"
size="medium"
sx={{ fontFamily: "Poppins", mr: 6, ml: 3 }}
>
Case Study
</Button>
<Button onClick={openProfile}>
<img
src={profilepic}
alt="Profile"
style={{ height: "62px", width: "62px", borderRadius: "50%" }}
></img>
</Button>
</Box>
<Box
sx={{
display: profileActive ? "inline" : "none",
flexDirection: "column",
}}
>
<Profile />
</Box>
</Box>
);
};

How to customize button css in react-admin

I am new to react-admin, and how to customize react admin buttons?
In my scenario I have a list also in create, edit, and export button there and I don't know which is the best way to change button css in react-admin. Can anyone solve this?
By default I have button like above mentioned, so I need to add css for this two buttons.
Here is my sample code
// style list
const useStyles = makeStyles((theme) => ({
userCard: {
padding: "20px",
borderRadius: "12px",
},
btn_edit: {
background: "#5E35B1",
color: "#fff",
fontSize: "10px",
},
mainList: {
boxShadow: "none !important",
borderRadius: "0px",
},
listCreateIcon: {
padding: "0px !important",
},
}));
//main
export const UserList = (props) => {
const classes = useStyles();
return (
<Card className={classes.userCard}>
<List
{...props}
pagination={null}
perPage={9999}
className={classes.mainList}
>
<Datagrid className={classes.listCard}>
<TextField source="username" />
<BooleanField source="enabled" />
<ReferenceArrayField reference="_roles" source="roles">
<SingleFieldList>
<ChipField source="name" />
</SingleFieldList>
</ReferenceArrayField>
<EditButton className={classes.btn_edit} />
</Datagrid>
</List>
</Card>
);
};
export const UserCreate = (props) => {
const classes = useStyles();
return (
<Create {...props} className={classes.listCreateIcon}>
<SimpleForm style={{ padding: "0px !important" }}>
<TextInput source="username" />
<TextInput type="password" source="password" />
<ReferenceArrayInput source="roles" reference="_roles" allowEmpty>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
<BooleanInput source="enabled" defaultValue={true} />
</SimpleForm>
</Create>
);
};
export const UserEdit = (props) => (
<Edit {...props}>
<SimpleForm>
<TextField source="username" />
<ReferenceArrayInput source="roles" reference="_roles">
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
<BooleanInput source="enabled" />
</SimpleForm>
</Edit>
);
This will work for you.
btn_create: {
display: "inline-flex",
alignItems: "center",
gridGap: 4,
backgroundColor: "transparent",
borderColor: "transparent",
fontSize: 16,
color: "blue" // the text color you want
// if you are using the svg icon
svg : {
width: 16,
height: 16,
path: {
fill : "blue" //color code of icon
}
}
},
btn_group: {
display:"flex",
gridGap: "16px",
alignItems: "Center",
}
on Component
<div className={classes.btn_group}>
<EditButton className={classes.btn_create} />
<EditButton className={classes.btn_create} />
</div>
PS. If the icon is font icon, you may not use the svg styling.

Passing values from database to flip cards in JS React

I am trying to pass my API value to the Flippy card to dynamically render image and the word displayed on flip.
I am struggling with something I thought would be easy: I have an API where I call back an image link and a word to populate the flip cards. I want to dynamically render my flip cards to the page using that data, but right now my image link and the word are hard coded and I don't know to pass my API call values to the component. API works and correctly returns links and words; hard coded flip card also works with no issues and functions as intended. I believe I have to implement props and pass it somehow to the const DefaultCardContents- thanks so much for taking a stab at it.
I tried implemeneting props but I am unsure how to pass them to the constant
THIS IS MY FLASHCARD COMPONENT
import React, { Component } from 'react';
import Flippy, { FrontSide, BackSide } from './../lib';
import Button from 'react-bootstrap/Button';
import './style.css';
const FlippyStyle = {
width: '200px',
height: '200px',
textAlign: 'center',
color: '#FFF',
fontFamily: 'sans-serif',
fontSize: '30px',
justifyContent: 'center'
}
const DefaultCardContents = ({ children }) => (
<React.Fragment>
<FrontSide
style={{
backgroundColor: 'white',
display: 'flex',
alignItems: 'center',
flexDirection: 'column'
}}
>
<img
src="https://parent.guide/wp-content/uploads/2014/08/Banana-baby-food-recipes.jpg"
style={{ maxWidth: '100%', maxHeight: '100%' }}
/>
<span
style={{
fontSize: '12px',
position: 'absolute',
bottom: '10px',
width: '100%'
}}>
{children}<br />
Hover over to show key word
</span>
</FrontSide>
<BackSide
style={{
backgroundColor: '#EB6864',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column'
}}>
<h1>Banana</h1>
<span
style={{
fontSize: '12px',
position: 'absolute',
bottom: '10px',
width: '100%'
}}>
{children}<br />
<Button variant="success">Success</Button>
<Button variant="outline-warning">Warning</Button>
</span>
</BackSide>
</React.Fragment>);
const FlippyOnHover = ({ flipDirection = 'vertical' }) => (
<Flippy
flipOnHover={true}
flipDirection={flipDirection}
style={FlippyStyle}
>
<DefaultCardContents>
</DefaultCardContents>
</Flippy>
);
class Flashcard extends Component {
constructor(props) {
super(props);
this.state = {
isFlipped: false
}
}
render() {
return (
<div className="App">
<div style={{ display: 'flex', flex: '1 0 200px', justifyContent: 'space-around', 'flex-wrap': 'wrap' }}>
<FlippyOnHover flipDirection="horizontal" />
</div>
</div>
);
}
}
export default Flashcard;
THIS IS MY API CALL AND PAGE RENDER
import React, { Component } from "react";
import Flashcard from "../components/Flashcard";
import API from "../utils/API";
class Flashcards extends Component {
state = {
flashcards: [],
flashcardName: "",
flashcardImage: "",
flipped: false
};
componentDidMount() {
this.loadFlashcards();
};
loadFlashcards = () => {
API.getFlashcards()
.then(res => {
// console.log(res.data);
this.setState({ flashcards: res.data, flashcardName: "", flashcardImage: "" })
// console.log("flashhhhhhhhhh" + JSON.stringify(this.state.flashcards));
})
.catch(err => console.log(err));
};
flipped = () => {
console.log(this.state)
if (this.state.flipped === false) {
this.setState({ flipped: true })
}
}
render() {
return (
<div>
{this.state.flashcards.length ? (
<div>
{this.state.flashcards.map(flashcards => (
<div key={flashcards._id} >
<Flashcard/>
</div>
))}
</div>
) : (
<h3>No Results to Display</h3>
)}
</div>
)
}
}
export default Flashcards;
Expected result is a dynamically generated array of cards base don API
You need to modify your Flashcard component to accept props for the title and the URL for the image, assuming that is in the data returned from the API.
In your map, pass the values to the Flashcard, something like:
{this.state.flashcards.map(flashcard => (
<div key={flashcard._id}>
<Flashcard
title={flashcard.title}
imageUrl={flashcard.image}
/>
</div>
))}
Edit:
Looks like your are using the react-flippy library, so you don't need to manage what state the flash card is in.
Crudly, your FlashCard component could look something like:
import React, { Component } from "react";
import Flippy, { FrontSide, BackSide } from "./../lib";
import Button from "react-bootstrap/Button";
import "./style.css";
const FlippyStyle = {
width: "200px",
height: "200px",
textAlign: "center",
color: "#FFF",
fontFamily: "sans-serif",
fontSize: "30px",
justifyContent: "center"
};
const CardContents = ({ title, imageUrl }) => (
<React.Fragment>
<FrontSide
style={{
backgroundColor: "white",
display: "flex",
alignItems: "center",
flexDirection: "column"
}}
>
<img
src={imageUrl}
style={{ maxWidth: "100%", maxHeight: "100%" }}
/>
<span
style={{
fontSize: "12px",
position: "absolute",
bottom: "10px",
width: "100%"
}}
>
<br />
Hover over to show key word
</span>
</FrontSide>
<BackSide
style={{
backgroundColor: "#EB6864",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column"
}}
>
<h1>{title}</h1>
<span
style={{
fontSize: "12px",
position: "absolute",
bottom: "10px",
width: "100%"
}}
>
<br />
<Button variant="success">Success</Button>
<Button variant="outline-warning">Warning</Button>
</span>
</BackSide>
</React.Fragment>
);
class Flashcard extends Component {
render() {
return (
<div>
<div
style={{
display: "flex",
flex: "1 0 200px",
justifyContent: "space-around",
"flex-wrap": "wrap"
}}
>
<Flippy flipOnHover={true} flipDirection='horizontal' style={FlippyStyle}>
<CardContents imageUrl={this.props.imageUrl} title={this.props.title}/>
</Flippy>
</div>
</div>
);
}
}
export default Flashcard;

Categories