Ant design on preview is not getting fired - reactjs

I have an upload button like this. I want to show preview of uploaded Image on a div. Hence, I want the previewImage. This is how i'm trying to achieve it but onPreview is not getting fired at all.
It's a functional component. Sandbox=> https://codesandbox.io/s/silly-breeze-2gvewe
function AddAttachment(props) {
const getBase64 = (file)=>{
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
const onChange = ({ fileList: newFileList }) => {
setFileList(()=>(newFileList));
console.log(fileList);
};
const handlePreviewImage = async (file)=>{
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setBase64({
previewImage: file.url || file.preview,
previewVisible: true,
previewTitle: file.name || file.url.substring(file.url.lastIndexOf('/') + 1),
});
}
return (
<UploadButton
fileList={fileList}
onPreview ={handlePreviewImage}
onChange={onChange}
aspect={2} listType="picture" />
)
}

From the docs:
onPreview
A callback function, will be executed when file link or preview icon is clicked.
When you try clicking on the preview image or link, you should see handlePreviewImage get called.

Related

Why does React state return undefined but page still loads from state OK?

I am attempting to develop a React app which makes a call to a database to load a set of pages to a board to build a drag and drop decision tree.
I am only just starting out with React, so keen to hear about anything I'm doing wrong here.
Using 'useEffect' the pageTree function will load the pages up on the first load and on every refresh, however the pages state returns with an empty array instead of the current pages.
Strangely enough the pages all show up on the board with the pages.map function which works on the pages state... (which returns as empty on console.log...)
If I add a page to the array it saves the change to the database, but then will only show the new page on the board. You will then have to refresh to see the new set of pages (including the added page).
Calls to add or delete a page are called by the layout menu buttons in the parent component.
Console after refresh
Additionally, if I move a page, the state will console OK:
Page state in console after moving a page. DB call and state update works OK
function PageTree({AddNewPageFunc}) {
const [pages, setPages] = useState([]);
const movePage = useCallback((droppedPage) => {
const updatedPages = pages.map(page => droppedPage._id == page._id ? droppedPage : page);
setPages(updatedPages);
}, [pages]);
const [{isOver}, drop] = useDrop(() => ({
accept: ItemTypes.PAGECARD,
drop(page, monitor) {
const delta = monitor.getDifferenceFromInitialOffset();
let x = Math.round(page.x + delta.x);
let y = Math.round(page.y + delta.y);
page.x = x;
page.y = y;
movePage(page);
setNewPagePosition(page);
return undefined;
},
}), [movePage]);
const setNewPagePosition = async (pageDetails) => {
console.log("function called to update page position");
Api.withToken().post('/pageupdate/'+pageDetails._id,
pageDetails
).then(function (response) {
console.log("moved page: ",response.data)
}).catch(function (error) {
//console.log(error);
});
}
React.useEffect(() => {
AddNewPageFunc.current = AddNewPage
}, [])
const AddNewPage = useCallback(() => {
console.log("calling add new page function")
console.log("the pages before the API call are ",pages)
Api.withToken().post('/addblankpage/'
).then(function (response) {
console.log("produced: ",response.data);
setPages(pages.concat(response.data))
console.log("the pages after updating state are: ",pages)
}).catch(function (error) {
//console.log(error);
});
}, [pages]);
const handleDelete = async (id) => {
Api.withToken().post('/deletepages/'+id
).then(function (response) {
let index = pages.findIndex(function(item){
return item.id === response.data._id;
});
const PageRemoved = pages.splice(index, 1);
setPages(PageRemoved);
}).catch(function (error) {
//console.log(error);
});
}
useEffect(() => {
Api.withToken().get('/pages/')
.then(res => {
setPages(res.data);
console.log('res data ',res.data);
console.log('pages ',pages);
})
}, []);
return (
<div ref={drop} style={styles}>
{pages.map((page) => (<PageCard page={page} id={page._id} key={page._id} handleDelete={() => handleDelete(page._id)} handleMaximise={() => handleMaximise(page)} handleCopy={() => handleCopy(page)}/>))}
</div>
)
}
export default PageTree;
As Danielprabhakaran pointed out, the issue was the callback in React.useEffect. On adding a new page it needed to send the updated page state back to the parent component.
Using console.log on a state after an API call seems to be fraught, even if using .then(console.log(state)
function PageTree({AddNewPageFunc}) {
const [pages, setPages] = useState([]);
const movePage = useCallback((droppedPage) => {
const updatedPages = pages.map(page => droppedPage._id == page._id ? droppedPage : page);
console.log("updated pages ",updatedPages);
setPages(updatedPages);
console.log("set pages ",pages);
}, [pages]);
const [{isOver}, drop] = useDrop(() => ({
accept: ItemTypes.PAGECARD,
drop(page, monitor) {
const delta = monitor.getDifferenceFromInitialOffset();
let x = Math.round(page.x + delta.x);
let y = Math.round(page.y + delta.y);
page.x = x;
page.y = y;
movePage(page);
setNewPagePosition(page);
return undefined;
},
}), [movePage]);
const setNewPagePosition = async (pageDetails) => {
console.log("function called to update page position");
Api.withToken().post('/pageupdate/'+pageDetails._id,
pageDetails
).then(function (response) {
console.log("?worked ",response)
}).catch(function (error) {
//console.log(error);
});
}
React.useEffect(() => {
AddNewPageFunc.current = AddNewPage
}, [pages])
const AddNewPage = useCallback(() => {
console.log("calling add new page function")
console.log("the pages before the API call are ",pages)
Api.withToken().post('/addblankpage/'
).then(function (response) {
console.log("produced: ",response.data);
setPages(pages.concat(response.data))
console.log("the pages after updating state are: ",pages)
}).catch(function (error) {
//console.log(error);
});
}, [pages]);
const handleDeletedCallback = (deletedIndex) => {
console.log("delete callback fired")
setPages(pages.splice(deletedIndex, 1));
}
useEffect(() => {
Api.withToken().get('/pages/') //can add in a prop to return only a given tree once the app gets bigger
.then(res => {
setPages(res.data);
console.log('res data ',res.data);
console.log('pages ',pages);
})
}, []);
return (
<div ref={drop} style={styles}>
{pages.map((page, index) => (<PageCard page={page} id={page._id} key={page._id} index={index} deleteCallback={handleDeletedCallback} handleMaximise={() => handleMaximise(page)} handleCopy={() => handleCopy(page)}/>))}
</div>
)
}
export default PageTree;

How to test a component with updates background depend on state changes

I have a component that changes the background image depending on the state. I added simplified codes down below.
Since I fetch an image from the server on state changes, the background image was flashing. This is the reason I load them to DOM with preloadImage() function. This function solved the issue.
The problem starts with testing. See the testing file!
const BackgroundImage = styled`
...
background-image: ${(props) => props.bg && `url(${props.bg})`};
`
const preloadImage = (src, wrapperRef, callback) => {
const img = new Image();
img.src = src;
img.style.display = 'none';
img.dataset.testid = 'preloaded-image';
const el = wrapperRef.current;
el.innerHTML = '';
el.appendChild(img);
img.onload = () => typeof callback === 'function' && callback(src);
};
const Panel = (defaultBG) => {
const imageCacheRef = useRef();
const [bg, setBG] = useState(defaultBG);
useEffect(() => {
const fetchImage = async () => {
const imageSrc = await import(`https://fakeimageapi.com/${bg}.png`);
return preloadImage(imageSrc.default, imageCacheRef, setImage);
}
try {
await fetchImage()
} catch (error) {
console.log(error)
}
}, [])
return (
<div ref={imageCacheRef}>
<BackgroundImage bg={bg} data-testid="bg" />
<button onClick={ () => setBG('cat') }>Cat</button>
<button onClick={ () => setBG('dog') }>Cat</button>
<button onClick={ () => setBG('rabbit') }>Cat</button>
<button onClick={ () => setBG('parrot') }>Cat</button>
</div>
)
}
This is the test suite written with Testing Library.
import { render, waitFor, screen, act } from '#testing-library/react';
describe('Panel', () => {
test('Sets background-image correctly', async () => {
render(<Panel defaultBG="panda" />)
expect(screen.getByTestId('bg')).toHaveStyle(
'background-image: url(panda.png);',
);
})
})
Unfortunately, this test fails. The problem (I guess) that I use a callback after the image is loaded inside useEffect. How can I final this test with a successful result?
The problem is solved. I added a test-id to the image inside preloadImage() and loaded the image with the fireEvent method. That's it!
import { render, waitFor, screen, fireEvent } from '#testing-library/react';
describe('Panel', () => {
test('Sets background-image correctly', async () => {
render(<Panel defaultBG="panda" />)
const image = await waitFor(() => screen.getByTestId('preloaded-image'));
fireEvent.load(image);
expect(screen.getByTestId('bg')).toHaveStyle(
'background-image: url(panda.png);',
);
})
})
Also, some refactoring on preloadImage() function.
const preloadImage = (src, wrapperRef, callback) => {
const img = new Image();
img.src = src;
img.style.display = 'none';
img.dataset.testid = 'preloaded-image';
const el = wrapperRef.current;
el.innerHTML = '';
el.appendChild(img);
if (typeof callback === 'function') {
img.onload = () => callback(src);
}
};

How to get the response data from backend to React in React-dropzone-uploader

const MyUploader = () => {
const getUploadParams = ({ meta,url }) => { // specify upload params and url for your files
console.log("uploadParams",meta,url)
return { url: '/v1/file_uploads/' }
}
const handleChangeStatus = ({ meta, file }, status) => { // called every time a file's `status` changes
console.log("handleStatus",status, meta, file)
}
const handleSubmit = (files, allFiles) => { // receives array of files that are done uploading when submit button is clicked
console.log(files.map(f => f.meta))
allFiles.forEach(f => f.remove())
}
return (
<Dropzone
getUploadParams={getUploadParams}
onChangeStatus={handleChangeStatus}
onSubmit={handleSubmit}
accept="image/*"
/>
)
}
<MyUploader />
I'm able to save the uploaded file in the Database, when file is saved i am rendering some information
render json: {status: "Success", blob: blob, url: URL }
How can i console log this data which i am rendering in the React ??
The link of the package is : https://github.com/fortana-co/react-dropzone-uploader
I have solved the problem by passing xhr as a parameter to handleChangeStatus function.
const MyUploader = () => {
const getUploadParams = ({ meta }) => { // specify upload params and url for your files
return { url: '/v1/file_uploads/' }
}
const handleChangeStatus = ({ meta, file,xhr }, status) => { // called every time a file's `status` changes
console.log("handleStatus",status, meta, file)
if(status == "done") {
var json = JSON.parse(xhr.response)
var arr_blob_ids = state.documents_blob_ids.slice()
console.log("id added",json.blob.id)
if (json.blob.id){
arr_blob_ids.push(json.blob.id)
setState({...state,documents_blob_ids: arr_blob_ids})
}
}
else if(status == "removed") {
var json = JSON.parse(xhr.response)
var arr_blob_ids = state.documents_blob_ids.slice()
console.log("id removed",json.blob.id)
if (json.blob.id){
arr_blob_ids = arr_blob_ids.filter( v => v!= json.blob.id)
setState({...state,documents_blob_ids: arr_blob_ids})
}
}
}
const handleSubmit = (files, allFiles) => { // receives array of files that are done uploading when submit button is clicked
console.log(files.map(f => f.meta))
allFiles.forEach(f => f.remove())
}
return (
<Dropzone
getUploadParams={getUploadParams}
onChangeStatus={handleChangeStatus}
onSubmit={handleSubmit}
accept="image/*"
submitButtonContent = {null}
SubmitButtonComponent = {null}
/>
)
}

React Testing Library - Simulating uploading file with FileReader

I want to test a React component where certificate file is uploaded and the result of file can be shown in a text area.
I am still learning writing unit test and here facing difficulty to mock FileReader, readAsText and onloadend.
I have read few examples but they do not work as expected. Any guidance would be helpful
Thanks
Cert Component
export default function Cert() {
const fileInputRef = useRef();
function handleEvent(obj, event) {
var event = new Event(event, { target: obj, bubbles: true });
return obj ? obj.dispatchEvent(event) : false;
}
let handleChangeFile = (file) => {
let fileData = new FileReader();
fileData.readAsText(file);
fileData.onloadend = () => {
let el = document.getElementById('cert');
el.value = event.target.result;
handleEvent(el, 'input');
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
fileData.onerror = function () {
console.log(fileData.error);
};
};
return (
<CheckboxWithOptions name="int" label='app'>
<Field
component={ExpandingTextareaField}
name="certificate"
label='Upload Certificate *'
isRequired
validate={required}
id="cert"
/>
<div>
<Button
label='Upload file'
type="button"
appearance="brand"
size="large"
onClick={() => {
fileInputRef.current.click();
}}
data-typeId="test"
/>
<input
style={{
opacity: 0,
position: 'fixed',
top: 0,
left: 0,
width: 0,
}}
type="file"
accept=".cert"
ref={fileInputRef}
onChange={(e) => handleChangeFile(e.target.files[0])}
data-testid="testinput"
/>
</div>
}
Cert.test.js
it('upload file to show in textarea ', async () => {
const file = new File(['dummy'], 'test.cert', { type: 'cert' })
render(
<div>
<label htmlFor="file-uploader">Upload file:</label>
<input id="file-uploader" type="file" onChange={(e) => handleChangeFile('e')} />
</div>,
)
const { getByLabelText } = render(<TestComponent />);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
expect(getByLabelText("CheckBox").value).toBe("false");
fireEvent.click(screen.getByLabelText("CheckBox"))
const input = screen.getByLabelText(/Upload file/i)
fireEvent.click(input)
userEvent.upload(input, file)
await waitFor(() => expect(handleChangeFile).toBeCalledTimes(1)); // working fine till here
jest.spyOn(global, "FileReader")
.mockImplementation(function () {
readAsText = jest.fn();
});
fireEvent.change(input, {
target: {
files: [file]
}
});
expect(FileReader).toHaveBeenCalled(); // not working
// test the FileReader and textarea
})
Had the same issue with checking some side effect that should be invoked on FileReader.onload, so I just ended up setting a short pause after triggering the event (I'm using enzyme):
const pauseFor = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
...
wrapper.find('.upload-box').simulate('drop', someMockedDropEvent);
// set pause was the only way to make reader.onload to fire
await pauseFor(100);
expect(something).toEqual(something)

Removing FilePond file after upload React

Ok I am at my wits end here. I am using FilePond with React in a functional component using Hooks. I am not using FilePond to handle the actual upload, just setting the state with the files, my simplified version is this:
The Filepond:
<FilePond
onupdatefiles={fileItems => handleFilepondUpdate(fileItems)}
/>
</Form.Field>
The handle update:
const handleFilepondUpdate = fileItems => {
if (fileItems.length === 0) {
addAttachment({
...picture,
bugAttachment: null
});
} else {
addAttachment({
...picture,
bugAttachment: fileItems[0].file
});
}
};
The state:
const [picture, addAttachment] = useState({
bugAttachment: ""
});
const { bugAttachment } = picture;
And finally my upload and clear the input state:
const onSubmit = e => {
e.preventDefault();
const fd = new FormData();
fd.append("email", props.user[0].email);
fd.append("bugDescription", bugDescription);
fd.append("bugAttachment", bugAttachment);
addBug(fd).then(() => {
setBug({
bugDescription: ""
});
});
};
So how would I go about removing the FilePond file after the form is sent through?
Try clearing the bugAttachment property inside onSubmit using addAttachment hook
const onSubmit = e => {
e.preventDefault();
const fd = new FormData();
fd.append("email", props.user[0].email);
fd.append("bugDescription", bugDescription);
fd.append("bugAttachment", bugAttachment);
addBug(fd).then(() => {
setBug({
bugDescription: ""
});
addAttachment({
...picture,
bugAttachment:""
});
});
};
Update:
It seems like that you have not used the files prop with your picture state,Try something like this.
<FilePond
files={bugAttachment}
onupdatefiles={fileItems => handleFilepondUpdate(fileItems)}
/>

Resources