React TypeScript - passing a callback function - reactjs

The following code would have worked happily in JavaScript. I am using React+TypeScript, hence this is a JSX file:
Calling component:
<FiltersPanel onFiltersChanged={() => { console.log('I was called') }} />
Inner Component:
const FiltersPanel = (onFiltersChanged) => {
const handleFiltersChange = () => {
onFiltersChanged(); /* I am getting `TypeError: onFiltersChanged is not a function` */
};
return (<div onClick={handleFiltersChange}/> );
};
export default FiltersPanel;
Why is TypeScript complaining that it cannot find the function, while the function is certainly passed as a prop.

You passed the function as a props so you should receive it to your component as a props like this
const FiltersPanel = (props) => { }
then use it like this
props.onFiltersChanged()
OR you can use destructuring like this
const FiltersPanel = ({onFiltersChanged}) => { }
so you can use it without the props object like this
onFiltersChanged()

Related

REACT: How to set the state in the child and access it in the parent, receiving undefined

I am building this project to try and improve my understanding of react :), so I am a n00b and therefore still learning the ropes of extracting components, states, props etc =)
I have a child Component DescriptionDiv, its parent component is PlusContent and finally the parent component is PlusContentHolder. The user types some input into the DescriptionDiv which then, using a props/callback passes the user input to the PlusContent.
My question/problem is: after setting useState() in the PlusContent component, I am after a button click in the PlusContentHolder component, returned with an undefined in the console.log.
How come I cannot read the useState() in the next parent component, the PlusContentHolder?
I know that useState() is async so you cannot straight up call the value of the state in the PlusContent component, but shouldn't the state value be available in the PlusContentHolder component?
below is my code for the DescriptionDiv
import './DescriptionDiv.css';
const DescriptionDiv = props => {
const onDescriptionChangeHandler = (event) => {
props.descriptionPointer(event.target.value);
}
return (
<div className='description'>
<label>
<p>Description:</p>
<input onChange={onDescriptionChangeHandler} type='text'></input>
</label>
</div>);
}
export default DescriptionDiv;
Next the code for the PlusContent comp
import React, { useState } from "react";
import DescriptionDiv from "./div/DescriptionDiv";
import ImgDiv from "./div/ImgDiv";
import "./PlusContent.css";
import OrientationDiv from "./div/OrientationDiv";
const PlusContent = (props) => {
const [classes, setClasses] = useState("half");
const [content, setContent] = useState();
const [plusContent, setPlusContent] = useState({
orientation: "left",
img: "",
description: "",
});
const onOrientationChangeHandler = (orientationContent) => {
if (orientationContent == "left") {
setClasses("half left");
}
if (orientationContent == "right") {
setClasses("half right");
}
if (orientationContent == "center") {
setClasses("half center");
}
props.orientationInfo(orientationContent);
};
const onDescriptionContentHandler = (descriptionContent) => {
props.descriptionInfo(setPlusContent(descriptionContent));
console.log(descriptionContent)
};
const onImageChangeHandler = (imageContent) => {
props.imageInfo(imageContent);
setContent(
<>
<OrientationDiv
orientationPointer={onOrientationChangeHandler}
orientationName={props.orientationName}
/> {/*
<AltDiv altPointer={onAltDivContentHandler} />
<TitleDiv titlePointer={onTitleDivContentHandler} /> */}
<DescriptionDiv descriptionPointer={onDescriptionContentHandler} />
</>
);
};
return (
<div className={classes}>
<ImgDiv imageChangeExecutor={onImageChangeHandler} />
{content}
</div>
);
};
export default PlusContent;
and lastly the PlusContentHolder
import PlusContent from "../PlusContent";
import React, { useState } from "react";
const PlusContentHolder = (props) => {
const onClickHandler = (t) => {
t.preventDefault();
descriptionInfoHandler();
};
const descriptionInfoHandler = (x) => {
console.log(x) // this console.log(x) returns and undefined
};
return (
<div>
{props.contentAmountPointer.map((content) => (
<PlusContent
orientationInfo={orientationInfoHandler}
imageInfo={imageInfoHandler}
descriptionInfo={descriptionInfoHandler}
key={content}
orientationName={content}
/>
))}
<button onClick={onClickHandler}>Generate Plus Content</button>
</div>
);
};
export default PlusContentHolder;
The reason why the descriptionInfoHandler() function call prints undefined in its console.log() statement when you click the button, is because you never provide an argument to it when you call it from the onClickHandler function.
I think that it will print the description when you type it, however. And I believe the problem is that you need to save the state in the PlusContentHolder module as well.
I would probably add a const [content, setContent] = useState() in the PlusContentHolder component, and make sure to call setContent(x) in the descriptionInfoHandler function in PlusContentHolder.
Otherwise, the state will not be present in the PlusContentHolder component when you click the button.
You need to only maintain a single state in the PlusContentHolder for orientation.
Here's a sample implementation of your use case
import React, { useState } from 'react';
const PlusContentHolder = () => {
const [orientatation, setOrientation] = useState('');
const orientationInfoHandler = (x) => {
setOrientation(x);
};
const generateOrientation = () => {
console.log('orientatation', orientatation);
};
return (
<>
<PlusContent orientationInfo={orientationInfoHandler} />
<button onClick={generateOrientation}>generate</button>
</>
);
};
const PlusContent = ({ orientationInfo }) => {
const onDescriptionContentHandler = (value) => {
// your custom implementation here,
orientationInfo(value);
};
return <DescriptionDiv descriptionPointer={onDescriptionContentHandler} />;
};
const DescriptionDiv = ({ descriptionPointer }) => {
const handleChange = (e) => {
descriptionPointer(e.target.value);
};
return <input type="text" onChange={handleChange} />;
};
I would suggest to maintain the orientation in redux so that its easier to update from the application.
SetState functions do not return anything. In the code below, you're passing undefined to props.descriptionInfo
const onDescriptionContentHandler = (descriptionContent) => {
props.descriptionInfo(setPlusContent(descriptionContent));
};
This shows a misunderstanding of the use of state. Make sure you're reading about "lifting state" in the docs.
You're also declaring needless functions, e.g. onDescriptionContentHandler in your PlusContent. The PlusContent component could just pass the descriptionInfoHandler from PlusContentHolder prop directly down to DescriptionDiv, since onDescriptionContentHandler doesn't do anything except invoke descriptionInfoHandler.
You may want to consider restructuring your app so plusContent state is maintained in PlusContentHolder, and pass that state down as props. That state would get updated when DescriptionDiv invokes descriptionInfoHandler. It'd subsequently pass the updated state down as props to PlusContent.
See my suggested flowchart.

React - Get displayName of functional component inside the component?

Is it possible to get the name of a functional component inside it?
Something like:
function CarWasher(props) {
const handleOnPress = () => {
console.log(this.displayName); // <-- Something like this displayName
}
return ...JSX;
};
CarWasher.displayName = "CarWasher";
You can reference the .name property of the function.
function CarWasher(props) {
const handleOnPress = () => {
console.log(CarWasher.name);
}
handleOnPress();
};
CarWasher();
If you're worried about accidentally making a typo when referencing one of the above variables, consider using TypeScript or at least the no-undef ESLint rule.
Also, with displayName:
function MemoizedCarWasher(props) {
const handleOnPress = () => {
console.log(MemoizedCarWasher.displayName);
}
handleOnPress();
};
MemoizedCarWasher.displayName = "CarWasher";
MemoizedCarWasher();

Set React Context inside function-only component

My goal is very simple. I am just looking to set my react context from within a reusable function-only (stateless?) react component.
When this reusable function gets called it will set the context (state inside) to values i provide. The problem is of course you can't import react inside a function-only component and hence I cannot set the context throughout my app.
There's nothing really to show its a simple problem.
But just in case:
<button onCLick={() => PlaySong()}></button>
export function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //cannot call useContext in this component
}
If i use a regular react component, i cannot call this function onClick:
export default function PlaySong() {
const {currentSong, setCurrentSong} = useContext(StoreContext) //fine
}
But:
<button onCLick={() => <PlaySong />}></button> //not an executable function
One solution: I know i can easily solve this problem by simply creating a Playbtn component and place that in every song so it plays the song. The problem with this approach is that i am using a react-player library so i cannot place a Playbtn component in there...
You're so close! You just need to define the callback inside the function component.
export const PlaySongButton = ({...props}) => {
const {setCurrentSong} = useContext(StoreContext);
const playSong = () => {
setCurrentSong("some song");
}
return (
<button
{...props}
onClick={() => playSong()}
/>
)
}
If you want greater re-usability, you can create custom hooks to consume your context. Of course where you use these still has to follow the rules of hooks.
export const useSetCurrentSong = (song) => {
const {setCurrentSong} = useContext(StoreContext);
setCurrentSong(song);
}
It is possible to trigger a hook function by rendering a component, but you cannot call a component like you are trying to do.
const PlaySong = () => {
const {setCurrentSong} = useContext(StoreContext);
useEffect( () => {
setCurrentSong("some song");
}, []
}
return null;
}
const MyComponent = () => {
const [shouldPlay, setShouldPlay] = useState(false);
return (
<>
<button onClick={() => setShouldPlay(true)}>Play</button>
{shouldPlay && <PlaySong />}
</>
)
}

Reactjs hook that uses other hooks

I needed to create a custom hook which is supposed to contain all the handlers that will be used everywhere in my page. My requirements were;
Handlers are supposed to be accessible from all the components in the page
Handlers should be able to use other hooks, like useContext
So, created a useHandlers hook sandbox
However, couldn't make the LogHandler accessible from the page, receving LogHandler is not a function
Any idea?
The issue why you're getting LogHandler is not a function is because it's undefined and it doesn't get initialized until HandlerComp gets called:
export const userHandlers = (): IUseHandlers => {
// initialization skipped, so `LogHandler` is undefined
let LogHandler: () => void;
const HandlersComp: React.FunctionComponent<HandlersProps> = (
props: HandlersProps
) => {
// initialized here, but that's after `HandlersComp` gets caled
LogHandler = () => {
console.log("Hi from LogHandler");
};
return <></>;
};
return { HandlersComp, LogHandler };
}
I suggest you move the initialization step from HandlersComp like so:
export const useHandlers = (): IUseHandlers => {
const LogHandler: () => void = () => {
console.log("Hi from LogHandler");
};
const HandlersComp: React.FunctionComponent<HandlersProps> = (
props: HandlersProps
) => {
LogHandler()
return <></>;
};
return { HandlersComp, LogHandler };
};
Some notes:
HandlersComp looks like it should be a separate and reusable component, rather than a hook
LogHandler also looks more like a utility function, rather than a hook
LogHandler shouldn't be using PascalCase for naming as that should be "reserved" for React components; HandlersComp is fine, since it looks like it's a component

Problem chaining Higher Order Components <Functions are not valid as a React child.>

I created a sample project to demonstrate a problem I have when trying to use two Higher Order Components (hoc) together.
First in isolation (no errors)
================================
The first hoc withStuff takes an injected argument and a prop and passes the sum to the wrapped component.
// withStuff.js
const withStuff = ({argNumber}) => (BaseComponent) => ({propNumber, ...passThroughProps}) => {
const sum = argNumber+propNumber
return <BaseComponent sum={sum} {...passThroughProps} />
}
export default withStuff
The second hoc withExtra takes an injected function and doubles the result, passing double to the wrapped component.
// withExtra.js
const withExtra = (extraFunction) => (BaseComponent) => ({...passThroughProps}) => {
const double = 2*extraFunction()
return <BaseComponent double={double} {...passThroughProps} />
}
export default withExtra
This is how a Base component would use for instance withStuff (all working fine so far).
// Base.js
import withStuff from './withStuff'
const Base = ({content, sum}) => <div>{content} -sum:{sum}</div>
export default withStuff({argNumber:2})(Base)
=================================
Now comes the problem: trying to use withExtra inside withStuff:
import withExtra from './withExtra'
const withStuff = ({argNumber}) => (BaseComponent) => ({propNumber, ...passThroughProps}) => {
const sum = argNumber+propNumber
// this does not work
return withExtra(()=>sum)(<BaseComponent sum={sum} {...passThroughProps}/>)
}
export default withStuff
This returns an error:
Warning: Functions are not valid as a React child.
Is it because now withStuff is returning a hoc function instead of a component? That function returns a component itself, so I cannot see the problem. How to solve this?
NOTE CODESANDBOX HERE: https://codesandbox.io/s/github/snirp/hoc-test
withExtra is supposed to get a component, so I think this line
return withExtra(()=>sum)(<BaseComponent sum={sum} {...passThroughProps}/>)
should either be:
return withExtra(()=>sum)(BaseComponent)
or
return withExtra(()=>sum)(() => <BaseComponent sum={sum} {...passThroughProps}/>)
The problem is that you passing the ReactElement and not the component.
Refer to what is JSX behind the scenes.
Note that if you want to add additional properties to given ReactElement you can use cloneElement.
const withStuff = ({ argNumber }) => BaseComponent => ({
propNumber,
...passThroughProps
}) => {
const sum = argNumber + propNumber;
const callback = () => sum;
// Like so you passing the node which leads to error
// return withExtra(callback)(<BaseComponent sum={sum} {...passThroughProps}
// Passing the reference
return withExtra(callback)(BaseComponent);
// Passing with additional props
// return withExtra(callback)(React.cloneElement(BaseComponent, ...));
// Equivalent
// const WithExtraProps = withExtra(() => sum)(BaseComponent);
// return <WithExtraProps sum={sum} {...passThroughProps} />;
};
Your passing JSX to withExtra not a component, Change withStuff like this :
const withStuff = ({argNumber}) => (BaseComponent) => ({propNumber, ...passThroughProps}) => {
const sum = argNumber+propNumber
// this works
// return <BaseComponent sum={sum} {...passThroughProps} />
const WithExtraComponent = withExtra(()=>sum)(BaseComponent);
return <WithExtraComponent sum={sum} {...passThroughProps}/>
}
https://codesandbox.io/s/hoc-test-pm02u
This should solve it:
return withExtra(() => sum)(BaseComponent)();
https://codesandbox.io/s/hoc-test-thxh0

Resources