React - Secondary Prop Value - Node - Material UI - javascript

I've been working on simplifying my code and am curious how I would approach passing a secondary value using props and fetching data from the back end. I'm using material UI's Autocomplete and the PERN stack. Everything is working, except I want to change "region_name" to be a prop. So I can change the values dynamically within event details.js. when I'm fetching other data.
I currently have this component setup.
Production.js
import Autocomplete from "#mui/material/Autocomplete";
import Box from "#mui/material/Box";
import Stack from "#mui/material/Stack";
import TextField from "#mui/material/TextField";
export default function CustomAutoComplete(props) {
return (
<Stack sx={{ m: 1 }}>
<Autocomplete
sx={{ ml: 2, mr: 2 }}
size="small"
id="combo-box-demo"
freeSolo
inputValue={props.inputValue}
onInputChange={(event, newValue) => {
props.set(newValue);
}}
getOptionLabel={(data) => `${data.region_name}`}
options={props.data}
isOptionEqualToValue={(option, value) =>
option.region_name === value.region_name
}
renderOption={(props, data) => (
<Box component="li" {...props} key={data.id}>
{data.region_name}
</Box>
)}
renderInput={(params) => <TextField {...params} label="Region" />}
/>
</Stack>
);
}
Then importing it into a separate file EventDetails.js fetching the data and storing it in LocalStorage, which I'll move to useState eventually.
import CustomAutoComplete from "../../atoms/AutoComplete";
import FormGroup from "#mui/material/FormGroup";
import { Fragment, useState, useEffect } from "react";
import { useLocalStorage } from "../../utils/LocalStorage.js";
const EventDetails = () => {
const [region, setRegion] = useLocalStorage("region", "");
const [getRegion, setGetRegion] = useState([]);
// fetching backend data
useEffect(() => {
fetch("/authentication/region")
.then((response) => response.json())
.then((getRegion) => setGetRegion(getRegion));
}, []);
return (
<Fragment>
<FormGroup sx={{ p: 2, m: 1 }}>
<CustomAutoComplete
inputValue={region}
set={setRegion}
data={getRegion}
key={getRegion.id}
name={region_name} // <--feeble attempt
label="Region"
/>
</FormGroup>
</Fragment>
);
};

I had someone help me with finding a solution just had to create a prop variable like
export default function CustomAutoComplete(props) {
const { labelValue } = props;
return (renderOption={(props, data) => (
<Box component="li" {...props} key={data.id}>
{data[labelKey]}
</Box>
)})
then in the parent component
<CustomAutoComplete labelValue="region_name" />

Related

How to use map in react component using nextjs app?

I have data returned from the backend as an array that i want to populate on react component.
home.js
import Head from "next/head";
import Header from "../src/components/Header";
import * as React from 'react';
import { styled } from '#mui/material/styles';
import Box from '#mui/material/Box';
import Paper from '#mui/material/Paper';
import Grid from '#mui/material/Grid';
import TextField from '#mui/material/TextField';
import SendIcon from '#mui/icons-material/Send';
import Stack from '#mui/material/Stack';
import Button from '#mui/material/Button';
import getDupImages from "../src/services/getDupImages";
import { useState, useEffect } from 'react'
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
export default function Home({data}) {
let _data;
const fetchData = async () => {
_data = await getDupImages();
console.log("DATA>>>", _data);
return _data;
};
const submit = (event) => {
event.preventDefault();
fetchData();
}
return (
<>
<Head>
<title>De-Dup</title>
<link rel="icon" type="image/ico" href="/img/goals.ico" />
</Head>
<Header />
<section>
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2}>
<Grid item xs={5}>
<Box
component="form"
sx={{
'& > :not(style)': { m: 1, width: '75ch' },
}}
noValidate
autoComplete="off"
>
<TextField id="outlined-basic" label="location path" variant="outlined" />
<Stack direction="row" spacing={2}>
<Button variant="contained" onClick={submit} endIcon={<SendIcon />}>
Submit
</Button>
</Stack>
</Box>
</Grid>
<Grid item xs={7}>
{data.map((d) => {
return (
<div>
{d.title}
</div>
);
})}
</Grid>
</Grid>
</Box>
</section>
</>
);
}
Error
1 of 1 unhandled error
Server Error
TypeError: Cannot read property 'map' of undefined
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Put the data in the component state and check if there actually is data before displaying it.
const [data, setData] = useState();
const fetchData = async () => {
setData(await getDupImages());
}
Then in your JSX:
{!!data && data.map(d => <div>{d.title}</div>}
You are trying to render the data before it is available. Use this instead
{data && data.map((d) => {
return (
<div>
{d.title}
</div>
);
})}
Either initialise the data state as an array or use the Optional chaining (?.) operator before the map function:
data?.map((d) => {
return <div>{d.title}</div>;
})
Hope this helps.

How updated object with child component should reflect on parent component

I want to add the macchines in machine array so I defined a specific component with add function in it. So when I add the "process" in "processes" array then it is reflecting on the console using useEffect. But when I add a machine it is reflected in MachineGround Component But not in App component. Overall I am planning to add a dashboard where if even a mcahine is added in machines array it should reflect in the processes in App Component and the dashboard should be updated.
I will appreciate your help.
App component
import React, { useEffect, useState } from 'react';
import { Container, Typography, Box, Button } from '#mui/material'
import MachineGround from './Components/MachineGround'
import { Process } from './types'
const App = () => {
const [processes, setProcesses] = useState<Process[]>([{
Name: 'Process-1', machines: [
{
Name: 'Machine-1', devices: [{
Name: 'device-1',
type: 'Timer'
}]
}]
}]) // dummy process
// const [processes, setProcesses] = useState<Process[]>([])
const [count, setCount] = useState<number>(1) // dummy process count.
// Add Process
const addProcess = () => {
if (processes.length < 10) {
setCount(count + 1)
const processNow: Process = {
Name: `Process-${count}`,
machines: []
}
setProcesses((process) => {
return (
[...process, processNow]
)
})
} else {
alert("Limit can't exceeds 10")
}
}
// Delete Process
const deleteProcess = (passProcess: Process) => {
setProcesses(processes.filter((process) => process.Name !== passProcess.Name))
}
useEffect(() => {
console.log(processes)
}, [processes])
return (
<>
<Container maxWidth='lg'>
<Typography variant='h3' mt={5} sx={{ fontWeight: 700 }} align={'center'} gutterBottom>
My DashBoard
</Typography>
<Box sx={{ bgcolor: '#F4F4F7', paddingInline: 5, borderRadius: 10 }} >
{/* here will be the renders of processes */}
{
processes.map((process) => (
<Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }} pb={2} pt={2}>
<Typography variant='h6' >
{process.Name}
</Typography>
<Button variant='contained' onClick={() => {
deleteProcess(process)
}}>
Delete
</Button>
</Box>
<MachineGround process={process} />
</Box>
))
}
</Box>
<Button variant='contained' color='primary' sx={{ marginBlock: 5, marginLeft: 10 }} onClick={addProcess}> Add Process</Button>
</Container>
</>
);
}
export default App;
import React, { useEffect, useState } from 'react'
import DeviceGround from './DeviceGround'
import { Box, Typography, Button } from '#mui/material'
//types
import { Machine, Process } from '../types'
type Props = {
process: Process
}
const MachineGround = ({ process }: Props) => {
const [count, setCount] = useState<number>(1)
const [machines, setMachines] = useState<Machine[]>(process.machines)
const handleAddMachine = () => {
if (machines.length < 10) {
const newMachine: Machine = { Name: `Machine-${count}`, devices: [] }
setMachines((machines) => [...machines, newMachine])
setCount(count + 1)
} else {
alert("You can't add more than 10 Machines.")
}
}
const handleDeleteMachine = (machine: Machine) => {
setMachines(machines.filter((current) => current.Name !== machine.Name))
}
useEffect(() => {
console.log('machines Array Changed')
}, [machines])
return (
<Box sx={{ bgcolor: '#00e676', borderRadius: 5 }} mt={2} ml={3} mr={3} pt={1} pb={1} mb={2}>
{machines.map((machine: Machine) => {
return (
<>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }} mt={2}>
<Typography paragraph ml={5} sx={{ fontWeight: 700 }}>
{machine.Name}
</Typography>
<Button variant='outlined' size='small' sx={{ marginRight: 5 }} onClick={() => {
handleDeleteMachine(machine)
}}>Delete Machine</Button>
</Box>
<Box>
{/* {
machine.devices.length !== 0 ?
<DeviceGround machine={machine}></DeviceGround>
: null we dont need conditional render
} */}
<DeviceGround machine ={machine} />
</Box>
</>
)
})}
<Button variant='contained' size='small' sx={{ marginLeft: 5 }} onClick={handleAddMachine}>Add Machine</Button>
</Box >
)
}
export default MachineGround
I am thinking that should I use Redux ? or another state management then what should I do? I messed up the states.
State management tools like Redux, Context-API can be a good option here but even if you do not want to use them, you can make use of normal JavaScript functions. Just pass them as props from your parent component to child component.
I will explain what I mean here. Write a function in your parent component which take a machine object and update the machines array. Now pass this component as props to your child component. Now inside your child component call this function with the machine object that you want to add to your machines array. And boom, your machines array in parent will be updated.

How to pass the search query to table filter between different JS files?

I have a datagrid table in which I'm getting my database data from an API call and I have written the table code in one file. I also have a search functionality where you can search for a particular record inside the table, but this search code is in another file. I am having difficulty of passing my state variable containing the search parameter from my search file to the table file. I have separated all my components in different pages since it'd be easier to structure them using a grid in my App.js. How do I get my search query to my table file next?
My search code:
export default function SearchInput() {
const [searchTerm, setSearchTerm] = React.useState('');
return (
<Grid item xs={3}>
<Box mt={1.6}
component="form"
sx={{
'& > :not(style)': { m: 1, width: '20ch', backgroundColor: "white", borderRadius: 1},
}}
noValidate
autoComplete="off"
>
<TextField
placeholder="Search Customer ID"
variant="outlined"
size="small"
sx={{input: {textAlign: "left"}}}
onChange={(event) => {
setSearchTerm(event.target.value);
console.log(searchTerm);
}}
/>
</Box>
</Grid>
);
}
My table code:
export default function DataTable() {
const [pageSize, setPageSize] = React.useState(10);
const [data, setData] = React.useState([]);
useEffect(async () => {
setData(await getData());
}, [])
return (
<div style={{ width: '100%' }}>
<DataGrid
rows={data}
columns={columns}
checkboxSelection={true}
autoHeight={true}
density='compact'
rowHeight='40'
headerHeight={80}
disableColumnMenu={true}
disableSelectionOnClick={true}
sx={datagridSx}
pageSize={pageSize}
onPageSizeChange={(newPageSize) => setPageSize(newPageSize)}
rowsPerPageOptions={[5, 10, 15]}
pagination
/>
</div>
);
}
App.js
function App() {
return (
<div className="App">
<Container maxWidth="false" disableGutters="true">
<Grid container spacing={0}>
<ABClogo />
<HHHlogo />
</Grid>
<Grid container spacing={0}>
<LeftButtonGroup />
<SearchInput />
<RightButtonGroup />
</Grid>
<Grid container spacing={0}>
<DataTable />
<TableFooter />
</Grid>
</Container>
</div>
);
}
Here is a minimal example using createContext(), and useReducer() to lift up state and share it between components, similar to what you are after, but as jsNoob says, there are multiple options. This is one I'm comfortable with.
The concept is explained here: https://reactjs.org/docs/context.html
Essentially you can create 'global' state at any point in your component tree and using Provider / Consumer components, share that state and functionality with child components.
//Main.js
import React, { createContext, useContext, useReducer } from 'react';
const MainContext = createContext();
export const useMainContext => {
return useContext(MainContext);
}
const mainReducer = (state, action) => {
switch(action.type){
case: 'SOMETHING':{
return({...state, something: action.data});
}
default:
return state;
}
}
export const Main = () => {
const [mainState, mainDispatch] = useReducer(mainReducer, {something: false});
const stateOfMain = { mainState, mainDispatch };
return(
<MainContext.Provider value={stateOfMain}>
<MainContext.Consumer>
{() => (
<div>
<Nothing />
<Whatever />
</div>
)}
</MainContext.Consumer>
</MainContext.Provider>
)
}
Then you can have the other components use either or both of the main state and dispatch.
//Nothing.js
import {mainContext} from './Main.js'
const Nothing = () => {
const { mainState, mainDispatch } = useMainContext();
return(
<button onClick={() => {mainDispatch({type: 'SOMETHING', data: !mainState.something})}}></button>
)
}
Clicking the button in the above file, should change the display of the below file
//Whatever.js
import {mainContext} from './Main.js'
const Whatever = () => {
const { mainState } = useMainContext();
return(
<div>{mainState.something}</div>
);
}

react-map-gl and using useContext and/or useState to change viewport from another component

I seem to be not understand anything lately and having a tough time grasping useContext and/or useState with React/Nextjs and react-map-gl.
Basically, I have two components. A searchbox utilizing Formik & a Map from react-map-gl. What I want to do is be able to have one of the variables from Formik be able to change the viewport on react-map-gl so it redirects the viewport of the map to the new coordinates. I tried researching and reading about what I can do to make this work but I just can't grasp it. I believe it has to be something simple and I'm just too new to understand. Every time I think I have it I get a error saying I can't actually do that.
For reference here's my current code using useState. Whenever I try to use it and hit the submit button I get " 1 of 1 unhandled error
Unhandled Runtime Error
TypeError: setViewport is not a function
"
which doesn't seem to make any sense :( . Other times when I can get it to work, I have to click the submit button twice to get the form to register the new setViewport. What am I doing wrong? Thank you for your help!
index.js
import { Grid } from '#material-ui/core';
import { useState } from 'react';
import SearchBox from '../components/searchbox';
import {
getAllCampgrounds,
getAllCities,
getCampgroundsByCity,
} from '../lib/api';
import Map from '../components/map';
export default function CampList({
graphCampgrounds,
cities,
campgroundsbycity,
}) {
const [viewport, setViewport] = useState({
height: '100vh',
latitude: 44.0456,
longitude: -71.6706,
width: '100vw',
zoom: 8,
});
return (
<Grid container spacing={3}>
<Grid item xs={12} sm={5} md={3} lg={2}>
<SearchBox
graphCampgrounds={graphCampgrounds}
cities={cities}
campgroundsbycity={campgroundsbycity}
setViewport={() => setViewport}
/>
</Grid>
<Grid item xs={12} sm={7} md={9} lg={10}>
<Map
campgrounds={graphCampgrounds}
viewport={viewport}
setViewport={() => setViewport}
/>
<pre style={{ fontSize: '2.5rem' }}>{}</pre>
</Grid>
</Grid>
);
}
I omitted my getServerSideProps area. Here is my Searchbox.js:
import Link from 'next/link';
import { Form, Formik, Field, useFormik } from 'formik';
import {
Paper,
Grid,
FormControl,
InputLabel,
Select,
MenuItem,
Button,
} from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles';
import { useRouter } from 'next/router';
const useStyles = makeStyles(theme => ({
paper: {
margin: 'auto',
maxWidth: 500,
padding: theme.spacing(3),
},
}));
export default function SearchBox({
graphCampgrounds,
cities,
campgroundsbycity,
viewport,
setViewport,
}) {
const router = useRouter();
const { query } = router;
const handleSubmit = async values => {
setViewport({
...viewport,
latitude: campgroundsbycity
? parseFloat(campgroundsbycity.nodes[0].acfDetails.latitude.toFixed(4))
: 44.43,
longitude: campgroundsbycity
? Math.abs(
parseFloat(campgroundsbycity.nodes[0].acfDetails.longitude.toFixed(4))
) * -1
: -72.352,
zoom: 11,
});
router.push(
{
pathname: '/camps',
query: { ...values, page: 1 },
},
undefined,
{ shallow: true }
);
};
const classes = useStyles();
const smValue = singleColumn ? 12 : 6;
const initialValues = {
city: query.city || 'all'
};
return (
<Formik initialValues={initialValues} onSubmit={handleSubmit}>
{({ values, submitForm }) => (
<Form>
<Paper className={classes.paper} elevation={5}>
<Grid container spacing={3}>
<Grid item xs={12} sm={smValue}>
<FormControl fullWidth variant="outlined">
<InputLabel id="search-cities">City</InputLabel>
<Field
name="city"
as={Select}
labelId="search-cities"
label="Cities"
>
<MenuItem value="all">
<em>All</em>
</MenuItem>
{cities.nodes.map(town => {
return (
<MenuItem
key={town.acfDetails.city}
value={town.acfDetails.city}
>
{town.acfDetails.city}
</MenuItem>
);
})}
</Field>
</FormControl>
</Grid>
<Grid item xs={12}>
<Button
color="primary"
variant="outlined"
type="button"
fullWidth
onClick={submitForm}
>
Search Campgrounds
</Button>
</Grid>
</Grid>
</Paper>
</Form>
)}
</Formik>
);
}
And here's my map component:
import { useContext, useMemo } from 'react';
import ReactMapGL, { Marker, MapContext } from 'react-map-gl';
import { ViewportContext } from '../lib/state';
export default function Map({ campgrounds, viewport, setViewport }) {
const markers = campgrounds.map(({ node }) => {
console.log(node.acfDetails);
return (
<Marker
key={node.title}
longitude={
Math.abs(parseFloat(node.acfDetails.longitude.toFixed(4))) * -1
}
latitude={parseFloat(node.acfDetails.latitude.toFixed(4))}
>
<img src="pin.png" alt={node.title} />
</Marker>
);
});
return (
<ReactMapGL
mapboxApiAccessToken={process.env.NEXT_PUBLIC_MAPBOX_KEY}
mapStyle="mapbox://styles/mapbox/outdoors-v11"
{...viewport}
onViewportChange={nextViewport => setViewport(nextViewport)}
>
{markers}
</ReactMapGL>
);
}
Agreed with #juliomalves's answer.
In you index.js, pass the function setViewport to your SearchBox and Map components by setViewport={setViewport}.
That is how React hooks should be called.

Reactjs - Update options by hitting API on every input change in Autocomplete component (Material UI)

I am a beginner in Reactjs. I want to create an Autocomplete component where on every input change the API is hit and the options are updated accordingly. I am using the Autocomplete component provided by Material UI. As I understand, the example given here hits an API once and filters it locally. I tried using the InputChange props provided by material component. Also I found this anser - https://stackoverflow.com/a/59751227/8090336. But can't figure out the right way.
import Autocomplete from "#material-ui/lab/Autocomplete";
import TextField from "#material-ui/core/TextField";
import {CircularProgress} from "#material-ui/core";
import debounce from 'lodash/debounce';
const SelectField = ({inputLabel}) => {
const [ open, setOpen ] = React.useState(false);
const [ options, setOptions ] = React.useState([]);
const [ inputValue, setInputValue ] = React.useState("");
const loading = open && options.length === 0;
const onInputChange = debounce((event, value) => {
console.log("OnInputChange",value);
setInputValue(value);
(async() => {
const response = await fetch('https://api.tvmaze.com/search/shows?q='+inputValue);
console.log("API hit again")
let movies = await response.json();
if(movies !== undefined) {
setOptions(movies);
console.log(movies)
}
})();
}, 1500);
return (
<Autocomplete
style={{ width:300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
getOptionLabel={(option) => option.show.name}
onInputChange={onInputChange}
options={options}
loading={loading}
renderInput={(params) => (<TextField
{...params}
label={inputLabel}
variant="outlined"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} />: null }
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
)}
/>
);
}
export default SelectField;
I had faced this problem I manually called the API when ever the user types in. find the link for the sandbox. Check the onChange prop for the textfield rendered inside the autocomplete
// *https://www.registers.service.gov.uk/registers/country/use-the-api*
import fetch from "cross-fetch";
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import CircularProgress from "#material-ui/core/CircularProgress";
export default function Asynchronous() {
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const loading = open && options.length === 0;
const onChangeHandle = async value => {
// this default api does not support searching but if you use google maps or some other use the value and post to get back you reslut and then set it using setOptions
console.log(value);
const response = await fetch(
"https://country.register.gov.uk/records.json?page-size=5000"
);
const countries = await response.json();
setOptions(Object.keys(countries).map(key => countries[key].item[0]));
};
React.useEffect(() => {
if (!open) {
setOptions([]);
}
}, [open]);
return (
<Autocomplete
id="asynchronous-demo"
style={{ width: 300 }}
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
getOptionSelected={(option, value) => option.name === value.name}
getOptionLabel={option => option.name}
options={options}
loading={loading}
renderInput={params => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
onChange={ev => {
// dont fire API if the user delete or not entered anything
if (ev.target.value !== "" || ev.target.value !== null) {
onChangeHandle(ev.target.value);
}
}}
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? (
<CircularProgress color="inherit" size={20} />
) : null}
{params.InputProps.endAdornment}
</React.Fragment>
)
}}
/>
)}
/>
);
}

Categories