I am using a .zip file with thousands of geographical coordinates and converting to base64.
I can convert the file to base64, the problem is to save the result of this variable for later use.
I'm trying to use setState to save the variable's value but nothing happens.
Can you tell me what I'm doing wrong?
Here's my code I put in codesandbox
And here the zilFile I'm converting
const [zipFile, setZipFile] = useState("");
const [base64, setBase64] = useState("");
const getBase64 = (file, cb) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {
cb(reader.result);
};
reader.onerror = function (error) {};
};
const toBase64 = async () => {
let auxZip = "";
await getBase64(zipFile, (result) => {
auxZip = result.substring(28);
console.log("auxZip: ", auxZip);
setBase64(auxZip);
});
};
const onSave = () => {
toBase64();
console.log("base64: ", base64);
};
const handleZipChangle = (event) => {
const file = event.target.files[0];
setZipFile(file);
};
I have fixed like this, it worked perfectly, please take a look.
import React, { useState } from "react";
import "./styles.css";
import FormControl from "#material-ui/core/FormControl";
import Typography from "#material-ui/core/Typography";
export default function App() {
const [base64, setBase64] = useState("");
const getBase64 = (file, cb) => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = (e) => {
cb(e.target.result);
};
reader.onerror = function (error) {};
};
const onSave = () => {
console.log("base64: ", base64);
};
const handleZipChangle = (event) => {
const file = event.target.files[0];
let auxZip = "";
getBase64(file, (result) => {
auxZip = result.substring(28);
setBase64(auxZip);
});
};
return (
<div className="App">
<FormControl>
<Typography>Select Zip File:</Typography>
<input
accept="zip/*"
type="file"
id="contained-button-file"
onChange={handleZipChangle}
/>
</FormControl>
<div style={{ marginTop: "30px" }}>
<button onClick={onSave}>SAVE</button>
</div>
</div>
);
}
or if you want to use zip file, you can use useEffect to check the state loaded and call getBase64
useEffect(() => {
let auxZip = "";
zipFile &&
getBase64(zipFile, (result) => {
auxZip = result.substring(28);
setBase64(auxZip);
});
}, [zipFile]);
const onSave = () => {
console.log("base64: ", base64);
};
const handleZipChangle = (event) => {
const file = event.target.files[0];
setZipFile(file);
};
Your auxZip has been set. Cause function toBase64 is async, the auxZip is empty.
You can check by:
const toBase64 = async () => {
let auxZip = "";
getBase64(zipFile, (result) => {
auxZip = result.substring(28);
// console.log("auxZip: ", auxZip);
setBase64(auxZip);
});
};
console.log(base64);
const onSave = async () => {
await toBase64();
console.log("base64: ", base64);
};
Related
I'm fetching Dogs from my API through a JavaScript timeout. It works fine, except it fails to clear the timeout sometimes:
import { useState, useEffect, useCallback } from 'react';
const DogsPage = () => {
const [dogs, setDogs] = useRef([]);
const timeoutId = useRef();
const fetchDogs = useCallback(
async () => {
const response = await fetch('/dogs');
const { dogs } = await response.json();
setDogs(dogs);
timeoutId.current = setTimeout(fetchDogs, 1000);
},
[]
);
useEffect(
() => {
fetchDogs();
return () => clearTimeout(timeoutId.current);
},
[fetchDogs]
);
return <b>Whatever</b>;
};
It looks like the problem is that sometimes I unmount first, while the code is still awaiting for the Dogs to be fetched. Is this a common issue and if so, how would I prevent this problem?
One idea would be to use additional useRef() to keep track of whether the component has been unmounted in between fetch:
const DogsPage = () => {
const isMounted = useRef(true);
const fetchDogs = useCallback(
async () => {
// My fetching code
if (isMounted.current) {
timeoutId.current = setTimeout(fetchDogs, 1000);
}
},
[]
);
useEffect(
() => {
return () => isMounted.current = false;
},
[]
);
// The rest of the code
};
But perhaps there is a cleaner way?
You can assign a sentinel value for timeoutId.current after clearing it, then check for that value before starting a new timer:
import { useState, useEffect, useCallback } from 'react';
const DogsPage = () => {
const [dogs, setDogs] = useRef([]);
const timeoutId = useRef();
const fetchDogs = useCallback(
async () => {
const response = await fetch('/dogs');
const { dogs } = await response.json();
setDogs(dogs);
if (timeoutId.current !== -1)
timeoutId.current = setTimeout(fetchDogs, 1000);
},
[]
);
useEffect(
() => {
fetchDogs();
return () => void (clearTimeout(timeoutId.current), timeoutId.current = -1);
},
[fetchDogs]
);
return <b>Whatever</b>;
};
I Wanna Select Multiple Image from one input And Read It With File Reader , and if i leave it like this, it give me one picutre
const [image , setImage] = useState('');
const [imageSelected , setImageSelected] = useState(null);
const onImageChange = event => {
const reader = new FileReader();
reader.onload = () => {
if(reader.readyState === 2){
setImageSelected(reader.result)
console.log(reader.result)
}
}
setImage(event.target.files[0]);
reader.readAsDataURL(event.target.files[0])
console.log(image)
}
HTML
<label htmlFor="image" className = "col-span-3 ">
{
!imageSelected ? <PhotoCameraIcon fontSize="120" className ="border rounded p-2 h-24 text-8xl text-gray-600 cursor-pointer" /> : (
<img src={imageSelected} alt="" className = "h-44" />
)
}
<input hidden multiple onChange={onImageChange} type="file" name="image" id="image" />
</label>
import "./styles.css";
import React, {Component} from 'react';
export default class App extends Component {
constructor (props){
super(props);
this.state={
images: null
}
}
readFileContents = async (file) => {
return new Promise((resolve, reject) => {
let fileReader = new FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.onerror = reject;
fileReader.readAsDataURL(file);
});
}
readAllFiles = async (AllFiles) => {
const results = await Promise.all(AllFiles.map(async (file) => {
const fileContents = await this.readFileContents(file);
return fileContents;
//let a = URL.createObjectURL(fileContents);
//return a;
}));
console.log(results, 'resutls');
return results;
}
handleUpload = (e) => {
let AllFiles = [];
[...e.target.files].map(file => AllFiles.push(file));
this.readAllFiles(AllFiles).then(result => {
let allFileContents = [];
result.map(res =>{
allFileContents.push(res);
})
this.setState({images: allFileContents});
}).catch(err => {
alert(err);
});
}
render = () => {
return (<div>
<input type="file" multiple onChange={(e) => this.handleUpload(e)}/>
{this.state.images && this.state.images.map((img, idx)=>{
return <img width={100} height={50} src={img} alt={idx} key={idx} />
})
}
</div>
)
}
}
Sample react code what I am currently using...
I want to abort the request as soon as I make another request using debouncing.
Right now it cancels the very request it is making even for the first time.
import "./App.css";
import React, { useCallback, useState } from "react";
function App() {
const [mesg, setMesg] = useState(0);
const [pin, setPin] = useState("");
const abortCon = new AbortController();
const signal = abortCon.signal;
const debounce = (fn, timer) => {
let time;
return function () {
let arg = arguments;
let context = this;
if (time) clearTimeout(time);
time = setTimeout(() => {
fn.apply(context, arg);
time = null;
}, timer);
};
};
const onChangeHandler = (val) => {
const url = "https://jsonplaceholder.typicode.com/todos/1";
console.log(val);
if (abortCon) abortCon.abort();
fetch(url, { signal })
.then((result) => {
return result.json();
})
.then((res) => {
// const result = await res.json();
console.log(res.title);
setPin(val);
setMesg((prev) => prev + 1);
})
.catch((e) => console.log(e));
};
// const newHandler = debounce(onChangeHandler, 400);
const newHandler = useCallback(debounce(onChangeHandler, 200), []);
return (
<div className="App">
<p>{mesg}</p>
<input
type="text"
placeholder="PIN code"
value={pin}
onChange={(e) => {
setPin(e.target.value);
newHandler(e.target.value);
}}
/>
</div>
);
}
export default App;
I want to abort the request as soon as I make another request using debouncing.
Right now it cancels the very request it is making even for the first time.
The problem is that you are not making a new Abort Controller for each request.
So instead we save a ref to the AbortController using useRef(). Then create a new AbotrController and assign it to the ref at the start of each request.
Please see the updated code below:
import "./App.css";
import React, { useCallback, useRef, useState } from "react";
function App() {
const [mesg, setMesg] = useState(0);
const [pin, setPin] = useState("");
const abortConRef = useRef();
const debounce = (fn, timer) => {
let time;
return function () {
let arg = arguments;
let context = this;
if (time) clearTimeout(time);
time = setTimeout(() => {
fn.apply(context, arg);
time = null;
}, timer);
};
};
const onChangeHandler = (val) => {
const url = "https://jsonplaceholder.typicode.com/todos/1";
console.log(val);
if (abortConRef.current) abortConRef.current.abort();
abortConRef.current = new AbortController();
fetch(url, { signal: abortConRef.current.signal })
.then((result) => {
return result.json();
})
.then((res) => {
// const result = await res.json();
console.log(res.title);
// setPin(val);
setMesg((prev) => prev + 1);
})
.catch((e) => console.log(e));
};
// const newHandler = debounce(onChangeHandler, 400);
const newHandler = useCallback(debounce(onChangeHandler, 200), []);
return (
<div className="App">
<p>{mesg}</p>
<input
type="text"
placeholder="PIN code"
value={pin}
onChange={(e) => {
setPin(e.target.value);
newHandler(e.target.value);
}}
/>
</div>
);
}
export default App;
https://codesandbox.io/s/serene-wright-f85hy?file=/src/App.js
Edit: I also commented out the setPin in the response callback. It was making the input buggy.
In many components, I need to fetch some data and I'm ending up with a lot of similar code. It looks like this:
const [data, setData] = useState();
const [fetchingState, setFetchingState] = useState(FetchingState.Idle);
useEffect(
() => {
loadDataFromServer(props.dataId);
},
[props.dataId]
);
async function loadDataFromServer(id) {
let url = new URL(`${process.env.REACT_APP_API}/data/${id}`);
let timeout = setTimeout(() => setFetchingState(FetchingState.Loading), 1000)
try {
const result = await axios.get(url);
setData(result.data);
setFetchingState(FetchingState.Idle);
}
catch (error) {
setData();
setFetchingState(FetchingState.Error);
}
clearTimeout(timeout);
}
How can I put it into a library and reuse it?
Thank you guys for the suggestion, I came up with the following hook. Would be happy to some critics.
function useFetch(id, setData) {
const [fetchingState, setFetchingState] = useState(FetchingState.Idle);
useEffect(() => { loadDataFromServer(id); }, [id]);
async function loadDataFromServer(id) {
let url = new URL(`${process.env.REACT_APP_API}/data/${id}`);
let timeout = setTimeout(() => setFetchingState(FetchingState.Loading), 1000)
try {
const result = await axios.get(url);
setData(result.data);
setFetchingState(FetchingState.Idle);
}
catch (error) {
setData();
setFetchingState(FetchingState.Error);
}
clearTimeout(timeout);
}
return fetchingState;
}
And this is how I use it:
function Thread(props) {
const [question, setQuestion] = useState();
const fetchingState = useFetch(props.questionId, setQuestion);
if (fetchingState === FetchingState.Error) return <p>Error while getting the post.</p>;
if (fetchingState === FetchingState.Loading) return <Spinner />;
return <div>{JSON.stringify(question)}</div>;
}
You can wrap your APIs calls in /services folder and use it anywhere
/services
- Auth.js
- Products.js
- etc...
Example
Auth.js
import Axios from 'axios';
export const LoginFn = (formData) => Axios.post("/auth/login", formData);
export const SignupFn = (formData) => Axios.post("/auth/signup", formData);
export const GetProfileFn = () => Axios.get("/auth/profile")
in your component
import React, { useState } from 'react'
import { LoginFn } from '#Services/Auth'
export LoginPage = () => {
const [isLoading, setIsLoading] = useState(false);
const LoginHandler = (data) => {
setIsLoading(true)
LoginFn(data).then(({ data }) => {
// do whatever you need
setIsLoading(false)
})
}
return (
<form onSubmit={LoginHandler}>
.......
)
}
FilePond onupdatefiles method is called two times.
As seen in the photo, two of the same file is selected. How can I prevent this?
React code
<Form className='orange-color ml-2'>
<FilePond
ref={ref => (ref)}
allowFileEncode={true}
allowMultiple={false}
oninit={() =>
console.log("FilePond "+formKey.toString()+" has initialised")
}
onupdatefiles={(fileItems) => {
const file = fileItems.map(fileItem => fileItem.file)
if (file[0]) {
const reader = new FileReader();
reader.readAsDataURL(file[0]);
reader.onload = (event) => {
const convertedResult = event.target.result
if (convertedResult) {
const regex = '(.*)(base64,)(.*)';
const matches = convertedResult.match(regex);
const val = matches[3];
changeSelected(val)
}
};
}
}
}
/>