React controlled MUI form field components - javascript

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.

Related

Can someone help me with my react app that runs in the localhost and doesn't display anything?

import React, { useState, useEffect } from 'react';
import { makeStyles } from '#mui/material/styles';
import { Grid, Typography, Link } from '#mui/material';
// We can use the makeStyles hook from MUI to create a custom styles object
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
}));
function App() {
const classes = useStyles();
const [data, setData] = useState(null); // we'll use this state variable to store the data we get from the API
useEffect(() => {
// This function will run when the component mounts (similar to componentDidMount in a class-based component)
// We'll use the fetch API to get data from the API
fetch('https://api.example.com/endpoint')
.then((response) => response.json())
.then((json) => {
setData(json);
});
}, []); // We pass an empty array as the second argument to useEffect to make sure it only runs once
return (
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Typography variant="h2">Hello BeyondMD!</Typography>
</Grid>
<Grid item xs={12}>
{/* Here, we use the Link component from MUI to create a link to a PDF file */}
<Link href="public\Resume+Template.pdf" target="_blank">
View my resume
</Link>
</Grid>
{data && (
<Grid item xs={12}>
{/* If we have data from the API, we can render it here */}
<Typography variant="body1">{data.someProperty}</Typography>
</Grid>
)}
</Grid>
</div>
);
}
export default App;
Im trying to display my resume that I have put in the public and also fetch data from a free API (I dont know where though). In the end it is giving me a blank page with the localhost.
I have tried almost everything I can find online. I want it to display a line of text and my resume and also data from a free API anywhere.

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}
/>
);
};

Why do 2 same div container produce different result when navigating through keyboard on radio button?

I would like to wrap radio button group with 200px width. And I expect keyboard navigation to behave the same, which is to move between radio buttons through right/left arrow back and forth. Weird thing is if I implement parent div by wrapping this Container component const Container = ({ children }) => ( <div style={{ width: "200px" }}>{children}</div>);, after moving to different radio button once, it stops. I can't no longer navigate radio buttons. But if I implement parent div using plain html code <div style={{ width: "200px"}}>, keyboard navigation works fine. When I inspect using dev tools, semantically, they both appear the same. So I'm having trouble figuring out why Container component impacts keyboard navigation while hard coding in html doesn't. Anybody knows why? https://codesandbox.io/s/radio-button-example-3t8l80?file=/App.js
import { Stack, RadioButton } from "#shopify/polaris";
import { useState, useCallback } from "react";
function RadioButtonExample() {
const [value, setValue] = useState("disabled");
const handleChange = useCallback(
(_checked, newValue) => setValue(newValue),
[]
);
const Container = ({ children }) => (
<div style={{ width: "200px" }}>{children}</div>
);
return (
<Container> // keyboard navigation doesn't work properly with this
{/* <div style={{ width: "200px"}}> */} // keyboard navigation works fine
<Stack vertical>
<RadioButton
label="Accounts are disabled"
helpText="Customers will only be able to check out as guests."
checked={value === "disabled"}
id="disabled"
name="accounts"
onChange={handleChange}
/>
<RadioButton
label="Accounts are optional"
helpText="Customers will be able to check out with a customer account or as a guest."
id="optional"
name="accounts"
checked={value === "optional"}
onChange={handleChange}
/>
</Stack>
{/* </div> */}
</Container>
);
}
export default RadioButtonExample;
The reason for this to happen is because your Container component is part of the RadioButtonExample component. Every time a state or prop gets updated, the Container component gets created as a new component. To prevent this, take your Container component outside of the RadioButtonExample like this:
import { Stack, RadioButton } from "#shopify/polaris";
import { useState, useCallback } from "react";
const Container = ({ children }) => (
<div style={{ width: "200px" }}>{children}</div>
);
function RadioButtonExample() {
const [value, setValue] = useState("disabled");
const handleChange = useCallback(
(_checked, newValue) => setValue(newValue),
[]
);
return (
<Container>
<Stack vertical>
<RadioButton
label="Accounts are disabled"
helpText="Customers will only be able to check out as guests."
checked={value === "disabled"}
id="disabled"
name="accounts"
onChange={handleChange}
/>
<RadioButton
label="Accounts are optional"
helpText="Customers will be able to check out with a customer account or as a guest."
id="optional"
name="accounts"
checked={value === "optional"}
onChange={handleChange}
/>
</Stack>
</Container>
);
}
export default RadioButtonExample;
A different solution would be to wrap the container in a useMemo to make sure it stays the same. This would give you something like this:
const Container = useMemo(() => ({ children }) => (
<div style={{ width: "200px" }}>{children}</div>
), []);
Both will work, but I would suggest going for the first solution.

How can I get or see prop value in my other component?

Experts am in the learning stage and I wanted to know how can I can get the text input value from the search bar and pass it to my another component where I have an API URL as a search query.
I have seen many videos which use props and I guess that’s the correct way if not wrong to send the data between components. However, I tried to use that but I have an issue. I can not see the props value which I have passed from my nav component (textfiled value) to the product component.
any help how can I do that.
Please if you can provide a source code explanation would be great.
Also please find the relevant code below:
Please see the full code here https://codesandbox.io/s/determined-nightingale-m2mtn?file=/src/App.js
This is the code on my nav component and I want the onchange value to be passed to the product component.
const Navbar = () =>{
const[searchText, setSearchText] = useState();
const handleChange = (event) =>{
setSearchText(event.target.value)
}
return (
<>
<div className="nav_main">
<div className="logo_main">
<Link exact to='/home' className="logo" > Movie </Link>
</div>
<div className="search_main">
<TextField
id="input-with-icon-textfield"
className= "searchTextStyle"
size= "small"
placeholder="search"
variant="standard"
value = {searchText}
onChange = {handleChange}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon style={{color:'white'}} />
</InputAdornment>
),
}}
/>
</div>
<div className="nav_menu">
<NavLink exact activeClassName= "active_link" to='/home' className="navitems" > Home </NavLink>
<NavLink exact activeClassName= "active_link" to='/about'className="navitems" > About </NavLink>
<NavLink exact activeClassName= "active_link" to = '/products' className="navitems" > Products </NavLink>
<IconButton className="account_icon">
<AccountCircleIcon fontSize="small"/>
</IconButton>
</div>
</div>
</>
)
};
export default Navbar;
The Product component:
const Produts = (props) =>{
console.log(props.value);
return (
<>
<h1>{`http://api.themoviedb.org/3/search/movie?api_key=Mykey&query=${props.value}`}</h1>
</>
)
};
export default Produts;
Thanks
how are you?
What happens is, it's okay, you are getting the data from de input, but you are not passing it forward to the other component where you want to use that data.
How can you do that?
As they are 'sibling' components, i recommend you to put the handleChange and the useState where you are going to save the data in the FATHER component, pass them through props for the component where the user types, and then pass the stored data also via props for the component where you are going to use it.
Let me give you a pratical example of how you can do it:
On the App you handle the state and provide it for the components via props.
function App() {
const [searchText, setSearchText] = useState();
const handleChange = (event) => {
setSearchText(event.target.value);
};
return (
<>
<Navbar handleChange={handleChange} searchText={searchText}/>
<Switch>
<Route path="/products"
render= { (props) => <Produts {...props} searchText={searchText} /> }
/>
</Switch>
</>
);
}
in the NavBar component you get them via props, destructuring it:
const Navbar = ({handleChange, searchText}) => {
return (
<>
<div className="nav_main">
<div className="logo_main">
<Link exact to="/home" className="logo">
{" "}
Movie{" "}
</Link>
</div>
and in the Produts component you get them also via props:
const Produts = (props) => {
console.log(props.searchText);
const classes = useStyles();
const [movieData, setMovieData] = useState([
// the below is an fake api data which needs to be deleted when final disgn is ready and add real api from useEffect
{
adult: false,
observe that, before you were getting "props.value" but the name that you get in the component is the same name that you used to pass it from the provider component, in this case 'searchText'.
tl;dr
You need to pass them via props, choosing a name, and you get them from the other side using the same name. To get them you can either use 'props.PROP_NAME' or you can use the destructuring method and get the name directly.
Also, to pass a props through a Router rendered component you have to use this syntax:
render= { (props) => <Produts {...props} PROP_NAME={VARIABLE_YOU_ARE_USING}
On the NavBar and the Produts components i used different methods so you can see that you can use both for getting the props information.
I hope that can help, anyway i'm available for further explanations.
The NavLink has additional properties which can be used. The 'to' attribute needn't be only a string value. For example:
<NavLink exact activeClassName= "active_link" to = '/products' className="navitems" > Products </NavLink>
can be rewritten as
to={{
pathName: '/products',
someProps:{data: 'Place any Data here'}
}}
className="navitems" > Products </NavLink>
Then in your products component you should be able to retrieve the data using this.props.location.someProps.data.
Take a look at these articles:
https://reactrouter.com/web/api/Link/to-object,
https://medium.com/#bopaiahmd.mca/how-to-pass-props-using-link-and-navlink-in-react-router-v4-75dc1d9507b4
They outline exactly how this should be used.

react native display input on real time

I have a Text Input in React Native and I want to display the typed input on real time (two way binding ) in a way that when typing each letter in the input, the text under the input field is automatically updated with the letter typed. I want to achieve this without the use of state but this code doesn't work
export default function App() {
const updateDisplay=(typedLetters)=> {return (<View><Text>typedLetters</Text></View>)}
return(
<TextInput
style={{height: 40,margin:20}}
placeholder="Search"
onChangeText={(text)=>updateDisplay(text)}
/>)
}
First, updateDisplay should be like this:
const updateDisplay = (typedLetters) => {
return (
<View>
// notice argument is in {}
<Text>{typedLetters}</Text>
</View>
);
};
In order to show the text, you have to call the updateDisplay inside the component:
return (
<View>
<TextInput
style={{ height: 40, margin: 20 }}
placeholder="Search"
onChangeText={(text) => updateDisplay(text)}
/>
{/* what parameter you are going to be passing to this function */}
{updateDisplay()}
</View>
The thing is when you defined the updateDisplay, it receives an argument. So somehow you need to extract the input value of TextInput component. That is why we need to use the state.
TextInput is actually a function and you cannot go inside of a function and grab a value. Inside a function, you mutate the state. we use setState because we are not setting the state, we are asking React and it decides when to set it.
export default function App() {
const [text, setText] = useState(null);
const updateDisplay = (typedLetters) => {
return (
<View>
<Text>{typedLetters}</Text>
</View>
);
};
return (
<View>
<TextInput
style={{ height: 40, margin: 20 }}
placeholder="Search"
// now save the input value to state.
onChangeText={(text) => setText(text)}
/>
{/* what parameter you are going to be passing to this function */}
{updateDisplay(text)}
</View>
);
}
It is not possible to do this without state. Using state you provide a hook for the UI to know when to re-render your component. Without it, your UI will not re-render, and you won't see any difference.

Categories

Resources