crude-lavender
crude-lavender12mo ago

Use Base64 string and conversion for simple "upload" Field inside of existing form

Hey everyone, I'm trying to implement a simple Upload-Field inside of an existing form which is using Base64 encoding. So basically I have an API endpoint which takes a string and stores the string inside of a column in the database. Everything is working fine except for the editing part. This seems to me more like a Antd specific question but maybe somebody else has implemented something like this. Here's a working sample for "multiple image support": https://codesandbox.io/s/github/refinedev/refine/tree/next/examples/upload-antd-base64?from-embed=&file=/src/pages/users/edit.tsx Taking this example, I want to store the avatar inside the user-table as a simple base64 string. But I cannot find an option to pass an existing base64 string to the Antd Upload component. Currently I'm doing something like this:
<Form.Item label="avatar" name="avatar">
<Upload.Dragger listType="picture" beforeUpload={() => false} maxCount={1}>
<p className="ant-upload-text">Drag & drop a file in this area</p>
</Upload.Dragger>
{(formProps.initialValues as any)?.avatar != '' && (
<ImageFieldBase64 data={(formProps.initialValues as any)?.avatar} />
)}
</Form.Item>
<Form.Item label="avatar" name="avatar">
<Upload.Dragger listType="picture" beforeUpload={() => false} maxCount={1}>
<p className="ant-upload-text">Drag & drop a file in this area</p>
</Upload.Dragger>
{(formProps.initialValues as any)?.avatar != '' && (
<ImageFieldBase64 data={(formProps.initialValues as any)?.avatar} />
)}
</Form.Item>
But that seems like a hack to me 🤷 . If somebody knows a better and maybe "accepted" way of doing this, I'd be very glad 😄
CodeSandbox
upload-antd-base64 - CodeSandbox
upload-antd-base64 using @refinedev/antd, @refinedev/cli, @refinedev/core, @refinedev/react-router-v6, @refinedev/simple-rest, @types/node, @types/react, @types/react-dom, antd
4 Replies
kapa.ai
kapa.ai12mo ago
Hi @bastianwegge. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- You can use the file2Base64 function from refine to convert the file to a Base64 string and store it in the user table. Here's an example of how to implement this using Ant Design's Upload component:
import {
file2Base64,
} from "@refinedev/core";

import {
Create,
useForm,
getValueFromEvent,
} from "@refinedev/antd";
import { Form, Upload, Input } from "antd";

export const UserCreate: React.FC = () => {
const { form, formProps, saveButtonProps } = useForm<IUser>();

return (
<Create saveButtonProps={saveButtonProps}>
<Form
{...formProps}
layout="vertical"
onFinish={async (values) => {
const base64Files = [];
// @ts-ignore
const { avatar } = values;

for (const file of avatar) {
if (file.originFileObj) {
const base64String = await file2Base64(file);

base64Files.push({
...file,
base64String,
});
} else {
base64Files.push(file);
}
}
return (
formProps.onFinish &&
formProps.onFinish({
...values,
avatar: base64Files,
})
);
}}
>
<Form.Item label="Avatar">
<Form.Item
name="avatar"
valuePropName="fileList"
getValueFromEvent={getValueFromEvent}
noStyle
rules={[
{
required: true,
},
]}
>
<Upload.Dragger
listType="picture"
multiple
beforeUpload={() => false}
>
<p className="ant-upload-text">
Drag & drop a file in this area
</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Create>
);
};

interface IUser {
id: number;
}
import {
file2Base64,
} from "@refinedev/core";

import {
Create,
useForm,
getValueFromEvent,
} from "@refinedev/antd";
import { Form, Upload, Input } from "antd";

export const UserCreate: React.FC = () => {
const { form, formProps, saveButtonProps } = useForm<IUser>();

return (
<Create saveButtonProps={saveButtonProps}>
<Form
{...formProps}
layout="vertical"
onFinish={async (values) => {
const base64Files = [];
// @ts-ignore
const { avatar } = values;

for (const file of avatar) {
if (file.originFileObj) {
const base64String = await file2Base64(file);

base64Files.push({
...file,
base64String,
});
} else {
base64Files.push(file);
}
}
return (
formProps.onFinish &&
formProps.onFinish({
...values,
avatar: base64Files,
})
);
}}
>
<Form.Item label="Avatar">
<Form.Item
name="avatar"
valuePropName="fileList"
getValueFromEvent={getValueFromEvent}
noStyle
rules={[
{
required: true,
},
]}
>
<Upload.Dragger
listType="picture"
multiple
beforeUpload={() => false}
>
<p className="ant-upload-text">
Drag & drop a file in this area
</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Create>
);
};

interface IUser {
id: number;
}
This code is taken from the refine Base64 Upload documentation (https://refine.dev/docs/advanced-tutorials/upload/base64-upload). The file2Base64 function is used to convert the uploaded file to a Base64 string, which can then be stored in the user table. For displaying the existing Base64 string as an image, you can use the Image component from Ant Design:
import { Image } from "antd";

// ...

<Form.Item label="Avatar">
<Image src={`data:image/png;base64,${formProps.initialValues.avatar}`} />
{/* ... */}
</Form.Item>
import { Image } from "antd";

// ...

<Form.Item label="Avatar">
<Image src={`data:image/png;base64,${formProps.initialValues.avatar}`} />
{/* ... */}
</Form.Item>
This way, you can display the existing Base64 string as an image without using a separate ImageFieldBase64 component. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai12mo ago
<@317433576721219584> kapa.ai is still learning and improving, please let us how it did by reacting below
foreign-sapphire
foreign-sapphire12mo ago
i think it's a good approach. probably I would do the same thing 🤔
crude-lavender
crude-lavender12mo ago
Alright @alicanerdurmaz thanks for the immediate feedback! Hey @alicanerdurmaz , sorry to bug you with this one again. I've create a Base64Upload component to handle the input and output as a simple string and have kind of a translation in between. The only thing missing here is >passing the converted value back to the form<, so to say a way to bring the value back to my formProps inside of the original form. You don't happen to know how that could be done? The usage could be much cleaner this way:
const { formProps, saveButtonProps } = useForm();

return (
<Edit saveButtonProps={saveButtonProps}>
<Form
{...formProps}
layout="vertical">
<Form.Item label="avatar" name="avatar">
<Base64Upload />
</Form.Item>
</Form>
</Edit>
)
const { formProps, saveButtonProps } = useForm();

return (
<Edit saveButtonProps={saveButtonProps}>
<Form
{...formProps}
layout="vertical">
<Form.Item label="avatar" name="avatar">
<Base64Upload />
</Form.Item>
</Form>
</Edit>
)
This is missing an "onChange" event of some sorts to let my form know I updated the value.
import { UploadOutlined } from '@ant-design/icons';
import { Button, Upload, UploadFile } from 'antd';
import React, { useState } from 'react';

type Base64UploadProps = {
value?: string;
};

export const Base64Upload = ({ value }: Base64UploadProps) => {
const flagSrc = !!value && value !== '' ? `data:image/png;base64,${value}` : '';
const tmpFileList = flagSrc !== null ? [{ uid: '-1', name: 'file', url: flagSrc }] : [];
const [fileList, setFileList] = useState<UploadFile[]>(tmpFileList);

return (
<Upload
defaultFileList={fileList}
listType="picture"
beforeUpload={async (file, fileList) => {
console.log({ file, fileList });
setFileList(fileList);

// convert to base64
// if (fileList && fileList.length > 0) {
// for (const file of fileList) {
// if (file.originFileObj) {
// flagBase64 = await file2Base64(file);
// }
// }
// }

// how to
return false;
}}
maxCount={1}
>
<Button icon={<UploadOutlined />}>Click to Change image</Button>
</Upload>
);
};
import { UploadOutlined } from '@ant-design/icons';
import { Button, Upload, UploadFile } from 'antd';
import React, { useState } from 'react';

type Base64UploadProps = {
value?: string;
};

export const Base64Upload = ({ value }: Base64UploadProps) => {
const flagSrc = !!value && value !== '' ? `data:image/png;base64,${value}` : '';
const tmpFileList = flagSrc !== null ? [{ uid: '-1', name: 'file', url: flagSrc }] : [];
const [fileList, setFileList] = useState<UploadFile[]>(tmpFileList);

return (
<Upload
defaultFileList={fileList}
listType="picture"
beforeUpload={async (file, fileList) => {
console.log({ file, fileList });
setFileList(fileList);

// convert to base64
// if (fileList && fileList.length > 0) {
// for (const file of fileList) {
// if (file.originFileObj) {
// flagBase64 = await file2Base64(file);
// }
// }
// }

// how to
return false;
}}
maxCount={1}
>
<Button icon={<UploadOutlined />}>Click to Change image</Button>
</Upload>
);
};
Here's the result if anybody is interested:
import { UploadOutlined } from '@ant-design/icons';
import { Button, FormInstance, message, Upload } from 'antd';
import React, { useState } from 'react';

type Base64UploadProps = {
value?: string;
name: string;
form: FormInstance;
};

const Base64Upload = ({ value, name, form }: Base64UploadProps) => {
const [base64Data, setBase64Data] = useState<string | ArrayBuffer | null>(value ?? null);

const handleUpload = (file: File) => {
const reader = new FileReader();

reader.onloadend = () => {
// Convert file to base64 format
const base64 = reader.result;
setBase64Data(base64);
if (base64) {
const dataString = base64.toString().split(',')[1];
form.setFieldsValue({ [name]: dataString });
}
message.success('File uploaded successfully!');
};

reader.onerror = () => {
message.error('Failed to upload file.');
};

reader.readAsDataURL(file);
return false;
};

return (
<>
<Upload beforeUpload={handleUpload} showUploadList={false}>
<Button icon={<UploadOutlined />}>Upload File</Button>
</Upload>
{base64Data && <div>{base64Data.toString()}</div>}
</>
);
};

export default Base64Upload;
import { UploadOutlined } from '@ant-design/icons';
import { Button, FormInstance, message, Upload } from 'antd';
import React, { useState } from 'react';

type Base64UploadProps = {
value?: string;
name: string;
form: FormInstance;
};

const Base64Upload = ({ value, name, form }: Base64UploadProps) => {
const [base64Data, setBase64Data] = useState<string | ArrayBuffer | null>(value ?? null);

const handleUpload = (file: File) => {
const reader = new FileReader();

reader.onloadend = () => {
// Convert file to base64 format
const base64 = reader.result;
setBase64Data(base64);
if (base64) {
const dataString = base64.toString().split(',')[1];
form.setFieldsValue({ [name]: dataString });
}
message.success('File uploaded successfully!');
};

reader.onerror = () => {
message.error('Failed to upload file.');
};

reader.readAsDataURL(file);
return false;
};

return (
<>
<Upload beforeUpload={handleUpload} showUploadList={false}>
<Button icon={<UploadOutlined />}>Upload File</Button>
</Upload>
{base64Data && <div>{base64Data.toString()}</div>}
</>
);
};

export default Base64Upload;
I'm passing in form from useForm() and also the name of the property. This enables treating a Base64 image like a string column. IMPORTANT: Don't use this for large images :), we use it for images that are 1-2 kb in size, which works perfectly fine with strings.