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

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

Related

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.

How to pass Image object as props in React?

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

Why I can not use inline function in component prop? Can you explain in detail by giving an example? What means saying losing all state?

Note: The component prop accepts component, not a render function. Don't pass an inline function (e.g. component={() => }), or your component will unmount and remount losing all state when the parent component re-renders. See Passing additional props for alternatives.
This warning from the simulator.
enter image description here
function HomeScreen(props: Object) {
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={() => {
return (
<View>
<Text>Example Text</Text>
</View>
);
}}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
It's warning you that component is going to be re-rendered every time screen is re-rendered. This is because every time the component arg is evaluated it returns a fresh inline function. (the prop changes every time thus it must always re-render)
Evaluation of the same inline function twice yields different objects.
Eg.
let funcs = []
for (let i =0 ; i<2; i++) {
funcs.push(() => 1)
}
funcs[0] == funcs[1]
>>> false
You can wrap this is useCallback to get a stable function that only updates when it needs to (never in your case since it captures no state). More on hooks here
function HomeScreen(props: Object) {
let component = useCallback(() => {
return (
<View>
<Text>Example Text</Text>
</View>
);
}, [] // no dependencies. This lambda will only be evaluated once.
);
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={component}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
Or since this function doesn't depend on anything stateful you could also just declare it globally at the top of this file.
let component = () => {
return (
<View>
<Text>Example Text</Text>
</View>
);
};
function HomeScreen(props: Object) {
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={component}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
You need to understand what is hiding behind JSX syntax.
When you use <SomeComponent/> it is added to the React Virtual DOM and exists there until the tree is changing and component has to be unmounted. It survives rerenders, the DOM is updating, but component lives.
When you use () => <SomeComponent /> this component is a new object on every rerender of the parent. If it has state (via setState hook) it will be reinitialized. If it has some code in useEffect(() => {}, []) hook it will be called again.

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.

Pass props nicely to screens in react navigation

I want to pass a prop to the screen. When I try that inline e.g (props) => <Comp {...props} {...customProps} /> I get a warning message, that I shouldn't parse functions to that component property. Okay. I thought I'll just create functions for every component which needs custom props. It is working, but is there a better solution? Here is my component:
export default function Loading() {
const [loggedIn, setLoggedIn] = React.useState(false);
const Stack = createStackNavigator();
const authService: AuthService = new AuthService();
const authProps: IAuthProps = {
authService
};
/**
* Bind neccessary props to the login component
* #param props Props
*/
function LoginWithProps(props) {
return <Login {...props} {...authProps} />;
}
/**
* Bin neccessary props to the registration component
* #param props Props
*/
function RegistrationWithProps(props) {
return <Registration {...props} {...authProps} />;
}
return (
<>
{/*Show the app, when logged in*/}
{loggedIn === true ? (
<View>
<Text>Test</Text>
</View>
) : (
<Stack.Navigator
initialRouteName="Login"
screenOptions={{ headerShown: false, animationEnabled: false }}
>
<Stack.Screen name="Login" component={LoginWithProps} />
<Stack.Screen name="Registration" component={RegistrationWithProps} />
</Stack.Navigator>
)}
</>
);
}
```
Yours is not a good solution to the problem because new types of LoginWithProps and RegistrationWithProps components will be created every render, meaning old ones will be unmounting and new ones mounting every time. The same thing that happens when you pass a function, but without a warning
You can't pass props to these screens, as it is not the Loading component that is a direct parent to these screens. If you want to pass data to customize those components, you need to do it through navigation params, in 2 ways:
when navigating to a screen navigation.navigate('Login', { param: 'hello' })
by proviging initial params
.
<Stack.Screen
name="Login"
component={Loaing}
initialParams={{ param: 'hello' }}
/>
And read it in Login with props.route.params
Note though this is called initialParams for a reason - they are not reactive, changing them after component is mounted has no effect (it is also possible to change them from inside component). If you want to pass reactive params, use React Context or Redux
Passing parameters to routes

Categories

Resources