Hey guys I am trying to pass a command in a prop to child component from a parent. The thing is that after my button is clicked a state in parent component has to be changed for intended sidebar to open. I seem to not get it right with the .then() part at the end on the onClick function, as I get error: ×
Unhandled Rejection (TypeError): Cannot read property 'then' of undefined
CHILD COMPONENT:
return (
<div
className="results-item"
onClick={async () => {
let place_detail;
try {
const URL = `https://maps.googleapis.com/maps/api/place/details/json?place_id=${places.place_id}&fields=name,rating,formatted_phone_number&key=
MYAPIKEY`;
const response = await axios.get(URL);
console.log(response.data);
place_detail = response.data.results;
} catch (error) {
console.log(error.message);
}
this.setState({ place_detail }).then(this.props.sideBarOpen);
}}
>
<ResultsItem
name={places.name}
image={Thumbnail}
rating={places.rating}
rating_total={places.user_ratings_total}
/>
</div>
);
PARENT COMPONENT:
<Search sideBarOpen={() => setOpen(!open)} />
setState doesn't return a promise. Use a callback instead:
this.setState({ place_detail }, () => {
this.props.sideBarOpen();
});
You can't use .then on setState call. You can use callback function of setState to what you are trying to do -
this.setState({ place_detail }, () => this.props.sideBarOpen());
Related
Got an array on objects from the server.
Want to display for the user a loader spinner when the request is lodaing.
const onDownland = (reportId: string) => {
setDownloadLoadingState(() => true);
backendAPIAxios.get(`/download/${reportId}`)
.then((response: AxiosResponse<IDownloadResponse>) => {
})
.catch((e: AxiosError) => {
}).finally(() => {
setDownloadLoadingState(() => false);
});
};
The problem is I get multiple objects from the server, and I got one state that changes all of the objects UI.
<Button>
{!props.downloadLoadingState ?
<MSvg
name='download'
className={classes['svgContainerBlack']}
onClick={() => props.onDownload(history.id!)}
/> :
<Tooltip title={<h1 style={{ fontSize: '17px' }}>Loading</h1>} placement="left" arrow>
<CircularProgress color="inherit" />
</Tooltip>
}
</Button>
when loading
after loading
How can I display the loader spinner for each object when I fire the request.
Added -
If you move your loadingState into the Button component, you can have independent spinners for each object.
You can set the onDownload prop to an async function (i.e. a function returning a Promise), and manage the loading state inside the button component, instead of in its parent.
Something like this might work:
// your button component
const [downloadLoadingState, setDownloadLoadingState] = useState(false);
...
<Button>
{!downloadLoadingState ? // Now looking into local state, instead of prop
<MSvg
...
onClick={() => {
setDownloadLoadingState(true)
props.onDownload(history.id!).finally(() => setDownloadLoadingState(true))
}
}
/> :
<Tooltip ...>
...
</Tooltip>
}
</Button>
// in the parent component, keep only the fetching in onDownload and remove the loading state management
// then, return the axios promise so the .finally() can be used inside the button component
const onDownload = (reportId: string) => {
return backendAPIAxios.get(`/download/${reportId}`)
.then((response: AxiosResponse<IDownloadResponse>) => {
})
.catch((e: AxiosError) => {
});
};
I have a list of urls, I want to fetch all of them and to return the images found in all these APIs so I can render it in react component using react-responsive-masonry. I have made my function in javascript but I am not sure how to write it in typescript and also I don't know how to render it in my component.
Here's my function
var photos_info = [];
async function get_photos(urls) {
var promises = urls.map((url) => fetch(url).then((y) => y.json()));
await Promise.all(promises).then((results) => {
photos_info = results;
return photos_info;
});
return photos_info;
}
I want to render it in src in my component
<ResponsiveMasonry columnsCountBreakPoints={columnsCountBreakPoints}>
<Masonry gutter={4}>
{
<img src={} />
}
</Masonry>
</ResponsiveMasonry>
Edit
Another method using useState and useEffect
const [photosList, setPhotosList] = useState<any>();
useEffect(() => {
const photosPromises = urls.map((url) =>
fetch(url).then((res) => res.json())
);
Promise.all(photosPromises).then((data) => {
setPhotosList(data);
});
}, []);
console.log("hi", photosList);
I tried to render a simple one just to see what is inside
<div>
{photosList.map((photo: any) => {
return <pre>{JSON.stringify(photo)}</pre>;
})}
</div>
but it gives me this error Cannot read property 'map' of undefined
I have situation in my unit test case for a react application, where in a function calls for another function received in props from parent component. The parent component functions definition is something like this:
onSavePropClick(action) {
const save = this.saveProperty(action);
if(action === SAVE){
return () => new Promise(() => {
resolve(this.calculate().then(save));
});
}
return save;
}
This function call has been passed as props to the child component as
<MyComponent finalSave={this.onSavePropClick(SAVE)} onClose={()=>this.setState({closeWindow: true})} />
MyComponent has a function:
savingAndShowResults() {
const { finalSave, onClose } = this.props;
finalSave().then(() => {
onClose();
});
return true;
}
Now when I have a test for the executed, it throws me error as “Cannot read property then of undefined”, the test is as follows
const initialProps={
finalSave: jest.fn(),
onClose: jest.fn()
};
it(‘should handle saving and show results’, () => {
const component = shallow(
<MyComponent {...initialProps} />
);
component.instance().savingAndShowResults();
expect(initialProps.finalSave).toHaveBeenCalled();
expect(initialProps.onClose).toHaveBeenCalled();
});
I am not able to figure out why even on resolving in return in promise of Parent component’s function, gives me this error.
Please suggest.
Assuming initialProps.finalSave is a mock function, you need to make sure you're returning a promise from initialProps.finalSave:
const initialProps = {
finalSave: jest.fn().mockImplementation(() => Promise.resolve());
...
};
I'm using React Hooks. I set the state property questions after an axios fetch call. Now when I click a button, in its function questions state is still empty
const [questions, setQuestions] = useState([]);
const [customComponent, setCustomComponent] = useState(<div />);
useEffect(() => {
axios.get("urlhere").then(res => {
console.log(12, res.data);
setQuestions(res.data);
res.data.map(q => {
if (q.qualifyingQuestionId == 1) {
setCustomComponent(renderSteps(q, q.qualifyingQuestionId));
}
});
});
}, []);
const handleNext = i => {
console.log(32, questions); //questions is still an empty array here
};
const renderSteps = (step, i) => {
switch (step.controlTypeName) {
case "textbox":
return (
<div key={i}>
<input type="text" placeholder={step.content} />
<button onClick={() => handleNext(i)}>Next</button>
</div>
);
}
};
return <>{customComponent}</>;
Do I need to use reducers here and put the custom component in another "file"?
setQuestions does not update state immediately, you should use the prevState instead to access the new value.
Here's a sandbox to match your codes with some explanation on why it was empty > https://codesandbox.io/s/axios-useeffect-kdgnw
You can also read about it here: Why calling react setState method doesn't mutate the state immediately?
Finally I have my own solution
I passed down the data from the fetch function to another component as props
useEffect(() => {
axios.get('url')
.then((data) => {
setCustomComponent(<Questions questions={data} />)
})
}, [])
I am trying to test the onClick but they are not being called using props:
here is part of the file.js
handleSystemClick = () => {
// var message = All unsaved changes will be lost.`
confirmAlert({
title: 'Confirm Navigation',
message: ' All unsaved changes will be lost.',
childrenElement: () => <div></div>,
confirmLabel: 'Confirm',
cancelLabel: 'Cancel',
onConfirm: () => {this.setState({ toSystemEntitlments: true})},
onCancel: () => {},
})
}
handleCancelClick = () => {
window.history.back();
}
here is render method of file.js
render()
return(
<div className='add-edit-button' id= 'test1' onClick={() => {this.handleSystemClick()}}>System</div>
<div className='add-edit-button' onClick={() => {this.handleCancelClick()}}>Cancel</div>
<div className='add-edit-button' onClick={() => {this.handleSave()}}>Save</div>
</div>
I have seen some examples here on stackoverflow and I tried to apply the following:
// jest mock functions (mocks this.props.func)
// defining this.props
const baseProps = {
describe(' FunctionalEntitlement Test', () => {
let wrapper;
let tree;
beforeEach(() => wrapper = shallow(<BrowserRouter><Component {...baseProps} /></BrowserRouter>));
it("should call handlesave function on button click", () => {
// Reset info from possible previous calls of these mock functions:
baseProps.handleSave.mockClear();
wrapper.setProps({
});
wrapper.setState({ getINITIAL_STATE:""
});
wrapper.find('.add-edit-button').at(0).simulate("click");
expect(baseProps.handleSave).toHaveBeenCalled();
expect(toJson(wrapper)).toMatchSnapshot();
});
Also How could I apply the same method for the first 2 clicks based on file.js
Thank you for the help
If you want to use shallow there is a way to get the shallow wrapper of one of the children using the dive method. If you have to wrap your components with BrowserRouter frequently on the test, maybe it's worth it to have a helper method for this like:
function shallowWithBrowserRouter(component) {
return shallow(<BrowserRouter>{component}</BrowserRouter>).childAt(0).dive();
}
Able to work something out but how could I improve the following answer.
Switched shallow to Mount in order to render children components
it("should call button click 3 times", () => {
// Reset info from possible previous calls of these mock functions:
wrapper.setProps({
});
wrapper.setState({
});
wrapper.find('.add-edit-button').at(0).simulate("click");
wrapper.find('.add-edit-button').at(1).simulate("click");
wrapper.find('.add-edit-button').at(2).simulate("click");
expect(toJson(wrapper)).toMatchSnapshot();
});