Passing props to dynamic react component - reactjs

So I have the below code to load a custom component called foo. Loading of the component works fine, but props arent passing to it like I would prefer
Container component
....
const id= foo
React.createElement(LoadComponent(id, attributes))
...
Custom component
export const LoadComponent = (id, attributes) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
How do I pass attributes prop to the Component in this case? I keep getting render exceptions.

Here is a simplified demo: https://codesandbox.io/s/zrw7x1zrrp
props are being passed to the component as the second parameter of createElement
const id = "foo";
const LoadComponent = props => {
return props.id;
};
ReactDOM.render(
React.createElement(LoadComponent, { id }, null),
document.getElementById("root")
);
React.createElement(component, props, ...children)
https://reactjs.org/docs/react-without-jsx.html

It's a bit confusing what you are trying to do. If you are using JSX you shouldn't need to invoke React.createElement.
If you insist of doing it without JSX though, React.createElement can take 3 parameters as per React's API. So, in your case your code will be React.createElement(LoadComponent, { id, attributes }, null) where the third parameter is the children.
Now, the id and attributes are accessible from within the props object of your custom component. So you have two options:
Destructure the props object:
export const LoadComponent = ({ id, attributes }) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
Use the props object directly:
export const LoadComponent = (props) => {
/*This will load up foo.js*/
const Component = require(`./${props.id}`);
return Component;
};

Related

how to handle props between components in react

i have came accross a problem where i am passing three props to a component Landingheader from parent Landing.js now i have another component called Cart and i want to use LandingHeader
as child component inside Cart but then i would also have to pass all the three props again to Landingheader which is very difficult and alot of code to rewrite
here is the code in Landing.js
<div>
<Landingheader
fetchproductResults={fetchproductResults}
user={user}
cartValue={cartValue}
/>
above you can see landingHeader component is getting three differenct props
here is my cart component where i want to resuse landingHeader component
import { Fragment } from "react";
import Landingheader from "./landingHeader";
const Cart = () => {
return (
<Fragment>
<Landingheader />
</Fragment>
);
}
export default Cart;
so above the landingHeader will now require three props so this means i would have to rewrite the whole logic again? how to solve this propblem? thanks
code for fetchproductResults
const fetchproductResults = (keyword) => {
setWord(keyword);
if (keyword !== "") {
const searchedRs = allproducts.filter((eachproduct) => {
return Object.values(eachproduct)
.join("")
.toLowerCase("")
.includes(keyword.toLowerCase());
});
setResult(searchedRs);
} else {
setResult(allproducts);
}
};
In case you don't need to pass any props to Landingheader from Cart you could use default value props in Landingheader. Something like:
const Landingheader = (props) => {
const { fetchproductResults = [], user = "", cartValue = "" } = props;
return (...);
}
export default Landingheader;
You can use context instead of props
in Landing component:
const MyContext=createContext(null)
const Landing=()=>{
.......
return (<MyContext.Provider value={[fetchproductResults,user,cartValue]}>
... all child compoenents
</MyContext.Provider/>
Now in Landingheader :
const [fetchproductResults,user,cartValue]=useContext(MyContext) /// use them as you like
Now you don't need to pass any props to either Cart or LandingHeader, it is receiving the data through context.

Create custom component wrapper

I am migrating react code to typescript and have one issue with type custom component wrapper.
I am sending as prop, from which i will create component and use it as JSX. By default it is set to div but also it can be React component, specifically custom Link component, which return RouterLink from react-router-dom or a tag.
Current code
function DropdownMenuItemWrapper({
as ='div',
to,
target,
onClick,
active,
children
}) {
const Wrapper = as ;
return (
<Wrapper
to={to}
target={target}
onClick={onClick}
>
{children}
</Wrapper>
);
}
Issue is, i do not know, how to type as property and how to use Wrapper. If i try something like:
const Wrapper = React.createElement(as); or just const Wrapper = as;
i got error: JSX element type 'Wrapper' does not have any construct or call signatures.
Close :)
type DropdownMenuItemWrapperProps = {
as: Parameters<typeof React.createElement>[0],
// other props type
}
const DropdownMenuItemWrapper = ({
as = "div",
to,
target,
onClick,
active,
children
}: DropdownMenuItemWrapperProps) => React.createElement(as, { to, target, onClick }, children);

React child component does not re-render when props passed in from parent changes

I have a simplified react structure as below where I expect MyGrandChildComponent to re-render based on changes to the 'list' property of MyParentComponent. I can see the list take new value in MyParentComponent and MyChildComponent. However, it doesnt even hit the return function of MyGrandChildComponent. Am i missing something here?
const MyGrandChildComponent = (props) => {
return (
<div>props.list.listName</div>
);
};
const MyChildComponent = (props) => {
return (
<div><MyGrandChildComponent list={props.list}/></div>
);
}
const MyParentComponent = (props) => {
const list = { listName: 'MyList' };
return (
<div><MyChildComponent list={list} /></div>
);
}
In your MyParentComponent, the list is not a state variable and as such changing it will not even cause a re-render. If you absolutely want that when ever you change the value of list it re renders, then you will want to bring state to your functional component and the way to do that is to use hooks.
In this case your parent component will be something like below
import React, {useState} from 'react'
const MyParentComponent = (props) => {
const [list, setList] = useState({ listName: 'MyList' });
return (
<div><MyChildComponent list={list} /></div>
);
}
then at the child component you render it as I suggested in the comment above.
The parent needs to hold the list as a state variable and not just as a local variable. This is because react rerenders based on a state or prop change and at the parent you can only hold it in the state. With this when the value of list changes there will be a re-render which will then propergate the change to the children and grandchildren.
Also the only way of maintaining state in a functional component is to use hooks.
const MyGrandChildComponent = (props) => {
return (
<div>{props.list.listName}</div>
);
};
You forgot the {} around props.list.listName

Shallow render a functional stateless component -react

I have a functional component in React defined as follows
const MyComponent = ({
prop1,
prop2,
prop3
}, param1 , param2) => {
return [
//list of spans
]
}
In the spec file, I used shallow to render the component
const fakeObj = {
prop1:value,
prop2:value,
prop3:value
}
shallow(<MyComponent {...fakeObj} param1={something} param2={something} />)
However, when I console.log the params in the MyComponent, I get {} and undefined for param1 and param2 respectively,while the fakeObj is received fine.Is there any way where we can shallow render the component whilst passing object as one of the parameters?
When I just call the component from the spec file as a function ie
MyComponent({fakObj},param1,param2)
,I get the correct values for params,but not able to find the span elements properly using enzyme.
React functional component, maybe the function (function to create component) accepts only one parameter to get its props. So you can't define functional component in the way (define multiple parameters) what you did. You have to define it like following.
let MyComponent = (props) => {
// props is an object, it contains all props passed by component call.
// So you can get property value as props.propName
return 'something' // what you like to return.
}
If you use the component like bellow
<MyComponent prop1="Propone 1 Value" prop2="Prop 2 Value" prop3="Prop 3 Value" />
And console props inside your component like
let MyComponent = (props) => {
console.log(props);
return 'something' // what you like to return.
}
You will get all your passed properties as function argument (as props parameter) as an object like below.
{
prop1:"Propone 1 Value",
prop2:"Propone 2 Value",
prop3:"Propone 3 Value"
}
Now come to your requirement. You can create your component like following
let Home = (props) => {
let {fakeObj, param1, param2} = props;
return [
//list of spans
]
}
And call component like this
const fakeObj = {
prop1:value,
prop2:value,
prop3:value
}
shallow(<MyComponent fakeObj = {fakeObj} param1={something} param2={something} />)

Joining React props with custom props

When defining props in React, using Typescript, it seems that the default React props get overwritten by whatever the interface is. Is there a clean way to merge the two without having to specify every prop React already knows about?
Example:
interface IProps { // extends React.???
title: string;
// Generally seen adding children?: any
// But this can get out of hand with onClick, onChange, etc
}
function MyComponent(props: IProps) {
return props.children; // Typescript error: children doesn't exist on props
}
What you're referring to as "React default props" aka "every prop React already knows about" are more properly called "props accepted by any React DOM element wrapper component", i.e. onClick, className, etc.
Default props typically refers to the static defaultProps property on a React.Component, with which you provide default values for any props that were not passed to your component.
onClick, className, etc. are not reserved prop names and you can use them however you want in your own components, for instance you could have your component expect className to be a function (regardless of whether it's a good idea). The only reserved prop names that work on React elements of any kind (at the time of writing) are key and ref, and they're not really true props because they're not available to your component's render method.
Passing onClick to your own component does not automatically register a click handler. It will only do so if you pass the onClick you received to a <div>, <button>, or other React DOM Element wrapper that you render somewhere down the line. If you don't do anything with a prop you were passed, it has no effect (besides possibly causing a pure render component to update when it otherwise wouldn't).
For example, the following component will not respond to clicks:
const ClickFail = props => <button />
render(<ClickFail onClick={...} />, document.getElementById('root'))
But the following will:
const ClickSuccess = props => <button onClick={props.onClick} />
render(<ClickSuccess onClick={...} />, document.getElementById('root'))
And you could pass onClick to only one subelement if you really wanted:
const ClickButtonOnly = props => (
<form>
<input placeholder="ignores clicks" />
<button onClick={props.onClick}>Handles Clicks</button>
</form>
)
Or you could pass in multiple click handlers with different names:
const SimpleForm = props => (
<form>
<button onClick={props.onCancelClick}>Cancel</button>
<button onClick={props.onOKClick}>OK</button>
</form>
)
Also keep in mind that some DOM element wrappers accept props that others do not, for instance readOnly applies only to <input> and <textarea>.
You can even require children to be whatever type you want. For instance, you can pass a function as the children of a component and use it (again, not the best use of React, but just to illustrate what's possible):
type Props = {
value: number,
children: (value: number) => number,
}
const ApplyFunction = (props: Props) => (
<div>{React.Children.only(props.children)(props.value)}</div>
)
render(
<ApplyFunction value={3}>
{value => value * value}
</ApplyFunction>,
document.getElementById('root')
)
// renders <div>9</div>
So you see, IProps does not necessarily have to extend anything.
However, it is common to pass along rest props to a React DOM Element wrapper (e.g. <div {...props}>...</div> and as you were asking, it would be nice to be able to check the type of all of those input properties to your component.
I think you could do the following with Flow to check the types correctly, but unfortunately I don't think there's any Typescript equivalent (someone correct me if I'm wrong):
type Props = React.ElementProps<typeof 'div'> & {
title: string,
}
const MyComponent = (props: Props) => (
<div {...props}>
{props.title}
</div>
)
You should define that your stateless functional component will return React.SFC<YourProps>.
Try this
import * as React from "react";
const MyComponent: React.SFC<IProps> = (props) => {
return props.children;
}
If you want to use class-based component, you can extend your class with React.Component<YourProps(optional), YourState(optional)> instead
For example
import * as React from "react"
class MyComponent extends React.Component<IProps> {
public render(): JSX.Element {
return (
<div>...</div>
);
}
}
type TitleProps = { // your custom props
level?: level;
color?: string;
};
const Title = (props: TitleProps & React.Component['props']) => { // join default props and custom props
const { level = 'h1', color, children } = props; // joined props containing default props
return <Text style={[styles[level], color && { color }]}>{children}</Text>;
}
I was able to solve the problem by this way.

Resources