SetState not rendering the component - javascript

So I am working on my React app and a one point i need to upload some files. So I simply used the input file to make it work. I set its display none as I wanted my attachment icon to be clicked when wanting to upload the file.
Problem : Using the ref method, everything is working fine except one thing and that is below in my hangleFileChange function, when the setFiles() set the file variable, the component is not rendered and I do not see the file array. but if I do the file saving simply like
setFile(event.target.files[0])
I can see the rendering . But with the below code, the component is not rendering
import React, { useRef, useState } from "react";
const App = () => {
const fileInput = useRef(null);
const [file, setFile] = useState([]);
const handleClick = () => {
fileInput.current.click();
};
const handleFileChange = (event) => {
console.log("Make something");
let newFiles = file;
newFiles.push(event.target.files[0]);
console.log(newFiles);
setFile(newFiles);
};
// This should run on every render
console.log("the files array is ", file);
return (
<div className="patientactions-container">
<input
type="file"
style={{ display: "none" }}
onChange={(e) => handleFileChange(e)}
ref={fileInput}
/>
<div onClick={() => handleClick()}>clck</div>
</div>
);
};
export default App;
Please help.
Sandbox : https://codesandbox.io/s/kind-breeze-czc3w?file=/src/App.js:0-692

Try this version
const handleFileChange = (event) => {
console.log("Make something");
// Set the ne variable to an array, not file
let ne = [];
ne.push(event.target.files[0]);
// then set it equals file.
ne = file;
console.log(ne);
console.log(file);
setFile(file);
};

You can fix the code like this below.
import React, { useRef, useState } from "react";
const App = () => {
const fileInput = useRef(null);
const [file, setFile] = useState(null);
const handleClick = () => {
fileInput.current.click();
};
const handleFileChange = (nfile) => {
console.log("Make something");
if (file == null) setFile([nfile]);
else setFile([...file, nfile]);
};
console.log("the files array", file);
return (
<div className="patientactions-container">
<input
type="file"
style={{ display: "none" }}
onChange={(e) => handleFileChange(e.target.files[0])}
ref={fileInput}
/>
<div onClick={() => handleClick()}>clck</div>
</div>
);
};
export default App;

I'd have put this in the comments but my rep is not high enough.
I had a problem with rendering changes to an array, because arrays use pointers it did not "register" a state change that was enough to cause a render. Using the spread operator in your solution affected the pointer and thus a render occurred.
In my own solution I set my array to null before adding content and that worked fine for my problem.

Related

My React App state is not in sync with my Firebase

I am having a hard time getting my React App working properly.
The thing is that I tried to use UseEffect hooks only to run side effects in my app and this has brought me some problems.
In this simple component I have a chat that get data from Firebase and is capable of updating the Db. I have no problem with the Firebase side but on the front end, the first render is not able to get me the messages into state properly.
I feel that it has of course something to do with async behaviors.
I will try to explain you the flow of my component :
The message text is kept in a const in state call "inputText"; when the form is submited a const call "numberOfMessageSent" is incremented; I have a UseEffect Hook that has [numberOfMessageSent] in its depedency; so after the first mount of the component and when "NumberOfMessageSent" increments the callback will fire; this callback fires 2 async functions: one to fetch the current discussion from the db and another to create a discussion object or update an existing one into the Db. I have a condition :
"numberOfMessagesSent !== 0 && asyncWarperCreateDiscussionInDb()" in the UseEffect Hook so a new discussion empty discussion won't be created the first this component mount.
My problem is that no discussion is displayed (nor properly fetched and stored into state) BEFORE I send a first message. After I send this first message everything works properly.
Can someone help me to understand this better ?
Thank you very much
here is my code :
import React, { useContext, useEffect, useState } from "react";
import "./card-medium-message.style.scss";
import likeEmpty from "./like-empty.png";
import likeFull from "./like-full.png";
import cancel from "./cancel.png";
import send from "./send.png";
import back from "./back.png";
import { useNavigate, useParams } from "react-router-dom";
import { UsersListContext } from "../../context/usersList-context/users-list-context";
import { UserContext } from "../../context/user-context/user-context";
import {
createDiscussionInDb,
goFetchDiscussionInDb,
goFetchDisscussion,
} from "../../utils/firebase";
const CardMediumMessage = () => {
const params = useParams();
const { usersListCTX } = useContext(UsersListContext);
const { currentUserContext } = useContext(UserContext);
const currentUserClickedOn = usersListCTX.filter(
(user) => user.displayName === params.name
);
console.log(currentUserContext);
console.log(currentUserClickedOn[0]);
const [messages, setMessages] = useState([]);
const [inputText, setInputText] = useState("");
const [numberOfMessagesSent, setNumberOfMessagesSent] = useState(0);
const asyncWarperFetchDiscussionInDb = async () => {
if (currentUserClickedOn[0]) {
const discussion = await goFetchDiscussionInDb(
currentUserContext.displayName,
currentUserClickedOn[0].displayName
);
setMessages(discussion.messages);
}
};
const asyncWarperCreateDiscussionInDb = async () => {
await createDiscussionInDb(
currentUserContext.displayName,
currentUserClickedOn[0].displayName,
inputText
);
resetField();
};
useEffect(() => {
numberOfMessagesSent !== 0 && asyncWarperCreateDiscussionInDb();
asyncWarperFetchDiscussionInDb();
console.log(
"this is written after first render of the component or numberOfMessagesSent was updated"
);
}, [numberOfMessagesSent]);
const messageSubmit = async (e) => {
e.preventDefault();
if (inputText == "") {
return;
}
setNumberOfMessagesSent(numberOfMessagesSent + 1);
};
const textChanged = (e) => {
setInputText(e.target.value);
};
const resetField = () => {
setInputText("");
};
const navigate = useNavigate();
messages && console.log(messages);
return (
<div className="card-medium-warp">
<div className="card-medium-message">
<div className="section1" onClick={() => navigate(-1)}>
<div className="profile-image-outer-circle">
{currentUserClickedOn[0] ? (
<img
src={`https://api.dicebear.com/5.x/micah/svg?seed=${currentUserClickedOn[0].displayName}`}
alt="avatar"
className="profile-image"
/>
) : undefined}
</div>
{currentUserClickedOn[0] ? (
<h2 className="name">{currentUserClickedOn[0].displayName} </h2>
) : undefined}
<div
className="back"
style={{ backgroundImage: `url(${back})` }}
></div>
</div>
<div className="section2">
{messages
? messages.map((messageObject, index) => (
<p
key={index}
className={
messageObject.by === currentUserContext.displayName
? "sender-message"
: "receiver-message"
}
>
{messageObject.message}
</p>
))
: undefined}
</div>
<form className="section3" onSubmit={messageSubmit}>
<input
type="text"
className="input"
placeholder="your message"
onChange={textChanged}
value={inputText}
autoFocus
/>
<div
className="send-message"
style={{ backgroundImage: `url(${send})` }}
></div>
</form>
</div>
</div>
);
};
export default CardMediumMessage;
I think I found the solution so I would like to share it :
My mistake was that I was calling functions that were async in themselves but I didn't chain them in an async/await manner.
This is what I am talking about :
const asyncWarperSequence = async () => {
numberOfMessagesSent !== 0 && (await asyncWarperCreateDiscussionInDb());
await asyncWarperFetchDiscussionInDb();
};
useEffect(() => {
console.log("UseEffect Fired");
asyncWarperSequence();
}, [numberOfMessagesSent]);

what is making props behavior inconsistent when set as a variable and passed into another function

I am creating a simple audio player using React.
When pushing the "Play" button, audio will be played from a url and the button will display "Pause". Once the audio is finished the button will reset to "Play."
There are three buttons "one" "two" "three". Each one will load a new audio url to be played by the button. Here is the codesandbox.
I'm new to useEffect and useState and I think there is an issue with how I'm updating this with props.
In my audio player component, if I directly set the url (without updating it with props), the audio will play as expected when the "play" button is pushed. However, when I set the url with props it won't play (even though the console.log() displays the correct url).
here is my Audio3.js component (responsible for playing audio:
import React, { useState, useEffect } from "react";
const useAudio = (url) => {
console.log("in useAudio", url)
const [audio] = useState(new Audio(url));
const [playing, setPlaying] = useState(false);
const toggle = () => setPlaying(!playing);
useEffect(() => {
playing ? audio.play() : audio.pause();
}, [playing]);
useEffect(() => {
audio.addEventListener("ended", () => setPlaying(false));
return () => {
audio.removeEventListener("ended", () => setPlaying(false));
};
}, []);
return [playing, toggle];
};
const Player = (props) => {
console.log("the current page is", props.currPage)
// const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/2.mp3?raw=true"
const url = "https://github.com/cre8ture/audioFilesForBL/blob/main/" +
props.currPage +
".mp3?raw=true"
console.log("the audio 3 is ", url);
const [playing, toggle] = useAudio(url);
return (
<div>
<button onClick={toggle}>{playing ? "Pause" : "Play"}</button>
</div>
);
};
export default Player;
Here is the threeButtons.js file:
import React from "react";
function Tabs(props) {
return (
<>
<button onClick={() => props.handleChangeProps(1)}>ONE</button>
<br />
<button onClick={() => props.handleChangeProps(2)}>TWO</button>
<br />
<button onClick={() => props.handleChangeProps(3)}>THRE</button>
</>
);
}
export default Tabs;
Here is the header component that houses the audio Play button:
import React from "react";
import Audio from "./Audio3";
function Header(props) {
console.log("header", props.currPage);
return (
<Audio currPage={props.currPage} />
);
}
export default Header;
And lastly, here is my App.js
import React, { useState } from "react";
import Header from "./components/header";
import ConceptTabs from "./components/threeButtons";
function App() {
const [pageID, setPageID] = useState(0);
// goes to ConceptTabs
let handleChange = (id) => {
console.log("clicked", id);
handleChange2(id);
setPageID(id);
return id;
};
// Goes to Audio2
let handleChange2 = (id) => {
console.log("clicked2", id);
return id;
};
console.log("pageID", pageID);
return (
<>
<Header currPage={pageID} />
<br />
<ConceptTabs handleChangeProps={handleChange} />
</>
);
}
export default App;
Thanks so much
const [audio] = useState(new Audio(url));
is triggered only once when component mounts. If you want to change it when url change, you'll need to have a useEffect
const audio = useRef(new Audio(url));
useEffect(() => {
audio.current = new Audio(url);
// add listeners
return () => {
if (audio.current) {
// Destroy audio & listeners
}
}
}, [url])
You'll also notice that I've used a useRef instead of useState for your audio, which is better when you need to store something which do not need a "re-render" when changing value

React hooks updating array is not working

I'm currently learning hook, and I'm writing a todo-list:
import './App.css';
import React, {useState} from 'react';
import Item from './components/Item';
function App() {
const [tasks, setTasks] = useState([]);
const addTask = (e) => {
if(e.key === 'Enter'){
let newTask = {content: e.target.value, completed: false};
console.log(newTask);
setTasks(prevTask => {
return ([...prevTask, newTask]);
});
console.log(tasks);
}
}
const completeTask = (e) =>{
let newTask = tasks.slice();
newTask[e].completed = !tasks[e].completed;
setTasks(newTask);
}
const deleteTask = (e) => {
let newTask = tasks.slice();
newTask.splice(e);
setTasks(newTask);
}
return (
<>
<header className="todo-app__header">
<h1 className="todo-app__title">todos</h1>
</header>
<section className="todo-app__main">
<input className="todo-app__input" placeholder="What needs to be done?" onKeyDown={addTask}/>
<ul className="todo-app__list" id="todo-list">
{tasks.map(item => <Item num = {tasks.indexOf(item)} text={item.content} completed = {item.completed}
onClick = {completeTask(tasks.indexOf(item))} delete = {deleteTask(tasks.indexOf(item))}/>)}
</ul>
</section>
</>
);
}
export default App;
However, adding tasks is not working!!
The newTask printed is well, but it doesn't push into the tasks array.
The tasks is still empty.
What's the problem?
Also, another problem: is it related to useeffect? I don't know what useeffect is used for.
The issue is that every time your component renders, it executes both completeTask and deleteTask, because you are using function calls as props. You need to be passing in a function object or expression. Instead of telling the component to execute completeTask on click, the function call just executes it right there as soon as the component is rendered.
The problem is with this part of your code:
<ul className="todo-app__list" id="todo-list">
{tasks.map(item => <Item num = {tasks.indexOf(item)} text={item.content} completed = {item.completed}
onClick = {completeTask(tasks.indexOf(item))} delete = {deleteTask(tasks.indexOf(item))}/>)}
</ul>
The following lines:
delete = {deleteTask(tasks.indexOf(item))}
onClick = {completeTask(tasks.indexOf(item))}
Should be changed to:
delete = {() => deleteTask(tasks.indexOf(item))}
onClick = {() => completeTask(tasks.indexOf(item))}
In normal HTML, it would look like delete="deleteFunction()", but in React, it should be delete={deleteFunction}, because writing a function with parenthesis after it is a function call, not a function expression. If you need to pass in an argument, you can either pass the argument in as a prop on the component, or change the line to delete={() => deleteFunction(arg)}, as the parenthesis and arrow makes it a function expression.
See Handling Events: https://reactjs.org/docs/handling-events.html
Your code works well and there is no problem.
React setState action in async. Try to log it in useEffect.
const App = () = => {
const [tasks, setTasks] = useState([]);
useEffect(() => {
console.log(tasks)
}, [tasks]);
const addTask = () => {
const newTask = {...};
setTasks([...tasks, newTask]);
}
}
useEffect Docs
Here as I can see, you are doing console.log(tasks) in the same function.
Try the console.log(tasks) outside function and you will see tasks array with the values you entered.
there is no problem
import React, {useState} from 'react';
function Add() {
const [tasks, setTasks] = useState([]);
const addTask = (e) => {
if(e.key === 'Enter'){
let newTask = {content: e.target.value, completed: false};
console.log(newTask);
setTasks(prevTask => {
return ([...prevTask, newTask])
});
console.log(tasks);
}
}
const completeTask = (e) =>{
let newTask = tasks.slice();
newTask[e].completed = !tasks[e].completed;
setTasks(newTask);
}
const deleteTask = (e) => {
let newTask = tasks.slice();
newTask.splice(e);
setTasks(newTask);
}
return (
<>
<header className="todo-app__header">
<h1 className="todo-app__title">todos</h1>
</header>
<section className="todo-app__main">
<input className="todo-app__input" placeholder="What needs to be done?" onKeyDown={addTask}/>
<ul className="todo-app__list" id="todo-list">
{tasks.map((item)=> <li>{item.content}</li>)}
</ul>
</section>
</>
);
}
export default Add;
it works very well and that means your Item component is your problem.

how can I save image and video file in redux

I am preparing a form that has fields to allow the user to post his picture and a video about her/him(self). To post a picture I prepared a component based on input type file, but to upload a video I have used React Dropzone. Now I want to save this to redux, however when I try to do that redux is complaining that it is the non-serializable item, and when I put it into JSON.Stringify() redux is getting an empty object. What would be the optimal solution for that, I have to store it somewhere in-state, should it be a local state created in this step of the form (fortunately it is the last one) however if the user would like to go back to any previous step and come back this data will be lost
Please advise, the dropzone component is below, onResult is just a handler that takes the value and dispatches an action to redux
import React, { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export const FileDropzone = ({ onResult, fileTypes, maxFiles }) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState(false);
const onDrop = useCallback(acceptedFiles => {
acceptedFiles.forEach(file => {
const reader = new FileReader();
reader.onerror = () => setError(true);
reader.onprogress = data => {
if (data.lengthComputable) {
var progress = parseInt(
(data.loaded / data.total) * 100,
10
);
setProgress(progress);
}
};
reader.onloadend = () => onResult(reader.result);
reader.readAsArrayBuffer(file);
});
}, []);
const { getRootProps, getInputProps } = useDropzone({
onDrop,
maxFiles,
accept: fileTypes
});
return (
<>
<div
className={`border border-${
error ? "danger" : "light"
} rounded d-flex justify-content-center align-items-center hpx-100`}
{...getRootProps()}
>
<input {...getInputProps()} />
<p>Proszę kliknąć, lub upuścić wybrany plik.</p>
</div>
<div
className="bg-primary hpx-20 mt-1"
style={{ width: `${progress}%` }}
></div>
</>
);
};
Thank you
I think your best bet would be to convert the file to Base64.
Please check How to convert file to base64 in JavaScript?
and
https://github.com/reduxjs/redux/issues/2276

Reactjs not updating the State in functional component

I am developing a simple application in reactS. The main purpose of the app is it will show some cards and on search, cards will be filtered and selective cards will be displayed. I am sharing the code of App.js.
I have a file name 'Robots.js'
import './App.css';
import CardList from './Components/CardList';
import Header from './Components/Header';
import {Robots} from './Components/Robots';
function App() {
const [robots,setRobots] = useState({
robo:Robots,
search:''
});
const onSearchChange = (e) =>{
setRobots({...robots,search:e.target.value});
const filteredRobots = robots.robo.filter(item=>{return item.name.toLowerCase().includes(robots.search.toLowerCase())});
//setRobots({...robots,robo:filteredRobots});
console.log(filteredRobots);
}
return (
<div>
<Header onSearchChange={onSearchChange} />
<CardList Robots={robots.robo}/>
</div>
);
}
export default App;
If I comment
setRobots({...robots,robo:filteredRobots});
this line on console I can show array is reducing its number of items but with that line it just does nothing. I think it makes sense it should do.
I hope I made my point clear.
You can update the state in one go as shown below:
const onSearchChange = (e) => {
const filteredRobots = robots.robo.filter(item => {
return item.name.toLowerCase().includes(robots.search.toLowerCase())
});
console.log(filteredRobots);
setRobots({
robo: filteredRobots,
search: e.target.value
});
}
Since you only have two properties you can just create a new object with those properties and don't really need spread operator.
The setRobots run "asynchronous" - therefore if you have:
const[data, setData] = setState(5);
const update = (newData) => {
setData(1);
console.log(data) // will print 5
}
And only in the second render its will print 1
you can read more in here: https://reactjs.org/docs/state-and-lifecycle.html
In your implementation you have a problem that you don't save the original array of the cards
So first save the original array
// Here will be the full list of cards.
const [cards, setCards] = useState(allCards);
// Here will be the search by text
const [search, setSearch] = useState("");
// Here will be all the filtered cards
// If you don't know whats useMemo is you can look in here: https://reactjs.org/docs/hooks-reference.html#usememo
const filteredCards = useMemo(()=>{
return cards.filter((card) =>
card.item.toLowerCase().contains(search.toLowerCase()));
},[cards, search])
// on search changed callback
const onSearchChange = (e)=>{setSearch(e.target.value || '')}
// then return your component
return (
<div>
<Header onSearchChange={onSearchChange} />
<CardList Robots={filteredCards}/>
</div>
);

Categories