I'm using getDownloadURL from fire storage to get a url for any image that is uploaded on to the server. But when I try to display the images in my img element source, it returns a blank icon instead of the actual image. The image even on firestorage has trouble rendering and when its opened, its just a white square instead of the image. The function is the sendImage function in the code. Ive also added CORS configuration to my google cloud shell and changed the meta data multiple times, and yes the images do have tokens and the rules are fine. Any help .
import React, { useContext, useEffect, useRef, useState } from 'react'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faPaperclip, faImage, faPaperPlane } from '#fortawesome/free-solid-svg-icons'
import { doc, db } from '../firebase'
import { arrayUnion, onSnapshot, setDoc, updateDoc } from 'firebase/firestore'
import ChatContext from '../Context/ChatContext'
import { AuthContext } from '../Context/AuthContext'
import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
const Chat = () => {
const {user} = useContext(AuthContext)
const [messages, setMessages] = useState([])
const {data} = useContext(ChatContext)
const [file, setFile] = useState()
const [msg, setMsg] = useState()
const aref = useRef(null)
const scrollRef = useRef(null)
const photo = '013bfa05b2955d0bff2823c2f3304e1838ac30801c.jpg'
const storage = getStorage()
const storageRef = ref(storage, `images/${photo}`)
const metadata = {
contentDisposition: 'inline',
contentType: 'image/jpeg'
};
useEffect(() => {
const unSub = onSnapshot(doc(db, "messages", data.chatId), (doc) => {
doc.exists() && setMessages(doc.data().messages);
});
return () => {
unSub();
};
}, [data.chatId]);
useEffect(()=>{
scrollToBottom()
}, [messages])
const sendImage = async() =>{
const uploadTask = uploadBytesResumable(storageRef, file, metadata);
await uploadTask.on(
(error) => {
console.log(error)
},
async() => {
// Handle successful uploads on complete
// For instance, get the download URL: https://firebasestorage.googleapis.com/...
await getDownloadURL(uploadTask.snapshot.ref).then(async(downloadURL) => {
await updateDoc(doc(db, 'messages', data.chatId), {
messages: arrayUnion({photoURL: downloadURL})
})
console.log(downloadURL)
});
}
);
}
const handleChange = (e) =>{
setMsg({msg: e.target.value, origin: user.uid})
}
const sendMessage = async() =>{
if(data.chatId!='global'){
if(msg){
await updateDoc(doc(db, 'messages', data.chatId), {
messages: arrayUnion(msg)
})
}
}
if(file){
sendImage()
}
aref.current.value = ''
}
const handleFileSelect = (e) =>{
e.preventDefault()
setFile({photoURL: e.target.files[0], origin: user.uid})
}
const scrollToBottom = () =>{
scrollRef.current.scrollIntoView({behavior: 'smooth'})
}
const handleKey = (e) => {
e.code === "Enter" && sendMessage();
};
return (
<div className='chatWrapper'>
<button onClick={()=>sendImage()}>here</button>
<div className="cWrapper">
<div className="conversationProfile">
<img className='conversationImage' src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/1f/Dwayne_Johnson_2014_%28cropped%29.jpg/640px-Dwayne_Johnson_2014_%28cropped%29.jpg" alt=""/>
<span>DM Name</span>
</div>
<div className="messages">
{messages ? messages?.map((message) =>(
<div className={`message ${message.origin==user.uid && 'sender'}`}>{message.msg ? message.msg : <img className='imgMSG' referrerPolicy='no-referrer' src='https://firebasestorage.googleapis.com/v0/b/mwdims.appspot.com/o/images%2F013bfa05b2955d0bff2823c2f3304e1838ac30801c.jpg?alt=media&token=a95f8bd2-97fe-4bfa-a7d5-36772407c418"
'></img>}</div>
)) : <div className='placeHolder'>Say Something </div>}
<div ref={scrollRef} style={{visibility: 'hidden'}}></div>
</div>
<div className="chatBar">
<input ref={aref} type="text" placeholder='Type Something...' defaultValue='' onChange={handleChange} onKeyDown={handleKey}/>
<div className="chatFunctions">
<FontAwesomeIcon icon={faPaperclip} />
<input type='file' onChange={handleFileSelect}></input>
<FontAwesomeIcon icon={faImage}></FontAwesomeIcon>
<FontAwesomeIcon icon={faPaperPlane} onClick={()=>sendMessage()}/>
</div>
</div>
</div>
</div>
)
}
export default Chat
Related
What check did I miss that my edit page only works if it has a file selected?
I tried to make some conditionals in the onSubmit function but I haven't found the way yet.
I know it has to do with the image array because when I click edit without selecting a file I get this error:
TypeError: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
<script setup>
import { getAuth, onAuthStateChanged } from 'firebase/auth'
import {
getStorage,
uploadBytesResumable,
ref as firebaseRef,
getDownloadURL
} from 'firebase/storage'
import { doc, updateDoc, getDoc, serverTimestamp } from 'firebase/firestore'
import { db } from '../../firebase.config.js'
import { v4 as uuidv4 } from 'uuid'
const themeEditListing = ref([])
const formData = ref({
name: '',
})
const route = useRoute()
// get the theme id from the route
const themeId = route.params.id
const auth = getAuth()
onAuthStateChanged(auth, (user) => {
if (user) {
// set user on formData
formData.value.user = user.uid
} else {
navigateTo('/')
}
})
const fetchEditListing = async () => {
const docRef = doc(db, 'themes', themeId)
// response
const docSnap = await getDoc(docRef)
if (docSnap.exists) {
themeEditListing.value = docSnap.data()
formData.value = themeEditListing.value
}
}
fetchEditListing()
const onSubmit = async () => {
const newImgUrls = await Promise.all(
Array.from(formData.value.images).map((image) => {
return storeImage(image)
})
).catch((err) => {
console.log(err)
})
const imgUrls = newImgUrls ? formData.value.imgUrls.concat(newImgUrls) : formData.value.imgUrls
const formDataCopy = { ...formData.value, imgUrls, timestamp: serverTimestamp() }
delete formDataCopy.images
// update listing
// get doc reference
const docRef = doc(db, 'themes', themeId)
// update doc
await updateDoc(docRef, formDataCopy)
formData.value = { ...formData.value, imgUrls }
}
const onFileChange = (e) => {
const file = e.target.files
formData.value.images = file
}
// store images in firebase storage
const storeImage = async (image) => {
return new Promise((resolve, reject) => {
formData.value.imgUrls.push('')
newImgProgress.value = 1
const storage = getStorage()
const fileName = `${auth.currentUser.uid}-${image.name}-${uuidv4()}`
const storageRef = firebaseRef(storage, 'images/' + fileName)
const uploadTask = uploadBytesResumable(storageRef, image)
uploadTask.on(
'state_changed',
(snapshot) => {
newImgProgress.value = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
console.log('Upload is ' + newImgProgress.value + '% done')
switch (snapshot.state) {
case 'paused':
console.log('O Upload está parado')
break
case 'running':
console.log('Subindo a Imagem')
break
default:
break
}
},
(error) => {
reject(error)
},
() => {
// Handle successful uploads on complete
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
// pop the last element
formData.value.imgUrls.pop()
newImgProgress.value = 0
resolve(downloadURL)
})
}
)
})
}
</script>
<template>
<div>
<form #submit.prevent="onSubmit" class="w-full border shadow-md p-4 rounded-md">
<div>
<input
type="text"
#value="formData.name"
v-model="formData.name"
placeholder="Nome do Tema"
class="input w-full border-x-indigo-300"
/>
</div>
<div>
<input
type="file"
id="images"
#value="formData.images"
#change="onFileChange"
accept=".jpg, .png, .jpeg, .webp"
multiple
class="hidden"
/>
</label>
</div>
<button type="submit"">Edit Theme</button>
</form>
</div>
</template>
I edited the code as much as possible to make it short to read.
Whenever i try to upload an image, i get my own created error that it's not the right file type. I tried adding different file types and adding more acceptable file types.
import React, { Fragment, useState } from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faPlus } from "#fortawesome/free-solid-svg-icons";
import "./FileUpload.css";
import ProgressBar from "./ProgressBar";
const FileUpload = () => {
const [file, setFile] = useState(null);
const [error, setError] = useState(null);
const types = ["image/png, image/gif, image/jpeg"];
const uploadHandler = (e) => {
const selected = e.target.files[0];
console.log(selected);
if (selected && types.includes(selected.type)) {
setFile(selected);
setError(" ");
} else {
setFile(null);
setError("Please pick an image file that is a png or jpg");
}
};
return (
<Fragment>
<div className="file-card">
<div className="file-inputs">
<input type="file" className="input-files" onChange={uploadHandler} />
<button className="button-files">
<i className="button-i">
<FontAwesomeIcon icon={faPlus} />
</i>
</button>
</div>
<p className="main">Supported Files</p>
<p className="info">PDF, JPG,PNG</p>
</div>
<div className="output">
{error && <div className="error">{error}</div>}
{file && <div>{file.name}</div>}
{file && <ProgressBar file={file} setFile={setFile} />}
</div>
</Fragment>
);
};
export default FileUpload;
This is what shows when i try to upload a file. It's the right file type. it just doesn't upload. I tried adding different acceptable ones and still get the same issue.
I created this hook for storage, in order to decrease the code i have in my file uploader. The hook should be in charge of adding the files to firebase.
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import { projectStorage } from "../../firebase";
import { useEffect, useState } from "react";
const useStorage = (file) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
const [url, setUrl] = useState(null);
useEffect(() => {
const storageRef = ref(projectStorage, "images/" + file.name);
console.log(storageRef);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snapshot) => {
let percentage =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
setProgress(percentage);
},
(err) => {
setError(err);
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => setUrl(url));
}
);
}, [file]);
return { progress, url, error };
};
export default useStorage;
I get this error when I try and call a function I have imported within my useEffect() hook in Dashboard.jsx. I am just trying to pull in data from database on the page load pretty much so that when user click button they can send off correct credentials to the api.
I am pulling it in from database for security reasons, so client id is not baked into the code.
I am pretty sure that I am getting this error maybe because the function is not inside a react component? although I am not 100% sure. And if that is the case I am not sure of the best way to restructure my code and get the desired output.
Code below.
mavenlinkCredentials.js
import { doc, getDoc } from "firebase/firestore";
import { useContext } from "react";
import { AppContext } from "../../context/context";
import { db } from "../../firebase";
const GetMavenlinkClientId = async () => {
const {setMavenlinkClientId} = useContext(AppContext)
const mavenlinkRef = doc(db, 'mavenlink', 'application_id');
const mavenlinkDocSnap = await getDoc(mavenlinkRef)
if(mavenlinkDocSnap.exists()){
console.log("mavenlink id: ", mavenlinkDocSnap.data());
console.log(mavenlinkDocSnap.data()['mavenlinkAccessToken'])
setMavenlinkClientId(mavenlinkDocSnap.data()['application_id'])
} else {
console.log("No doc");
}
}
export default GetMavenlinkClientId;
Dashboard.jsx
import React, { useContext, useEffect, useState } from "react";
import { useAuthState } from "react-firebase-hooks/auth";
import { useNavigate } from "react-router-dom";
import { query, collection, getDocs, where, setDoc, doc, getDoc } from "firebase/firestore";
import { auth, db, logout } from "../firebase";
import { Button, Container, Grid, Paper } from "#mui/material";
import ListDividers from "../components/ListDividers";
import { AppContext } from "../context/context";
import axios from "axios";
import {SuccessSnackbar, ErrorSnackbar} from '../components/PopupSnackbar';
import GetMavenlinkClientId from "../helpers/firebase/mavenlinkCredentials";
const Dashboard = () => {
const [user, loading, error] = useAuthState(auth);
const [name, setName] = useState("");
const [ accessToken, setAccessToken ] = useState("")
const [errorAlert, setErrorAlert] = useState(false);
const [successAlert, setSuccessAlert] = useState(false);
const [mavenlinkClientId, setMavenlinkClientId] = useState("");
const {isAuthenticated} = useContext(AppContext);
const navigate = useNavigate();
const uid = user.uid
const parsedUrl = new URL(window.location.href)
const userTokenCode = parsedUrl.searchParams.get("code");
const { mavenlinkConnected, setMavenlinkConnected } = useContext(AppContext)
const { maconomyConnected, setMaconomyConnected } = useContext(AppContext)
const { bambooConnected, setBambooConnected } = useContext(AppContext)
const fetchUserName = async () => {
try {
const q = query(collection(db, "users"), where("uid", "==", user?.uid));
const doc = await getDocs(q);
const data = doc.docs[0].data();
setName(data.name);
} catch (err) {
console.error(err);
alert("An error occured while fetching user data");
}
};
//
useEffect(() => {
if (loading) return;
if (!user) return navigate("/");
fetchUserName();
if(userTokenCode !== null){
authorizeMavenlink();
}
if(isAuthenticated){
GetMavenlinkClientId()
}
}, [user, loading]);
///put this into a page load (use effect maybe) so user does not need to press button to connect to apis
const authorizeMavenlink = () => {
console.log(uid);
const userRef = doc(db, 'users', uid);
axios({
//swap out localhost and store in variable like apitool
method: 'post',
url: 'http://localhost:5000/oauth/mavenlink?code='+userTokenCode,
data: {}
})
.then((response) => {
setAccessToken(response.data);
setDoc(userRef, { mavenlinkAccessToken: response.data}, { merge: true });
setMavenlinkConnected(true);
setSuccessAlert(true);
})
.catch((error) => {
console.log(error);
setErrorAlert(true)
});
}
//abstract out client id and pull in from db
const getMavenlinkAuthorization = () => {
window.open('https://app.mavenlink.com/oauth/authorize?client_id='+mavenlinkClientId+'&response_type=code&redirect_uri=http://localhost:3000');
window.close();
}
const authorizeBamboo = () => {
axios({
method: 'get',
url: 'http://localhost:5000/oauth/bamboo',
data: {}
})
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error);
});
// console.log('bamboo connected')
setBambooConnected(true);
}
const authorizeMaconomy = () => {
console.log("Maconomy connected")
setMaconomyConnected(true);
}
const syncAccount = async() => {
if(!mavenlinkConnected){
await getMavenlinkAuthorization()
}
if (!bambooConnected){
await authorizeBamboo();
}
if (!maconomyConnected){
await authorizeMaconomy();
}
}
const handleAlertClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSuccessAlert(false) && setErrorAlert(false);
};
console.log(mavenlinkClientId);
return(
<>
<Container>
<div className="dashboard">
<h1>Dashboard</h1>
<Grid container spacing={2}>
<Grid item xs={12}>
<Paper style={{paddingLeft: "120px", paddingRight: "120px"}} elevation={1}>
<div className="dashboard-welcome">
<h2>Welcome {name}</h2>
<h4>{user?.email}</h4>
<hr/>
<h2>Integrations</h2>
<Button onClick={syncAccount}>
Sync Account
</Button>
{/* <Button onClick={getMavenlinkClientId}>
Bamboo Test
</Button> */}
<ListDividers/>
</div>
</Paper>
</Grid>
</Grid>
</div>
{successAlert === true ? <SuccessSnackbar open={successAlert} handleClose={handleAlertClose}/> : <></> }
{errorAlert === true ? <ErrorSnackbar open={errorAlert} handleClose={handleAlertClose}/> : <></> }
</Container>
</>
);
}
export default Dashboard;
the error is because you’re calling const {setMavenlinkClientId} = useContext(AppContext) inside the file mavenlinkCredentials.js which is not a react components.
you could maybe change the function inside mavenlinkCredentials.js to accept a setMavenlinkClientId and pass it from outside like this.
const GetMavenlinkClientId = async (setMavenlinkClientId) => {
const mavenlinkRef = doc(db, 'mavenlink', 'application_id');
const mavenlinkDocSnap = await getDoc(mavenlinkRef)
if(mavenlinkDocSnap.exists()){
console.log("mavenlink id: ", mavenlinkDocSnap.data());
console.log(mavenlinkDocSnap.data()['mavenlinkAccessToken'])
setMavenlinkClientId(mavenlinkDocSnap.data()['application_id'])
} else {
console.log("No doc");
}
}
and then you can call this function in your dashboard.js like so,
const {setMavenlinkClientId} = useContext(AppContext)
if(isAuthenticated){
GetMavenlinkClientId(setMavenlinkClientId)
}
I created a custom useAxios hook to make api calls.
I am trying to use this in my login component as below.
import { useContext, useState } from 'react';
import './login.scss';
import { useNavigate } from 'react-router-dom';
import { useAxios } from '../../api/use-axios';
import ApiConfig from '../../api/api-config';
import { AuthContext } from '../../context/AuthContext';
const LOGIN_AXIOS_CONFIG = ApiConfig.AUTH.LOGIN;
const Login = () => {
const [loginError, setLoginError] = useState('');
const [phone, setPhone] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate();
const { dispatch } = useContext(AuthContext);
const handleLogin = (e) => {
e.preventDefault();
LOGIN_AXIOS_CONFIG.data = {
phone,
password,
};
const { response: loginData, error } = useAxios(LOGIN_AXIOS_CONFIG);
if (error) {
setLoginError(error);
}
if (loginData) {
dispatch({ type: 'LOGIN', payload: loginData });
navigate('/');
}
};
return (
<div className="login">
<div className="logo">
<h1>LOGO</h1>
<h3>LOGO DESCRIPTION</h3>
</div>
<form onSubmit={handleLogin}>
<input type="number" placeholder="phone" onChange={(e) => setPhone(e.target.value)} />
<input type="password" placeholder="password" onChange={(e) => setPassword(e.target.value)} />
<button type="submit">Submit</button>
{loginError && <span>{loginError}</span>}
</form>
</div>
);
};
export default Login;
use-axios.js
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useAxios = (axiosParams) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const fetchData = async (params) => {
try {
const result = await axios.request({
...params,
method: params.method || 'GET',
headers: {
accept: 'application/json',
authorization:
'Bearer my-token',
},
});
console.log(result.data);
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData(axiosParams);
}, [axiosParams]); // execute once only
return { response, error, loading };
};
Is react seeing use prefix and giving error?
How can I fix this?
You need to make a slight change to your custom hook useAxios since hooks can be invoked only in the body of the component and not conditionally. I add a param to that hook to handle the auto-fetch. In this case you don't want it to trigger automatically the fetch, but imperatively, so just return the fetcher function from the hook and use it imperatively. It would be better not to couple the hook btw and create two separate hooks, something like useAxiosOnMount, useAxiosOnAction. For simplicity I'll just edit your useAxios hook here:
const Login = () => {
const [loginError, setLoginError] = useState('');
const [phone, setPhone] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate();
const { dispatch } = useContext(AuthContext);
const {fetch, loading, response, error} = useAxios(LOGIN_AXIOS_CONFIG, false)
useEffect(() => {
if (response) // do something
if (error) // do something else
},[response,error])
const handleLogin = (e) => {
e.preventDefault();
fetch()
};
return (
<div className="login">
<div className="logo">
<h1>LOGO</h1>
<h3>LOGO DESCRIPTION</h3>
</div>
<form onSubmit={handleLogin}>
<input type="number" placeholder="phone" onChange={(e) => setPhone(e.target.value)} />
<input type="password" placeholder="password" onChange={(e) => setPassword(e.target.value)} />
<button type="submit">Submit</button>
{loginError && <span>{loginError}</span>}
</form>
</div>
);
};
export default Login;
use-axios.js
export const useAxios = (axiosParams, isAuto) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const fetchData = async (params) => {
try {
const result = await axios.request({
...params,
method: params.method || 'GET',
headers: {
accept: 'application/json',
authorization:
'Bearer my-token',
},
});
console.log(result.data);
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (isAuto)
fetchData(axiosParams);
}, [axiosParams, isAuto]); // execute once only
return { fetch: () => fetchData(axiosParams), response, error, loading };
};
useAxios, as you designed it, is a hook. And a hook, should follow some rules, known as Rules of Hooks, and here is an overview:
It should only get called a the to level of a React component or at the top level of a hook, not in a function inside a hook or a component like you did, not inside any other block, like if-else...
An approche for your case may be something like this:
import { useContext, useState, useEffect } from 'react';
import './login.scss';
import { useNavigate } from 'react-router-dom';
import { useAxios } from '../../api/use-axios';
import ApiConfig from '../../api/api-config';
import { AuthContext } from '../../context/AuthContext';
const LOGIN_AXIOS_CONFIG = ApiConfig.AUTH.LOGIN;
const Login = () => {
const [loginAxioConfig, setLogicAxiosConfig]=useState(null);
const [loginError, setLoginError] = useState('');
const [phone, setPhone] = useState('');
const [password, setPassword] = useState('');
const { response: loginData, error } = useAxios(loginAxioConfig);
const navigate = useNavigate();
const { dispatch } = useContext(AuthContext);
const handleLogin = (e) => {
e.preventDefault();
setLogicAxiosConfig({...LOGIN_AXIOS_CONFIG, data = {
phone,
password,
} });
};
useEffect(()=>{
if (error) {
setLoginError(error);
}
},[error]);
useEffect(()=>{
if (loginData) {
dispatch({ type: 'LOGIN', payload: loginData });
navigate('/');
}
},[loginData])
return (
<div className="login">
<div className="logo">
<h1>LOGO</h1>
<h3>LOGO DESCRIPTION</h3>
</div>
<form onSubmit={handleLogin}>
<input type="number" placeholder="phone" onChange={(e) => setPhone(e.target.value)} />
<input type="password" placeholder="password" onChange={(e) => setPassword(e.target.value)} />
<button type="submit">Submit</button>
{loginError && <span>{loginError}</span>}
</form>
</div>
);
};
export default Login;
import { useState, useEffect } from 'react';
import axios from 'axios';
export const useAxios = (axiosParams) => {
const [response, setResponse] = useState(undefined);
const [error, setError] = useState('');
const [loading, setLoading] = useState(true);
const fetchData = async (params) => {
try {
const result = await axios.request({
...params,
method: params.method || 'GET',
headers: {
accept: 'application/json',
authorization:
'Bearer my-token',
},
});
console.log(result.data);
setResponse(result.data);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if(!axiosParams) return;
fetchData(axiosParams);
}, [axiosParams]);
return { response, error, loading };
};
Currently I am rendering a list of songs where there is a toggle button I made to render a form to add a song. How can I make it so when that form is submitted it will hide the form without a button click. I attempted to make a useEffect to trigger the function but I couldn't crack it. Thanks in advance.
The list of songs
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { deleteSong, getSongs, updateSong } from '../../store/song';
import ReactAudioPlayer from 'react-audio-player';
import { useHistory } from 'react-router';
import SongForm from '../AddSongForm';
import EditSongForm from '../EditSongForm';
import SpecificSong from '../SpecificSong';
const SongList = () => {
const [addShowForm, setAddShowForm] = useState(false);
// const [editShowForm, setEditShowForm] = useState(false);
const history = useHistory()
const dispatch = useDispatch();
const songsObj = useSelector((state) => state.songState.entries);
const songs = Object.values(songsObj)
const user = useSelector((state) => state.session.user);
const CurrentUserId = user?.id
const remove = (e) => {
dispatch(deleteSong(e.target.id));
}
const addFormCheck = (e) => {
if (addShowForm) setAddShowForm(false)
if (!addShowForm) setAddShowForm(true)
}
// const editFormCheck = (e) => {
// if (editShowForm) setEditShowForm(false)
// if (!editShowForm) setEditShowForm(true)
// }
useEffect(() => {
dispatch(getSongs());
}, [dispatch]);
return (
<div>
<div>
<button onClick={addFormCheck}>add a song</button>
{addShowForm ?
<SongForm />
: null}
</div>
<h1>Song List</h1>
<ol>
{songs.map(({ id, songName, songLink, userId }) => (
<div>
<SpecificSong id={id} songName={songName} songLink={songLink} userId={userId} />
</div>
))}
</ol>
</div>
);
};
export default SongList;
And the component that is being rendered
import { useState } from "react";
import { useDispatch } from "react-redux";
import { postSong } from "../../store/song";
import { useSelector } from "react-redux";
import Axios from 'axios'
const SongForm = () => {
const dispatch = useDispatch();
const [songName, setSongName] = useState("");
const [songLink, setSongLink] = useState("");
const [errors, setErrors] = useState([]);
const [songSelected, setSongSelected] = useState("")
const reset = () => {
setSongName("");
setSongLink("");
};
const user = useSelector((state) => state.session.user);
const userId = user?.id
let url;
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData()
formData.append('file', songSelected)
formData.append('upload_preset', 'd3gthd7l')
if (songSelected === '') {
setErrors(['You have to upload an audio file!'])
}
Axios.post("https://api.cloudinary.com/v1_1/dyhfkvy6u/video/upload", formData).then(async (response) => {
if (response.data.url) url = response.data.url
const newSong = {
songName,
songLink: url,
userId
};
const song = await dispatch(postSong(newSong))
.catch(async (res) => {
const data = await res.json()
if (data && data.errors) setErrors(data.errors)
})
})
// reset();
};
return (
<div className="inputBox">
<h1>Add A Song</h1>
<ul>
{errors.map((error, idx) => <li className='errors' key={idx}>{error}</li>)}
</ul>
<form onSubmit={handleSubmit}>
<input
type="text"
onChange={(e) => setSongName(e.target.value)}
value={songName}
placeholder="Song Name"
name="Song Name"
/>
<input
type='file'
onChange={(e) => { setSongSelected(e.target.files[0]) }}
placeholder="Song Link"
name="Audio File"
/>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default SongForm;
You could pass the setAddShowForm function to the form as a prop and update its state once submitted (Note that you can use && for conditional rendering):
<div>
<button onClick={addFormCheck}>add a song</button>
{addShowForm && <SongForm setAddShowForm={setAddShowForm}/>}
</div>
Change your component declaration to
const SongForm = ({ setAddShowForm }) => {
And update the state in the handleSubmit method:
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('file', songSelected);
formData.append('upload_preset', 'd3gthd7l');
if (songSelected === '') {
setErrors(['You have to upload an audio file!']);
}
Axios.post(
'https://api.cloudinary.com/v1_1/dyhfkvy6u/video/upload',
formData
).then(async (response) => {
if (response.data.url) url = response.data.url;
const newSong = {
songName,
songLink: url,
userId,
};
const song = await dispatch(postSong(newSong)).catch(async (res) => {
const data = await res.json();
if (data && data.errors) setErrors(data.errors);
// Hide the form
setAddShowForm(false);
});
});
};
You cant trigger useEffect with a dispatch variable change. Dispatch is a hook and dont change once invoked. You need to create a state variable useState and change its value on handleChange, when you do that, include that variable on useEffect instead of dispatch and that will trigger the useEffect content.