Can you pass in a useState function into a component on initialization? - javascript

I'm using the useState hook to manage rendering components on screen. I want to initialize it with a component while passing in the useState function to set the screen into the component.
Here is my App.js. The error I get is in regards to passing a function into itself on initialization.
function App() {
//useState hooks to determine which component should render
const [screenLoaded, loadScreen] = useState(() => {
<Home setLoadedScreen = {loadScreen}/>
})
return (
<div className="App">
{screenLoaded}
</div>
);
}

The default value for useState is always in the parentheses, no curly braces are needed in this case. const [state, setState] = useState(default). This state could be change in the future with setState(new value).

one simple method is to give your screen a name for example
<Home /> === "home-screen"
<About /> === "about-screen"
so when you pass the setState method of loadScreen into the component, you can switch them by setting the string, for example if you're in home screen and you want to switch to about screen, you'd write
setLoadedScreen("about-screen")
function App(){
const [screenLoaded, loadScreen] = useState("home-screen")
return (
<div className="App">
{screenLoaded === "home-screen" && <Home setLoadedScreen = "loadedScreen" />}
{screenLoaded === "about-screen" && <About setLoadedScreen = "loadedScreen" />}
</div>
);
}

Related

SonarQube "Do not define components during render" with MUI/TS but can't send component as prop

I am getting the following error during sonarqube scan:
Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state. Instead, move this component definition out of the parent component “SectionTab” and pass data as props. If you want to allow component creation in props, set allowAsProps option to true.
I understand that it says that I should send the component as a prop from the parent, but I don't want to send the icon everytime that I want to use this component, is there another way to get this fixed?
import Select from "#mui/material/Select";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faAngleDown } from "#fortawesome/pro-solid-svg-icons/faAngleDown";
const AngleIcon = ({ props }: { props: any }) => {
return (
<FontAwesomeIcon
{...props}
sx={{ marginRight: "10px" }}
icon={faAngleDown}
size="xs"
/>
);
};
const SectionTab = () => {
return (
<Select
id="course_type"
readOnly={true}
IconComponent={(props) => <AngleIcon props={props} />}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
export default SectionTab;
What can you do:
Send the component as the prop:
IconComponent={AngleIcon}
If you need to pass anything to the component on the fly, you can wrap it with useCallback:
const SectionTab = () => {
const IconComponent = useCallback(props => <AngleIcon props={props} />, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};
This would generate a stable component, but it's pretty redundant unless you need to pass anything else, and not via the props. In that case, a new component would be generated every time that external value changes, which would make it unstable again. You can use refs to pass values without generating a new component, but the component's tree won't be re-rendered to reflect the change in the ref.
const SectionTab = () => {
const [value, setValue] = useState(0);
const IconComponent = useCallback(
props => <AngleIcon props={props} value={value} />
, []);
return (
<Select
id="course_type"
readOnly={true}
IconComponent={IconComponent}
variant="standard"
defaultValue="cr"
disableUnderline
/>
);
};

Reac router component in a list as a state

does anyone have experience with React routers?Is there a way to create a list of routes and hold it in a usestate? When i try to do the [... prevCountryRoutes] i get the error that prevCountryRoutes is not iterable
const [countriesWithRouteList,setCountriesWithRouteList]=React.useState(JSON.parse(localStorage.getItem("countriesWithRouteList")) || [])
const [countryRoutes,setCountryRoutes]=React.useState()
function addCountryRoute(co){
if(countriesWithRouteList.filter(el => el == co) == null){
console.log('already route exists')
}else{
console.log('country page being added')
setCountryRoutes((prevCountryRoutes)=>{
const newRoute = <Route
key={nanoid()}
path={`/countrypage/${co.replace(/ /g, '%20')}`}
element={
<CountryPage
country={co}
holidays={holidays}
handleDeleteClick={handleDeleteClick}
handleFormSubmit={handleFormSubmit}
/>
}
/>
return(
[...prevCountryRoutes, newRoute]
)
})
}
setCountriesWithRouteList(prevList => [...prevList, co])
}
The error you are asking about is cause by not declaring an initial countryRoutes state that is iterable. It's undefined.
Anyway, it's a React anti-pattern to store React components and JSX in state. Just store the data and render the derived JSX from state.
I suggest the following refactor:
const [countries, setCountries] = React.useState(JSON.parse(localStorage.getItem("countries")) || []);
function addCountryRoute(co){
if (countries.some(el => el.co === co)){
console.log('already route exists')
} else {
console.log('country page being added')
setCountries((countries) => countries.concat({
id: nanoid(),
co,
}));
}
}
...
{countries.map(country => (
<Route
key={country.id}
path={`/countrypage/${co.replace(/ /g, '%20')}`}
element={
<CountryPage
country={co}
holidays={holidays}
handleDeleteClick={handleDeleteClick}
handleFormSubmit={handleFormSubmit}
/>
}
/>
))}
And instead of mapping a bunch of routes that differ only in the country path segment, render just a single route where the country code is a route path parameter and the CountryPage component uses the useParams hook to get the code.
Example:
<Route
path="/countrypage/:country"
element={
<CountryPage
holidays={holidays}
handleDeleteClick={handleDeleteClick}
handleFormSubmit={handleFormSubmit}
/>
}
/>
CountryPage
const { country } = useParams();
Initialize countryRoutes with an array, so the first time can be iterable.
const [countryRoutes,setCountryRoutes] = React.useState([])

Can't pass useState() 'set' function to grand child

I'm having issues trying to get my useState variable to work. I create the state in my grandparent then pass it into my parent. Here's a simplified version of my code:
export function Grandparent(){
return(
<div>
const [selectedID, setSelectedID] = useState("0")
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
Parent:
const Parent = ({setSelectedID2 ...}) => {
return(
<div>
{setSelectedID2("5")} //works
<Child setSelectedID3={setSelectedID2} />
</div>
)
}
From the parent I can use 'setSelectedID2' like a function and can change the state. However, when I try to use it in the child component below I get an error stating 'setSelectedID3' is not a function. I'm pretty new to react so I'm not sure if I'm completely missing something. Why can I use the 'set' function in parent but not child when they're getting passed the same way?
Child:
const Child = ({setSelectedID3 ...}) => {
return(
<div >
{setSelectedID3("10")} //results in error
</div>
);
};
In React you make your calculations within the components/functions (it's the js part) and then what you return from them is JSX (it's the html part).
export function Grandparent(){
const [selectedID, setSelectedID] = useState("0");
return(
<div>
<Parent setSelectedID2={setSelectedID} .../> //(elipses just mean that I'm passing other params too)
<div />
)}
You can also use (but not define!) some js variables in JSX, as long as they are "renderable" by JSX (they are not Objects - look for React console warnings).
That's your React.101 :)
Here's a working example with everything you have listed here. Props are passed and the function is called in each.
You don't need to name your props 1,2,3.., they are scoped to the function so it's fine if they are the same.
I moved useState and function calls above the return statement, because that's where that logic should go in a component. The jsx is only used for logic dealing with your display/output.
https://codesandbox.io/s/stupefied-tree-uiqw5?file=/src/App.js
Also, I created a working example with a onClick since that's what you will be doing.
https://codesandbox.io/s/compassionate-violet-dt897?file=/src/App.js
import React, { useState } from "react";
export default function App() {
return <Grandparent />;
}
const Grandparent = () => {
const [selectedID, setSelectedID] = useState("0");
return (
<div>
{selectedID}
<Parent setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Parent = ({ selectedID, setSelectedID }) => {
setSelectedID("5");
return (
<div>
{selectedID}
<Child setSelectedID={setSelectedID} selectedID={selectedID} />
</div>
);
};
const Child = ({ selectedID, setSelectedID }) => {
setSelectedID("10");
return <div>{selectedID}</div>;
};
output
10
10
10
const [selectedID, setSelectedID] = useState("0")
should be outside return

React: How to pass data to children

I am a beginner in React and what I am doing might not make sense.
What I am trying to do is just to pass a data to from a parent component which is used in every screen to children.
My code is like this.
AppDrawer.tsx
const AppDrawer: React: FC<Props> = ({
children,
}) => {
const [aString, setString] = React.useState<string>('Hello');
...
<div>
<Drawer>
...
</Drawer/>
<main>
<div>
<Container>
{children}
</Container>
</div>
</main>
</div>
App.tsx
<Swith>
<AppDrawer>
<Route path="/" component={ChildCompoent} />
...
...
</AppDrawer>
</Switch>
ChildComponent.tsx
export default class ChildComponent extends React.Component<Props, State> {
state = {
..
}
}
And now I want to access aString in AppDrawer.tsx in child components but I couldn't figure out how I can do it. How can I do it?
I think you can use Context here. Context allows you to pass down something from the parent component, and you can get it at the child component (no matter how deep it is) if you'd like.
I made an example link
You can read more about it here
Updated: I notice you use Route, Router, and I don't use in my codesandbox. However, it's fine though. The main idea is to use context :D
Use render in component Route
<Route
path='/'
render={(props) => (
<ChildCompoent {...props}/>
)}
/>
And should not props aString in component AppDrawer
I'm not sure about the version of react and if it is still supported but this how I did this in my app.
try checking for React. Children over here: https://reactjs.org/docs/react-api.html#reactchildren
see if it's suitable for your case.
render() {
const children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
...child.props,
setHasChangesBeenMade: (nextValue) => this.setState({ hasChangesBeenMade: nextValue })
});
});
return (children[0]);
}
for you it should be something like this :
const AppDrawer: React: FC<Props> = ({
children,
}) => {
const [aString, setString] = React.useState<string>('Hello');
...
const childrens = React.Children.map(children, child => {
return React.cloneElement(child, {
...child.props,
aString: aString
});
});
<div>
<Drawer>
...
</Drawer/>
<main>
<div>
<Container>
{childrens[0]}
</Container>
</div>
</main>
</div>

React passing down hooks causes rerender and loss of focus on input

I've got a parent component in which I initialize some piece of state, which I then pass down to the children components so that they can update that. However, when the update is triggered, the component tree is re-rendered and my inputs lose focus. Adding a key did not help.
// App.tsx
export function App(props) {
const useVal = useState("");
return (
<Router>
<Switch>
<Route
exact
path="/"
component={() => (
<StartScreen
useVal={useVal}
/>
)}
/>
// ...
</Router>
);
}
// StartScreen.tsx
interface StartScreenProps {
useVal: [string, React.Dispatch<React.SetStateAction<string>>];
}
function bindState<T>(
[value, setState]: [T, React.Dispatch<React.SetStateAction<T>>]
) {
return {
value,
onChange: ({ value }: { value: T }) => setState(value)
}
}
export const StartScreen = (props: StartScreenProps) => {
return (
<form>
<InputField
key="myInput"
{...bindState(props.useVal)}
/>
</form>
);
}
So, now when I start typing on my InputField (which is basically a wrapper on an <input>) on StartScreen.tsx, the input constantly loses focus as the component is totally re-rendered (I can see it in the DOM).
This happens because you are passing a function to the Route's component prop (I assume you are using react-router-dom) :
From the docs :
If you provide an inline function to the component prop, you would
create a new component every render. This results in the existing
component unmounting and the new component mounting instead of just
updating the existing component.
To solve this problem use the render prop :
<Route
exact
path="/"
render={() => (
<StartScreen
useVal={useVal}
/>
)}
/>
This allows for convenient inline rendering and wrapping without the
undesired remounting explained above.

Categories