React - pass prop that needs to be awaited for - javascript

Im trying to pass down from my component a prop that comes from an async function.
I have the following code:
export const Bar = (props: Props) => {
...
const getValue = async () => {
const { value } = await initValue();
return value;
}
...
return (
<Foo value={getValue()}/> //Error in this line
}
TS throws an error:
Type 'Promise' is missing the following properties from type 'Element': type, props, key ts(2739)
Blockquote
How can I achieve this?

No need to call getValue(), just send it as a function variable then call it where is needed
<Foo value={getValue}/>
In addition, in your case, the best way would be to set the value as a state and then send the value as props.

Related

Destructuring react hook results in: This expression is not callable

I'm creating a custom hook and want to return an object and two functions when the hook is called. I do not want to return it as return {body, setProperty, setBody}, since I might call the hook multiple times within the same component and need different names for the variables.
I'd want to call it just as useState where I can destructure it as an array const [firstBody, setFirstBodyProp, setFirstBody] = useJSONState({/*Some code*/}), but when I try to return it as such return [body, setProperty, setBody], I get the following error when calling it from a component:
This expression is not callable.
Type 'jsonType' has no call signatures.ts(2349)
My Code:
type jsonType = {
[property: string]: any
}
const useJSONState = (json: jsonType) => {
const [ body, setBody ] = useState(json)
function setProp(property: string, value: any){
let newBody = {...body}
newBody[property] = value
setBody(newBody)
}
return [body, setProp, setBody]
}
export default useJSONState
The reason for the error is TypeScript inference. Your hook is returning an array of body,setProp,setBody but TypeScript infers the type to be jsonType[], which is the type of body(the first element in the array). To solve this error, you have to specify the return type of the hook explicitly.
export const useJSONState = (
json: jsonType
): [
jsonType,
(property: string, value: any) => void,
React.Dispatch<React.SetStateAction<jsonType>>
] => {
// .....
return [body, setProp, setBody];
}

Cannot invoke an object which is possibly 'undefined' in children component

In my expo typescript app, I have a function in a root component:
const getCardType = (type = ECardType.WOOD) => {
setCardType(type);
};
and pass it in my first child component:
<Slider data={slideData} autoPlay={false} getCardType={getCardType} />
Here is my first child component where I pass the function, and it types declaration:
readonly getCardType?: (type: ECardType) => void;
const Slider: React.FunctionComponent<ISliderProps> = ({
data,
autoPlay,
getCardType,
})
After that I pass it into a second child component:
<SliderCardItem cardItem={item} index={index} getCardType={getCardType} />
And in this SliderItem component, I use this function:
useEffect(() => {
getCardType(cardType);
}, [cardType]);
But Have a TS error: Cannot invoke an object which is possibly 'undefined' in children component
I set the cardType below in an onPress()
I have this error only in this component
An idea to fix this error?
getCardType might be undefined, as stated in your type here:
getCardType?: (type: ECardType) => void;
Then you're trying to call it without checking if it exists:
useEffect(() => {
getCardType(cardType);
}, [cardType]);
So you'll need to perform that check:
useEffect(() => {
if (getCardType) getCardType(cardType);
}, [cardType]);
or with optional chaining:
useEffect(() => {
getCardType?.(cardType);
}, [cardType]);
If it will always be present then you can make it non optional in your type:
getCardType: (type: ECardType) => void;
Remove the ? in the type declaration, if it's required. If it's not required, you have to check if it exists first.
Another point, getCardType is actually a dependency of that effect as well, but by looking at it it's safe, to ignore it (because it just wraps a setState) call.
I don't like ignoring stuff, though. So if I were you, I'd probably write it like this:
// useCallback makes getCardType referentially identical between renders
const getCardType = useCallback((type = ECardType.WOOD) => {
setCardType(type);
// safe to ignore setCardType in the dependencies because it's a dispatcher
},[]);
// ... and in the child:
useEffect(() => {
getCardType(cardType);
}, [ getCardType, cardType ]);
I'm honestly dying to know what that useEffect is about in the child, because it smells a little fishy.

React functional component test file unable to update state value and call its props function

i do have a functional parent component and it do have a state value , which have default value as false and it is calling another component which is visible only when the state value changes to true and the child component do have 2 function in it.
Illustrating with code
export const MainSearch = (props) => {
const [search, setSearch] = useState(false);
const closeSearch = () => {
setSearch(false);
ANALYTICS.trackEvent('popup_collpsed');
}
const toggleSearch = async () => {
await setSearch(true);
ANALYTICS.trackEvent('popup_expanded');
}
};
return (
<React.Fragment>
<searchBar toggleSearch={toggleSearch} />
{search &&
<childSearch
toggleSearch={toggleSearch}
closeSearch={closeSearch}
/>}
</React.Fragment>
)
}
And its test file with one test case
describe('MainSearch',()=>{
it('AdvancedSearch - Toggle popup_expanded and popup_collapsed ', async () => {
const component = shallow(<MainSearch {...props} />);
const searchBar = component.find('searchBar');
await searchBar.props().toggleSearch(); // calling function
expect(ANALYTICS.trackEvent).toHaveBeenCalledWith('popup_expanded');
const childSearchComponent = component.find('childSearch'); // not working ,since state value hides
expect(childSearchComponent).toBeDefined();
await advancedSearchComponent.props().closeSearch();// here getting null for .props()
expect(ANALYTICS.page.trackEvent).toHaveBeenCalledWith('popup_collapsed');
});
});
i know its possible with component.update for CLASS COMPONENTS, but here am using functional components and am getting error
if i remove the state value search , am getting my test case PASS, but cant remove that , its needed. so my test case need to make the state value true and call the function closeSearch and then check the analytics.
BUT am getting error Method “props” is meant to be run on 1 node. 0 found instead.
I guess state value if false and its not getting that particular node .
Can you guys please help me on same , since am stuck with it and can give more info if needed
Take a look at your toggleSearch function. You're awaiting setSearch, which isn't a promise. Remove the await Keyword and you should be fine!
If you would want to trigger your Analytics call only after the State has been set, you would need to hook in to Reacts useEffect Hook.
Then you could do something like this:
useEffect(() => {
if(search){
ANALYTICS.trackEvent('popup_expanded');
}
}, [search])
https://reactjs.org/docs/hooks-effect.html

Is there a React hooks feature that has the same functionality as passing a callback as the second argument to setState?

Using hooks, I would like to execute a function only after a particular call to update state. For instance, I would like to achieve the same functionality that this does (assuming I already have already instantiated this piece of state.)
setState({name: Joe}, () => console.log('hi'));
I do not want this to log 'hi' every time that name changes, I only want to log 'hi' after this particular setState call has been executed.
This
const [name, setName] = useState('');
useEffect(() => {
console.log('hi');
}, [name]);
setName('Joe');
setName('Bob'); // only log 'hi' after this one!
setName('Joe');
setName('Bob');
will not work for my purposes because I don't want to log 'hi' every time name changes. The value in the setName call does not matter. The console.log must be executed only after this particular setName call has been executed.
Update: I was overthinking this. I was asking this question because I had a piece of state called "mode" that determined some conditional rendering through a switch statement:
switch(mode) {
case foo: return <Foo />
case bar: return <Bar />
}
I was only wanting to fire some logic when mode was a certain value (aka a certain component would be rendered). I simply moved this logic down a level into the lower-level component and used
React.useEffect(() => {
someLogic();
}, []);
in order to only fire this logic on component render.
State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().
Just spitballing here
const useStateWithCallback = val => {
const callback = useRef();
const [state,setState] = useState(val);
useEffect(() => {
callback.current && callback.current(state);
callback.current = null;
},[callback,state]);
return [
state,
useCallback((arg,cb) => {
callback.current = cb;
return setState(arg);
},[callback,setState])) // these are safe to omit, right?
]
}
EDIT: not to be too verbose, but usage:
import { useStateWithCallback } from './useStateWithCallback';
const MyCmp = () => {
const [name,setName] = useStateWithCallback('');
...
setName('joe',state => console.log(`Hi ${state}`));
...
}

Using branch from recompose

I am refactoring a stateless functional component to use branch and renderComponent from recompose.
The original component looks like this:
const Icon = props => {
const { type, name } = props
let res
if (type === 'font') {
return (<FontIcon name={name} />)
} else if (type === 'svg') {
res = (<SvgIcon glyph={name} />)
}
return res
}
The component with branch looks like this:
const isFont = props => {
const { type } = props
return type === 'font'
}
const FontIconHoC = renderComponent(FontIcon)
const SvgIconHoC = renderComponent(SvgIcon)
const Icon = branch(isFont, FontIconHoC, SvgIconHoC)
Icon.propTypes = {
type: string,
name: string
}
export default Icon
I try and render the component using:
<Icon name='crosshairs' type='font' />
The resulting error looks like this:
invariant.js:44Uncaught Error: Icon(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
branch returns a HOC, which accepts a component and return a component, so branch(...) is a HOC and branch(...)(...) is a component.
In your case, because Icon is not a component but a HOC, so React can't render it. To fix it, you can move SvgIcon out from branch's arguments and apply it to the HOC returned by branch(...), ex:
const Icon = branch(
isFont,
FontIconHoC,
a => a
)(SvgIcon)
We apply an identity function (a => a) to the third argument of branch. You can think of the identity function is also a HOC, which basically just return the component it gets and does nothing more.
Because this pattern is used very often, so the third argument of branch is default to the identity function. As a result, we can omit it and make our code simpler:
const Icon = branch(
isFont,
FontIconHoC
)(SvgIcon)
I've created a jsfiddle for these code. You can try it here.
You can also just use an if statement instead of branch. Consider that you just had some difficulties doing what an if statement does.
Maybe time to reconsider that library ?

Categories

Resources