React: How to supply callback arrow functions to stateless functional child components? - javascript

I'm struggling to supply a callback function to a stateless functional child component. I have these two components:
export const FrontDeskGUI = () => {
const callback = (event) => console.log(event.target.value);
return (
<Col xs={12}>
<Panel collapsible defaultExpanded header="Empfang">
<ButtonGrid/>
</Panel>
<Panel collapsible defaultExpanded header="Verwaltung">
<ButtonGrid/>
</Panel>
<CommandInput callback={callback}/>
</Col>);
};
export const CommandInput = (callback) => {
const style = {
textTransform: 'uppercase'
};
return (
<Col xs={12}>
<form>
<FormGroup
controlId="command">
<FormControl
type="text"
style={style}
placeholder="Kürzel eingeben"
onChange={callback}/>
</FormGroup>
</form>
</Col>);
};
During rendering I get the following error:
Warning: Failed form propType: Invalid prop onChange of type object supplied to input, expected function. Check the render method of FormControl.
Everytime I input something into the text input, I get the following error:
Uncaught TypeError: inputProps.onChange.call is not a function
at Object.executeOnChange (LinkedValueUtils.js:132)
Is this at all possible in a stateless environment? Technically the supplied callback function is constant, so there is no inherent state in the CommandInput component. I've seen some answers messing around with binding functions to the correct this pointer but I'd like to avoid that if possible.

The single argument your SFC receives is a properties object with a property for each property used on the component in the JSX markup.
So either accept the property and use its callback property:
export const CommandInput = (props) => {
// ...use props.callback...
}
or use parameter destructuring:
export const CommandInput = ({callback}) => {
// ^--------^---------------- note { }
// ... use callback...
}
Currently, you're trying to use the props object itself as the onChange, which is why you get the error.
Example using destructuring:
const Example = ({callback}) =>
<div onClick={callback}>Click me</div>;
ReactDOM.render(
<Example callback={() => console.log("Clicked")} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Related

Two components with the same state/functions but different UI

I have two components that take the exact same props and share the exact same methods. But they have different UIs. How would you recommend keeping this DRY? My first idea:
Create a Wrapper component that has the onClick method and props/state.
Create two UI components that take in the props/state and then render as they need to, such as:
<Wrapper>
<UIComponent-1 />
</Wrapper>
<Wrapper>
<UIComponent-2 />
</Wrapper>
Is there some better way to do this? I was thinking of making the wrapper an HOC but then the onClick functionality would need to be duplicated (or imported from some common file).
You could split your component into multiple components. Have one component act as the API/interface, defining the props that can be used and have a render prop that will take in the presentational component that you want to render in a certain case
Here is an example implementation of andy mccullough's answer.
This example creates a component that holds a string that can be reversed by calling the reverse function. Both the value and the reverse function are then provided to the render property. You can then decide how you attach the function to the DOM (if at all) and where and how the value is displayed.
I've also added a default render property, but if you don't have a default scenario this can be omitted.
The functions provided to render are currently anonymous functions, but if you want to re-use them you can store them in a variable or export them.
const {Fragment, useState} = React;
const {Alert, Button} = ReactBootstrap;
const defaultRender = ({value, reverse}) => <span onClick={reverse}>{value}</span>;
function Reversible({initialValue = "", render: Render = defaultRender}) {
const [value, setValue] = useState(initialValue);
const reverse = () => setValue(value => value.split("").reverse().join(""));
return <Render value={value} reverse={reverse} />;
};
ReactDOM.render(
<Fragment>
<Reversible initialValue="default render" />
<Reversible
initialValue="custom Alert render"
render={({value, reverse}) => (
<Alert variant="primary" onMouseEnter={reverse} onMouseOut={reverse}>
{value}
</Alert>
)}
/>
<Reversible
initialValue="custom Button render"
render={({value, reverse}) => (
<Button onClick={reverse}>{value}</Button>
)}
/>
</Fragment>,
document.getElementById("root")
);
<link rel="stylesheet" crossorigin="anonymous" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" />
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-bootstrap#next/dist/react-bootstrap.min.js"></script>
<div id="root"></div>
You could have a custom hook of the common methods then call the hook inside the function components. Looks like your issue has been specified https://reactjs.org/docs/hooks-effect.html.
As an example
const useCustomHook = (someArgs) => {
// perform some effects
return value;
}
// Then your component
const Component = (props) => {
const value = useCustomHook(someArgs);
return <div></div>
}
I hope it helps.

How to access ref that was set in render

Hi I have some sort of the following code:
class First extends Component {
constructor(props){super(props)}
myfunction = () => { this.card //do stuff}
render() {
return(
<Component ref={ref => (this.card = ref)} />
)}
}
Why is it not possible for me to access the card in myfunction. Its telling me that it is undefined. I tried it with setting a this.card = React.createRef(); in the constructor but that didn't work either.
You are almost there, it is very likely that your child Component is not using a forwardRef, hence the error (from the React docs). ref (in a similar manner to key) is not directly accesible by default:
const MyComponent = React.forwardRef((props, ref) => (
<button ref={ref}>
{props.children}
</button>
));
// ☝️ now you can do <MyComponent ref={this.card} />
ref is, in the end, a DOMNode and should be treated as such, it can only reference an HTML node that will be rendered. You will see it as innerRef in some older libraries, which also works without the need for forwardRef in case it confuses you:
const MyComponent = ({ innerRef, children }) => (
<button ref={innerRef}>
{children}
</button>
));
// ☝️ now you can do <MyComponent innerRef={this.card} />
Lastly, if it's a component created by you, you will need to make sure you are passing the ref through forwardRef (or the innerRef) equivalent. If you are using a third-party component, you can test if it uses either ref or innerRef. If it doesn't, wrapping it around a div, although not ideal, may suffice (but it will not always work):
render() {
return (
<div ref={this.card}>
<MyComponent />
</div>
);
}
Now, a bit of explanation on refs and the lifecycle methods, which may help you understand the context better.
Render does not guarantee that refs have been set:
This is kind of a chicken-and-egg problem: you want the component to do something with the ref that points to a node, but React hasn't created the node itself. So what can we do?
There are two options:
1) If you need to pass the ref to render something else, check first if it's valid:
render() {
return (
<>
<MyComponent ref={this.card} />
{ this.card.current && <OtherComponent target={this.card.current} />
</>
);
}
2) If you are looking to do some sort of side-effect, componentDidMount will guarantee that the ref is set:
componentDidMount() {
if (this.card.current) {
console.log(this.card.current.classList);
}
}
Hope this makes it more clear!
Try this <Component ref={this.card} />

How to pass two functions to onClick event in react

I want to pass two functions to onClick event which is handleSubmit and handleDelete to the HomePage.js from the HomeItem.js
Here is my Error:
No duplicate props allowed react/jsx-no-duplicate-props.
Here is my HomePage.js:
const HomePage = props => {
const tvshow = props.item;
let res;
if (tvshow.length > 0) {
res = tvshow.map(res=> (
<Content item={res} onClick={props.onClick}/>
));
}
return (
<div>
<Container>
<Row>{res}</Row>
</Container>
</div>
);
};
export default HomePage;
Here is my HomeItem.js:
const HomeItem = props => {
function handleSubmit() {
props.onClick({
name: props.item.name,
id: props.item.id
});
}
function handleName() {
props.onClick({
name: props.item.name
});
}
<Button onClick={handleSubmit}></Button>
<Button onClick={handleName}></Button>
Here is my App.js:
handleSubmit(newFavorite) {}
handleName(newFavorite) {}
render() {
<Route
exact
path="/"
render={() => (
<HomePage
item={this.state.SaveFavorite}
onClick={this.handleSubmit}
onClick={this.handleName}
/>
)}
/>
}
So my question is how to put 2 onClick function to the Hompage.js
How about this:
<HomePage
item={this.state.SaveFavorite}
onClick={(favorite)=>{
this.handleSubmit(favorite);
this.handleName(favorite);
}
}
/>
This assumes your goal is to call both functions one at a time. If they should be called in different situations give one function a different name, eg onSubmit or onNameChange.
Try This:
<HomePage
item={this.state.SaveFavorite}
onClick={(newFavorite) => this.handleSubmit(newFavorite);this.handleName(newFavorite)}
/>
you can pass multiple functions to events in react, let say changeEvent, to do follow those steps.
1- create your function two or the number of function you like.
2- create an object that contains those functions
3- pass the object as a props to where it would be consumed
4- choose the correspondant function to each form or whatever you need.
here is an example, this sample is with typescript.
const onChangeFunctions = {
onChangeForm1: handleChangeForm1,
onChangeForm2: handleChangeForm2,
};
<MainForm
onChange={onChangeFunctions} // here is your function
datas={yourData}
otherProps={otherProps}
/>
Now you use the fucntion on the child components
interface PropsFrom {
model1: Model1;
model2: Model2;
onChange: any;
}
export default function ProductForm(props: PropsForm) {
return (
<Container maxWidth="lg">
<Grid item md={6}>
<FormOne
model={props.model1} // the model that bind your form
onChange={props.onChange.onChangeForm1} // here you can use your first function
/>
</Grid>
<Grid item md={6}>
<FormTwo
model={props.model2} // the model that bind your form
onChange={props.onChange.onChangeForm2} // here you can use your second function
/>
</Grid>
</Grid>
</Container>
for javascript just pass the functions as props and delete the interface from the child components.

How to write a wrapper around a material UI component using React JS?

I am using Material UI next library and currently I am using List component. Since the library is in beta, lot of its parameter names get changed. To solve this I am planning to write a wrapper around the required components so that things wont break. My list component :
<List dense>
<List className={classes.myListStyles}>
<ListItem disableGutters/>
</List>
</List>
How should I write the wrapper for the List(say myListWrapper) and ListItem so that the wrapper component can handle props and pass them to the actual MUI list component inside?
I had worked on MUI wrappers, writing my own library for a project. The implementation we are focusing, is to pass the props to inner/actual-MUI component from the our wrapper component. with manipulation. In case of wrapping props for abstraction.
Following is my approach to the solution:
import { List as MaterialList } from 'material-ui/List';
import { React } from 'react';
import { ListItem as MaterialListI } from 'material-ui/ListItem';
class List extends MaterialList {
constructor(props){
const propsToPass = {
prop1 : change(props.prop1),
...props
}
super(propsToPass);
}
};
class ListItem extends MaterialListItem {
const propsToPass = {
prop1 : change(props.prop1),
prop2 : change(props.prop2),
...props
}
super(propsToPass);
}
};
class App extends React.Component {
render () {
return (
<List prop='value' >
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
<ListItem prop1={somevalue1} prop2={somevalue2} />
</List>
)
}
};
Above code will allow following things to do with your component:
You can use the props with exact names, as used in Material UI.
You can manipulate/change/transform/reshape you props passed from outside.
If props to you wrapper components are passed with exactly same names as MUI is using, they will directly be sent to the inner component. (... operator.)
You can use Component with exact same name as material is using to avoid confusion.
Code is written according to advance JSX and JavaScript ES6 standards.
You have a space to manipulate your props to pass into the MUI Components.
You can also implement type checking using proptypes.
You can ask for any confusion/query.
You can write it like this:
const MyList = props => (
<List
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</List>
)
const MyListItem = props => (
<ListItem
{/*mention props values here*/}
propA={props.A}
propB={props.B}
>
{props.children}
</ListItem>
)
Now you need to use MyList and MyListItem, decide the prop names for these component (as per your convenient), and inside these component map those values to actual Material-UI component properties.
Note:
If you are using the same prop names (same name as material-ui component expect) for your component then you can write like this also:
const MyList = ({children, ...rest}) => <div {...rest}>{children}</div>
const MyListItem = ({children, ...rest}) => <p {...rest}>{children}</p>
Check this example:
const A = props => <div>{props.children}</div>
const B = props => <p>{props.children}</p>
ReactDOM.render(
<A>
<A>
<B>Hello</B>
</A>
</A>,
document.getElementById('app')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app' />

How Destructuring Works In Function Parameters

I tried to search on this but no luck so far. I've looked at destructuring info on the web. I don't understand though this partcular pattern of destructuring.
const App = ({todos, actions}) => (
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
)
what is {todos, actions} doing here in the function's param definition? What todos and actions is it pulling from?
If you call App like App({todos:10,actions:{addTodo: addTodoFunction}}) in the App function the arguments todos and actions get assigned to 10 and {addTodo: addTodoFunction} respectively. So actions.addTodo becomes addTodoFunction. More at Object Destructuring.
Those are the props that React passes to your component:
<App todos={...} actions={...} />
So you could write your component without destructuring like this:
const App = (props) => (<div>
<Header addTodo={props.actions.addTodo} />
<MainSection todos={props.todos} actions={props.actions} />
</div>)
React passed the props to your component as an object, and destructuring extracts properties from an object, so it is a shortcut!
You could also destructure your props in two steps:
const App = (props) => {
const { todos, actions } = props
return (<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>)
}
Notice that in this last case you have to use curly braces and return explicitly because there are multiple statements in your arrow function.

Categories

Resources