My React App state is not in sync with my Firebase - javascript

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]);

Related

Displaying an image from an API In React

I am trying to display the image of the breed selected by the user in this code but it is not working,
any ideas or hints as of why?
Thank you
import React, { useState, useEffect } from 'react';
import './breed-image.css';
function BreedImage () {
const [breed, selectedBreed] = useState('');
useEffect(() => {
fetchImage();
}, []);
const fetchImage = async () => {
const res = await fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
const data = await res.json();
const imageUrl = data.message
selectedBreed(imageUrl);
};
return (
<div className="image-container">
<img className="image-card" src={breed} alt="doggopicture" />
</div>
);
}
export default BreedImage;
There's some weird logic in here that doesn't make sense. breed is initialized to an empty string. Then, in your useEffect you have an empty dependencies array, which means it will be called once. Which means your API request hits https://dog.ceo/api/breed//images/random which presumably would fail (since breed was '').
Most likely you instead want:
import React, { useState, useEffect } from 'react';
import './breed-image.css';
function BreedImage () {
const [breed, setBreed] = useState('');
const [url, setUrl] = useState('');
useEffect(() => {
fetchImage();
}, [breed]);
const fetchImage = async () => {
const res = await fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
const data = await res.json();
setUrl(data.message);
};
return (
<>
<DogPicker onChange={(breed) => setBreed(breed)} />
<div className="image-container">
<img className="image-card" src={url} alt="doggopicture" />
</div>
</>
);
}
export default BreedImage;
where you'd pass setBreed to some other component. Or, you could pass breed down to this component as a prop, and again use useEffect to watch for changes.

React useState conversion

I made a static webpage app that I have been slowly converting to React (MERN stack) to make it more dynamic/so I won't have to configure each and every HTML document. It's a product configurator that uses Google's model-viewer.
I'm fairly new to using a full-stack workflow but have found it pretty fun so far! I am having trouble however understanding on how to convert some of my vanilla JS to work within React. This particular script will change a source/3D model when a user clicks on a button. Below is a code snipit of what I have working currently on a static webpage.
import {useEffect, useState} from "react";
import {useSelector, useDispatch} from "react-redux";
// Actions
import {getProductDetails} from "../redux/actions/productActions";
const ProductScreen = ({match}) => {
const dispatch = useDispatch();
const [currentSrc, setCurrentSrc] = useState()
const [srcOptions, setSrcOptions] = useState()
const productDetails = useSelector((state) => state.getProductDetails);
const {loading, error, product} = productDetails;
useEffect(() => {
if (product && match.params.id !== product._id) {
dispatch(getProductDetails(match.params.id));
setCurrentSrc(product.src);
setSrcOptions(product.srcList);
}
}, [dispatch, match, product]);
return (
<div className="productcreen">
{loading ? (
<h2> Loading...</h2>) : error ? (
<h2>{error}</h2>) : (
<>
<div className='sizebuttons'>
{srcOptions.map((src) => (
<button onClick={() => setCurrentSrc(src)}>{src}{product.size}</button>
))}
{srcOptions.map((src) => (
<button onClick={() => setCurrentSrc(src)}>{src2}{product.size2}</button>
))}
{srcOptions.map((src) => (
<button onClick={() => setCurrentSrc(src)}>{src3}{product.size3}</button>
))}
</div>
<div className="productscreen__right">
<model-viewer id="model-viewer" src={currentSrc} alt={product.name} ar ar-modes="scene-viewer quick-look" ar-placement="floor" shadow-intensity="1" camera-controls min-camera-orbit={product.mincameraorbit} max-camera-orbit={product.maxcameraorbit} interaction-prompt="none">
<button slot="ar-button" className="ar-button">
View in your space
</button>
</model-viewer>
</div>
</> )} )};
Here is what the DB looks like:
The "product.size" is being pulled in from MongoDB, and I'm wondering if I could just swap models with: "product.src","product.src2","product.src3" (which is also defined in the DB already) I'm assuming I need to use useState in order to switch the source, but I am unsure. Any help would be greatly appreciated! If you'd like to see the static webpage of what I'm trying to accomplish, you can view it here if that helps at all.
Here is how the products are being exported in redux:
import * as actionTypes from '../constants/productConstants';
import axios from 'axios';
export const getProductDetails = (id) => async(dispatch) => {
try {dispatch({type: actionTypes.GET_PRODUCT_DETAILS_REQUEST});
const {data} = await axios.get(`/api/products/${id}`);
dispatch({
type: actionTypes.GET_PRODUCT_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: actionTypes.GET_PRODUCT_DETAILS_FAIL,
payload: error.response && error.response.data.message ?
error.response.data.message :
error.message,
});
}
};
You can use the useState hook from React to create the state. After you fetch your product from the DB you can set the initial value with setCurrentSrc or if it's coming from props, you can set the initial value like this: const [currentSrc, setCurrentSrc] = useState(props.product.src).
Then change the src of your model-viewer to use the state value so it will automatically rerender if the state value changes. Lastly, add onClick handlers to some buttons with the setCurrentSrc function to change the state.
const ProductViewer = (props) => {
const [currentSrc, setCurrentSrc] = useState()
const [srcOptions, setSrcOptions] = useState()
const dispatch = useDispatch()
const { loading, error, product } = useSelector(
(state) => state.getProductDetails
)
useEffect(() => {
if (product && match.params.id !== product._id) {
dispatch(getProductDetails(match.params.id))
}
}, [dispatch, match, product])
// update src and srcOptions when product changes
useEffect(() => {
setCurrentSrc(product.src)
setSrcOptions(product.srcList)
}, [product])
return (
<div className="productscreen__right">
<model-viewer
id="model-viewer"
src={currentSrc}
alt={product.name}
ar
ar-modes="scene-viewer quick-look"
ar-placement="floor"
shadow-intensity="1"
camera-controls
min-camera-orbit={product.mincameraorbit}
max-camera-orbit={product.maxcameraorbit}
interaction-prompt="none"
>
<button slot="ar-button" className="ar-button">
View in your space
</button>
{/* add your switch buttons somewhere... */}
{/* this assumes you have a srcList, but this could also be hardcoded */}
{srcOptions.map((src) => (
<buttton onClick={() => setCurrentSrc(src)}>{src}</buttton>
))}
</model-viewer>
</div>
)
}

How to instantly display data after an API call in react hooks

I don't understand why the second line, which reads data from the props, is not displayed as instantly as the first, i would like them to be displayed instantly
I update the state when a button is clicked, which calls api, data is coming in, the state is updating, but the second line requires an additional press to display
How to display both lines at once after a call? What's my mistake?
I'm using react hooks, and i know that required to use useEffect for re-render component, i know, that how do work asynchronous call,but i'm a little confused, how can i solve my problem, maybe i need to use 'useDeep effect' so that watching my object properties, or i don't understand at all how to use 'useEffect' in my situation, or even my api call incorrectly?
I have tried many different solution methods, for instance using Promise.all, waiting for a response and only then update the state
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./test";
ReactDOM.render(<App />, document.getElementById("root"));
app.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const useDataApi = (initialState) => {
const [state, setState] = useState(initialState);
const stateCopy = [...state];
const setDate = (number, value) => {
setState(() => {
stateCopy[number].date = value;
return stateCopy;
});
};
const setInfo = async () => {
stateCopy.map((item, index) =>
getFetch(item.steamId).then((res) => setDate(index, res.Date))
);
};
const getFetch = async (id) => {
if (id === "") return;
const requestID = await fetch(`https://api.covid19api.com/summary`);
const responseJSON = await requestID.json();
console.log(responseJSON);
const result = await responseJSON;
return result;
};
return { state, setState, setInfo };
};
const Children = ({ data }) => {
return (
<>
<ul>
{data.map((item) => (
<li key={item.id}>
{item.date ? item.date : "Not data"}
<br></br>
</li>
))}
</ul>
</>
);
};
const InfoUsers = ({ number, steamid, change }) => {
return (
<>
<input
value={steamid}
numb={number}
onChange={(e) => change(number, e.target.value)}
/>
</>
);
};
function App() {
const usersProfiles = [
{ date: "", id: 1 },
{ date: "", id: 2 }
];
const profiles = useDataApi(usersProfiles);
return (
<div>
<InfoUsers number={0} change={profiles.setID} />
<InfoUsers number={1} change={profiles.setID} />
<button onClick={() => profiles.setInfo()}>Get</button>
<Children data={profiles.state} loading={profiles} />
</div>
);
}
export default App;
To get the data, just click GET
In this example, completely removed useEffect, maybe i don’t understand how to use it correctly.
P.s: Sorry for bad english
You don't need stateCopy, as you have it in the callback of the setState:
const setInfo = async () => {
// we want to update the component only once
const results = await Promise.all(
state.map(item => getFetch(item.steamId))
);
// 's' is the current state
setState(s =>
results.map((res, index) => ({ ...s[index], date: res.Date })
);
};

don't understand how can I get pollId from reactjs poll

this is my react code here I am getting react poll using API but when I start working on handalchange For POST API request I need (PollId,userId and answer) I am getting userId through { const userId = isAutheticated() && isAutheticated().user._id; } but I do not understand how can I get PollId from my all polls, please help...!
import React, { useState, useEffect } from "react";
import Poll from "react-polls";
import "../../styles.css";
import { isAutheticated } from "../../auth/helper/index";
import { getPolls, postPoll } from "../helper/coreapicalls";
import { useParams } from "react-router-dom";
const MainPoll = () => {
const userId = isAutheticated() && isAutheticated().user._id;
const pollId = useParams();
const id = pollId._Id;
console.log(id);
const [polls, setPoll] = useState([]);
const [error, seterror] = useState(false);
// Setting answers to state to reload the component with each vote
const [pollAnswers, setPollAnswers] = useState([]);
useEffect(() => {
loadPoll();
}, []);
const loadPoll = () => {
getPolls().then((data) => {
if (data.error) {
seterror(data.error);
} else {
setPoll(data);
console.log(data);
}
});
};
// Handling user vote
// Increments the votes count of answer when the user votes
const handalchange = () => {
postPoll();
console.log("hello");
};
return (
<div className="">
<div className="container my-5">
<h1 className="blog_heading my-3">Poll's of the Day</h1>
<div className="row">
{polls.reverse().map((poll, index) => (
<div className="col-lg-4 col-12 poll_border" key={index}>
<Poll
noStorage
question={poll.question}
answers={Object.keys(poll.options).map((key) => {
return {
option: key,
votes: poll.options[key].length,
};
})}
onVote={handalchange}
className="mb-2"
/>
</div>
))}
</div>
</div>
</div>
);
};
export default MainPoll;
my frontend image -
Here I have 5 polls , so I can not get PollId from useParams ...! so how can I get..?
Your component seems to represent list of polls, not any specific poll. So if you have an array of polls instead of one poll, than you have multiple ids instead of the single one.
You can get them by mapping your polls array like that:
const pollIds = polls.map((poll) => poll.id); // or any other prop that stores id

Which useEffect will be called after every Render?

I'm a beginner in React and stuck with some problem. I have several queries regarding this code.
Which UseEffect will be called after every render?
Why and How console.log() is called 13 times ?(Please find the screenshot below)
Why the fetched data is not shown in browser until I type something in the search bar?
App.js
import React, { useEffect } from "react";
import { useState } from "react";
import axios from "axios";
function App() {
const [monster, setMonster] = useState([]);
const [searchName, setName] = useState("");
const [filteredMonster, setFilter] = useState([]);
useEffect(() => {
async function fetchData() {
await axios.get(
"https://jsonplaceholder.typicode.com/users"
).then((resp)=>{
setMonster(resp.data);
})
console.log(monster);
}
fetchData();
}, []);
useEffect(()=>{
const mons = monster;
setFilter(mons.filter(mon =>
mon.name.toLowerCase().includes(searchName.toLowerCase())
));
}, [searchName]);
function changeName(event) {
setName(event.target.value);
}
console.log(monster);
const cunter = useRef(0);
return (
<div className="App">
<form>
<input
type="search"
name="searchName"
value={searchName}
onChange={changeName}
/>
</form>
{cunter.current++}
{filteredMonster&&filteredMonster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{monster&&!filteredMonster&&monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
</div>
);
}
export default App;
try this please. fetchData() will run only 1, searchName will run as many times you type on the screen.
TIP: To prevent this. add a timeoutdelay after user finishes typing to only render once instead of N times user presses a keyboard key.
import React, { useEffect } from "react";
import { useState } from "react";
import axios from "axios";
const URL = "https://jsonplaceholder.typicode.com/users"
function App() {
const [monster, setMonster] = useState([]);
const [searchName, setName] = useState("");
const [filteredMonster, setFilter] = useState([]);
useEffect(() => {
async function fetchData() {
await axios.get(URL).then((resp) => {
setMonster(resp.data);
})
console.log(monster);
}
fetchData();
}, []);
useEffect(() => {
if (monster.length > 0) {
const filter = mons.filter(({name}) =>
name.toLowerCase().includes(searchName.toLowerCase()));
setFilter(filter);
}
}, [searchName]);
function changeName(event) {
setName(event.target.value);
}
console.log(JSON.stringify(monster));
return (
<div className="App">
<form>
<input
type="search"
name="searchName"
value={searchName}
onKeyUp={(e) => changeName(e)}
/>
</form>
{monster.length > 0 &&
<div>{JSON.stringify(monster)}</div>
}
{filteredMonster && filteredMonster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{monster && !filteredMonster && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
</div>
);
}
export default App;
This is using Reducer, removes the use of state.
import React, { useEffect, useReducer } from "react";
import axios from "axios";
const URL = "https://jsonplaceholder.typicode.com/users"
const reducer = (state, action) => {
switch(action.type){
case 'FETCH_DATA':
return {
...state,
monster: action.monster,
name: "",
}
case 'SEARCH_MONSTER':
return {
...state,
name: action.name,
}
case 'FILTER_MONSTER':
const filter = state.monster.filter(({name}) =>
name.toLowerCase().includes(searchName.toLowerCase()));
return {
...state,
filteredMonster: filter,
name: state.name,
}
}
};
function App() {
const [state, dispatch] = useReducer(reducer, {
monster: [],
filteredMonster: [],
name: '',
});
useEffect(() => {
async function fetchData() {
await axios.get(URL).then((resp) => {
dispatch({ type: 'FETCH_DATA', monster: resp.data});
})
console.log(monster);
}
fetchData();
}, []);
useEffect(() => {
if (monster.length > 0) dispatch({ type: 'FILTER_MONSTER'});
}, [stat.name]);
console.log(JSON.stringify(monster));
return (
<div className="App">
<form>
<input
type="search"
name="searchName"
value={state.name}
onKeyUp={(e) => dispatch({ type: 'SEARCH_MONSTER', name: e.target.value })}
/>
</form>
{state.monster.length > 0 &&
<div>{JSON.stringify(monster)}</div>
}
{state.filteredMonster && state.filteredMonster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{state.monster && !state.filteredMonster && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
</div>
);
}
export default App;
1. Which UseEffect will be called after every render?
Ans: According to the react official doc useEffect does care about 3 lifecycle method namely componentDidMount componentDidUpdate and componentWillUnmount. So no matter what how many useEffect you have, all the effect hooks will execute when componentMount for the first time. But useEffect will execute further, only when it's dependency get updates else it will ignore
2. Why and How console.log() is called 13 times?
Ans: I tried to reproduce 13 times rerendering but I am not able to do so. But yes it's rerendering multiple times because in the 2nd useEffect on every Keystore the state is updating and because of that component is rerendering several times.
its happening something like this
changeName() → setName() → useEffect() → setFilter() → (on every keystore repeating same step) → ...loop
you can try debounce or throttling which can help you to avoid continuous Keystore hit by which no of rerendering can drastically reduce
Instead of using console.log, there is a hack to know the number of rerendering
declare the below code in the component
const cunter = useRef(0);
and then in the return block add {cunter.current++} by this you can see how many times your component is actually rerendering
3. Why the fetched data is not shown in the browser until I type something in the search bar?
This is because in your condition your checking !filteredMonster where filteredMonster is an array and !filteredMonster will return always false instead try Array length properties
filteredMonster.length === 0
{monster && !filteredMonster && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}
{(monster && filteredMonster.length === 0) && monster.map((item, index) => (
<p key={index}>{item.name}</p>
))}

Categories