Return value from react component - reactjs

I am developing a react app, and I must to build a custmon dropdonw component, I need that my component returns the selected value.
I know the starndad way (or I think is the best way but, may be better if you know)
const getSelectedValue = (value) => {
console.log(value);
}
const component = <Dropdonw valueList={list} getSelectedValue={getSelectedValue} />
Is there any way to return de value using a variable or the javascript way component.options[e.selectedIndex].value. For example
const component = <Dropdonw valueList={list} />
...
<code>
...
console.log(component.getSelectedValue());
Could you tell me if there are others ways to get or return values, or the standard and best way y passing a function?

You can't return a single value from a react component. What you can do is lift the state up:
Parent:
const [dropdownVal, setDropdownVal] = useState(null)
<MyDropdown valueList={list} setDropdownVal={setDropdownVal} dropdownVal={dropdownVal} />
MyDropdown:
export const MyDropdown = ({valueList, dropdownVal, setDropdownVal}} => {
return(
<Dropdown valueList={valueList} value={dropdownVal} onChange={/* get value and call setDropdownVal */} />
)
}

Related

Does it consider bad if a react component without any state is defined and rendered inside another react component?

I know that we shouldn't define and render a react component inside another react component because it's going to mess up the states of those components.
But for the example below, I'm wondering if there are any potential issues.
export default function Post() {
const { number } = useParams();
const headerQuery = useHeader(number);
const bodyQuery = useBody(number);
const Header = () => {
if (headerQuery.isLoading) {
return <p>Loading...</p>;
}
// Assume that headerQuery.data here is just a simple string
return <div>{headerQuery.data}</div>;
};
const Body = () => {
if (bodyQuery.isLoading) {
return <p>Loading...</p>;
}
return <div>{bodyQuery.data}</div>
};
return (
<div>
{Header()}
{Body()}
{/* or
<Header />
<Body />
*/}
</div>
);
}
Because those Header and Body components don't have any states so I think it's the same as below:
export default function Post() {
const { number } = useParams();
const headerQuery = useHeader(number);
const bodyQuery = useBody(number);
const header = headerQuery.isLoading ? <p>Loading...</p> : <div>{headerQuery.data}</div>
const body = bodyQuery.isLoading ? <p>Loading...</p> : <div>{bodyQuery.dat}</div>
return (
<div>
{header}
{body}
</div>
);
}
The reason I want to define header and body as components as in the first code snippet is because I don't want to use more than one ternary operator if I have to handle more than the loading state (isLoading ? isSomething ? <h1>...<h1> : <h2>...<h2> : <div>...<div>).
I can absolutely move header and body out of post but if they're nested inside post, are there any issues?
It's perfectly OK to have react components that contain other react components. The contained components will only re-render if they rely on state that has changed, and even if they do re-render - that just updates he virtual dom which isn't free but is pretty light weight.
Each component can have its own state. That is OK and how things work. What you cannot do is introduce "conditional state" into a component. i.e. all your useXXXXX code should be at the top of your component and not inside a function you call or within an if statement, etc.
You absolutely want to use components, e.g. <Header /> and not {Header()).
I recommend watching this video to learn about the differences between <Component /> and {Component()}.

I have a problem with react useState and rendering ! when i got a value passed by params to be setted in useState already declared with default value

when i got an value passed by params to be setted in useState already declared with default value first it render with the previous (default) value then render with new value that i'm passing in params .. is there is a possible way to render only after recieving the new value !? if i wasn't clear to describe my issue please don't hesitate to ask me for more details .. thanks in advance .
trying to set useState with new value expecting component render after recieving new value and not ernder with the default value.
here is my snippet code :-
const [searchParams] = useSearchParams();
const [selectedCollectionName] = searchParams.getAll(
"selectedCollectionName"
);
const [collectionName, setCollectionName] = useState(
"PersonalProfileAdminDashboard"
);
useEffect(() => {
setCollectionName(selectedCollectionName);
}, []);
it render firstly with PersonalProfileAdminDashboard and after that it render again with the new value comes from searchParams (selectedCollectionName) ..
i want to control render to be with value comes in searchParams !
and i tried :-
const [searchParams] = useSearchParams();
const [selectedCollectionName] = searchParams.getAll(
"selectedCollectionName"
);
const [collectionName, setCollectionName] = useState(
selectedCollectionName ? selectedCollectionName : "PersonalProfileAdminDashboard"
);
and i got the same issue.
If you could provide a snippet of your code that could help.
You could provide a child component with initial state like this:
const ParentComponent = () => {
const [initialValue, setInitialValue] = useState("Initial value")
return <ChildComponent key={initialValue} initialValue={initialValue} />
}
const ChildComponent = ({ initialValue }) => {
const [inputValue, setInputValue] = useState(initialValue)
return <input value={inputValue} onChange={handleChange} />
}
The key attribute on the child component will force react to create a new instance every time the initialValue changes. This should also render the component exclusively with the state you pass it.
This will work for small components, but if you are, for example, fetching data or handling complex logic in the child component, you should definitely handle the state within the child component.
You can also not render the component if the state is, for example, null by using a ternary operation in the return section:
// ...
return initialValue ? (
<input value={inputValue} onChange={handleChange} />
) : null
// ...
Please use the below render logic
class TestComponent extends React.Component
{
render(){
const {data}=this.props;
return(<Fragment>
{(data?<div>{data.id}</div>:null)}
</Fragment>);
}
}

Create Dynamic Components

I want to dynamically create a component, when I implement something like this:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const App = () => {
const Comp = gen_Comp("Hello");
return (
<Comp txt="World" />
);
}
Something goes wrong (what exactly goes wrong is hard to explain because it's specific to my app, point is that I must be doing something wrong, because I seem to be losing state as my component gets rerendered). I also tried this with React.createElement, but the problem remains.
So, what is the proper way to create components at runtime?
The main way that react tells whether it needs to mount/unmount components is by the component type (the second way is keys). Every time App renders, you call gen_Comp and create a new type of component. It may have the same functionality as the previous one, but it's a new component and so react is forced to unmount the instance of the old component type and mount one of the new type.
You need to create your component types just once. If you can, i recommend you use your factory outside of rendering, so it runs just when the module loads:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const Comp = gen_Comp("Hello");
const App = () => {
return (
<Comp txt="World" />
);
}
If it absolutely needs to be done inside the rendering of a component (say, it depends on props), then you will need to memoize it:
const gen_Comp = (my_spec) => (props) => {
return <h1>{my_spec} {props.txt}</h1>;
}
const App = ({ spec }) => {
const Comp = useMemo(() => {
return gen_Comp(spec);
}, [spec]);
return (
<Comp txt="World" />
);
}

How can we share a state variables between two React components on the same page?

I have two components within App.js. The main App component (that contains a basic form) and a custom TextInput component. I understand that ideally, it would be better to have separate files. However, I am trying to keep this as brief and simple as possible.
Anyway, I declare a state variable with const [success, setSuccess] = useState(false); within the first component and would like to use this within the second component. I tried declaring the variable outside of both components to enable sharing, but that didn't work. How should I go about doing this? I mean normally, I would just export and import, but here, both components are on the same page.
In this case, if you have given a pascal case naming for the text input such as function TextInput(){...} , then react will consider this as a separate component even if this is defined in the same file. This is why the state variables of the App component are not within the scope of TextInput
You can mitigate this by following either of the following approcahes,
Make text input a normal function which returns the JSX
function textInput(){
// the state variable 'success' and 'setSuccess' will be normally accessible here
return(
<div>
<input type="text" />
<div>
)
}
//return for the 'App' component
return(
<>
{textInput()}
</>
)
Passing the state as props to the TextInput component
If you follow this approach, then you can have the TextInput component in a separate file as well
//parent 'App' component
function App(){
const [success, setSuccess] = useState(false);
return(
<>
<TextInput success={success} setSuccess={setSuccess}></TextInput>
</>
)
}
function TextInput(props){
const {success, setSuccess} = props;
// In here you can refer success and setSuccess normally as you would even if this is in a separate file
}
To share the state you need to declare the state in the parent component (App in your case) and send it to the child component (TextInput)
e.g.
function App() {
const [success, setSuccess] = useState(false);
...
return (
<TextInput success={success} setSuccess={setSuccess} />
);
}
function TextInput({success, setSuccess}) {
// use success/setSuccess from props
}
You will have to pass props from parent(App component) like :
function App() {
const [success, setSuccess] = useState(false);
return (
<TextInput success={success} />
);
}
and to access it in TextInput component
function TextInput(props) {
//Access it using props.success
}
Not sure if maybe I am misunderstanding but...
//App.js
function App() {
const [success, setSuccess] = useState(false);
return (
<TextInput success={success} />
<FormComponent success={success} />
);
}

Field/Input label based on record in react admin

I'd like part of the record to be included in the label for a BooleanField (and BooleanInput). I'm trying to use WithProps to accomplish this.
If I use
<BooleanField source="FileSystem" label="FileSystem" />
This seems to work just fine. If, instead I try to wrap it
const makeLabel = (props)=>{
let label = `Filesystem for ${props.record.id}`;
return {label};
}
const withLabel = withProps(makeLabel);
const BooleanFieldWithLabel = compose(withLabel)((props)=>{
console.log("props after compose",props);
return <BooleanField {...props}/>
});
And then use <BooleanFieldWithLabel source="FileSystem" /> It doesn't render any label. I've tried a few different ways and nothing seems to work even though I can see in the console.log that the correct label is in props. What am I doing wrong here?
I have the same question, I cannot display the label base on field's value on "Show" page.
From react-admin source code, it seems only I set "addLabel" prop on the direct child of "SimpleShowLayout" or "TabbedShowLayout", then I can see label on my custom field.
But it is not configurable, I want to show/hide label base on field's value. Do I need to implement my own custom "SimpleShowLayout" or "TabbedShowLayout"? Or is there any better approaches?
Update my post.
I just figure out the solution by implementing an HOC like below. I am wondering is there any better approaches to implement the same feature?
import React from "react";
import get from "lodash/get";
import { TextField, DateField, Labeled } from "react-admin";
const NullableField = WrappedComponent => props => {
const { record, source } = props;
const value = get(record, source);
return value ? (
<Labeled {...props}>
<WrappedComponent {...props} />
</Labeled>
) : null;
};
const NullableTextField = NullableField(TextField);
const NullableDateField = NullableField(DateField);
export { NullableTextField, NullableDateField };

Resources