Can I do client side fetching inside the server side render component? - javascript

Can I perform client-side data fetching inside a component that's being rendered on a server-side rendered page? I have a page located at pages/solution/[solutionId]/index.js, which is server-side rendered, and it contains three components that should be performing client-side fetching. However, I am not getting any data, and it is returning null.
index.js
const Solution = ({ solution }) => {
const [isOpen, setIsOpen] = useState(false)
const router = useRouter()
const { id } = router.query
const { user } = useAuthContext()
return (
<>
<div className="px-5 row-start-2 row-end-3 col-start-2 col-end-3 mb-4">
// doing client-side fetching
<ShowWebsite
url={solution?.liveWebsiteUrl}
github={solution?.githubUrl}
title={solution?.title}
isPlayground={solution?.isPlayground}
/>
<div className="grid grid-col-1 md:grid-cols-[1fr_160px] items-start gap-x-5 mt-10">
<SolutionComments /> // doing client side fetching
<EmojiSection /> // doing client side fetching
</div>
</div>
</>
)
}
export default Solution
export async function getServerSideProps({ query }) {
const { solutionId } = query
console.log(solutionId)
const solution = await getDocument("solutions", solutionId)
return {
props: {
solution,
},
}
}
SolutionsComment:
import { useState } from "react"
import { useRouter } from "next/router"
import { useCollection } from "../../hooks/useCollection"
import Comment from "./Comment"
import CommentForm from "./CommentForm"
const SolutionComments = () => {
const router = useRouter()
const { id } = router.query
const { documents } = useCollection(`solutions/${id}/comments`)
return (
<div className="mt-10 md:mt-0">
<CommentForm docID={id} />
<div className="mt-10">
{documents &&
documents.map((comment) => (
<Comment
key={comment.id}
comment={comment}
replies={comment.replies}
/>
))}
</div>
</div>
)
}
EmojiSection:
import React from "react"
import { useRouter } from "next/router"
import { useDocument } from "../../hooks/useDocument"
import Emoji from "./Emoji"
const EmojiSection = () => {
const router = useRouter()
const { id: docID } = router.query
const { document: reactions } = useDocument(`solutions/${docID}/reactions`, "emojis")
console.log(reactions)
return (
// JSX CODE
)
}
useCollection:
import { collection, onSnapshot} from "firebase/firestore"
export const useCollection = (c) => {
const [documents, setDocuments] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
let ref = collection(db, c)
const unsubscribe = onSnapshot(ref, (snapshot) => {
const results = []
snapshot.docs.forEach(
(doc) => {
results.push({ ...doc.data(), id: doc.id })
},
(error) => {
console.log(error)
setError("could not fetch the data")
}
)
// update state
setDocuments(results)
setIsLoading(false)
setError(null)
})
return () => unsubscribe()
}, [])
return { documents, error, isLoading }
}

Related

Fetching data with axios in a custom react hook doesn't return data

I used this code to show the id of a product.
import { useState } from 'react';
import axios from 'axios';
const BASE_URL = "https://makeup-api.herokuapp.com/api/v1/products";
const useGetProduct = () => {
const [products, setProducts] = useState([]);
const [singleProduct, setSingleProduct] = useState(null);
const getTopProducts = () => {
axios.get(`${BASE_URL}.json`, {
params: {
product_tags: 'Canadian',
},
})
.then(Response => setProducts(Response.data));
};
const getSingleProduct = () => {
axios.get(`${BASE_URL}/1048.jason`)
.then(Response => setSingleProduct(Response.data));
};
return {
products,
getTopProducts,
singleProduct,
getSingleProduct,
}
};
export default useGetProduct;
import React, { useEffect } from "react";
import { useParams } from "react-router-dom";
import useGetProduct from "../hooks/useGetProduct";
const Product = () => {
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
return (
<div>
<p>Product: {id}</p>
</div>
);
};
export default Product;
app.js
<Route exact path="/product/:id" element={<Product />} />
But when I change <p>Product: {id}</p> to <p>Product: {singleProduct?.name}</p> the product name does not display. Instead, it just shows Product: without the details about the product on localhost and I am not sure why.
const Product = () => {
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
return (
<div>
<p>Product: {singleProduct?.name}</p>
</div>
);
};
export default Product;
I want to show details about a single product via a hook in react js.
const { id } = useParams();
const { singleProduct, getSingleProduct } = useGetProduct();
useEffect(() => {
getSingleProduct();
}, []);
You are not using the id from useParams. Shouldn't you pass it to getSingleProduct, I assume that singleProduct is undefined.

How to send array of items to localstorage within context api? (REACT)

I'm developing a pokedex using pokeAPI through react, but I'm developing a feature where I can favorite pokemons and with that through a context, I can store the names of these pokemons in a global array. I've already managed to test and verify that the pokemon names are going to this "database" array inside the pokeDatabase const in my context, but my goal now is to pass this array to localstorage so that the browser recognizes these favorite pokemons instead of disappearing every time I refresh the page, my solution was to try to create a useEffect inside the context so that every time I refresh my application, this information is saved in localStorage, but without success. What better way to achieve this?
context:
import { createContext } from "react";
const CatchContext = createContext({
pokemons: null,
});
export default CatchContext;
provider
import React, { useEffect } from "react";
import CatchContext from "./Context";
const pokeDatabase = {
database: [],
};
const CatchProvider = ({ children }) => {
useEffect(() => {
const dataStorage = async () => {
await localStorage.setItem('pokemons', JSON.stringify(pokeDatabase.database));
}
dataStorage();
}, [])
return (
<CatchContext.Provider value={{ pokemons: pokeDatabase }}>
{children}
</CatchContext.Provider>
);
}
export default CatchProvider;
pageAddPokemon
import * as C from './styles';
import { useContext, useEffect, useState } from 'react';
import { useApi } from '../../hooks/useApi';
import { useNavigate, useParams } from 'react-router-dom';
import PokeInfo from '../../components/PokeInfo';
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import DoNotDisturbOnIcon from '#mui/icons-material/DoNotDisturbOn';
import CatchContext from '../../context/Context';
const SinglePokemon = () => {
const api = useApi();
const { pokemons } = useContext(CatchContext);
const { name } = useParams();
const navigate = useNavigate();
const handleHompage = () => {
navigate('/');
}
const [loading, setLoading] = useState(false);
const [imgDatabase, setImgDatabase] = useState('');
const [infoPokemon, setInfoPokemon] = useState([]);
const [pokemonTypes, setPokemonTypes] = useState([]);
const [isCatch, setIsCatch] = useState(false);
useEffect(() => {
const singlePokemon = async () => {
const pokemon = await api.getPokemon(name);
setLoading(true);
setImgDatabase(pokemon.sprites);
setInfoPokemon(pokemon);
setPokemonTypes(pokemon.types);
setLoading(false);
console.log(pokemons.database);
}
singlePokemon();
verifyPokemonInDatabase();
}, []);
const verifyPokemonInDatabase = () => {
if (pokemons.database[infoPokemon.name]) {
return setIsCatch(true);
} else {
return setIsCatch(false);
}
}
const handleCatchAdd = async () => {
if (isCatch === false) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.push(infoPokemon.name);
setIsCatch(true);
}
}
const handleCatchRemove = async () => {
if (isCatch === true) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.splice(pokemons.database.indexOf(toString(infoPokemon.name)), 1);
setIsCatch(false);
}
}
return (
<C.Container>
<PokeInfo
name={infoPokemon.name}
/>
<C.Card>
<C.Info>
<C.Imgs>
<img src={imgDatabase.front_default} alt="" />
<img src={imgDatabase.back_default} alt="" />
</C.Imgs>
<h2 id='types'>Tipos</h2>
{pokemonTypes.map(type => {
return (
<C.Types>
<h2>{type.type.name}</h2>
</C.Types>
)
})}
{isCatch ? (
<DoNotDisturbOnIcon id='iconCatched' onClick={handleCatchRemove}/>
): <AddCircleOutlineIcon id='icon' onClick={handleCatchAdd}/>}
</C.Info>
</C.Card>
<C.Return>
<button onClick={handleHompage}>Retornar a Pokédex</button>
</C.Return>
</C.Container>
)
}
export default SinglePokemon;

Array doesn't save information ReactJS

import './App.css';
import io from 'socket.io-client'
import { useEffect, useRef, useState } from 'react'
import React from 'react';
import ReactDOM from "react-dom/client";
const socket = io.connect("http://localhost:3001");
function App() {
const [message, setMessage] = useState("");
const [state, setState] = useState([]);
const [chat, setChat] = useState([]);
const socketRef = useRef();
const sendMessage = () => {
socket.emit("send_message", { message });
};
const renderChat = () => {
return (
chat.map(msg => {
console.log(msg.data)
return (
<h3>{msg.data["message"]}</h3>
)
})
)
}
useEffect(() => {
socketRef.current = io.connect("http://localhost:3001")
socketRef.current.on("receive_message", ({ message }) => {
setChat([ ...chat, { message } ])
})
return () => socketRef.current.disconnect()
},
[ chat ]
)
return (
<div className="App">
<input placeholder="Message..." onChange={(event) => {
setMessage(event.target.value);}}
/>
<button onClick={sendMessage}>Send Message</button>
<h1>Message:</h1>
{renderChat()}
</div>
);
}
export default App;
For some reason the useEffect that needs to store information doesn't work. I have tried a few solutions to store new values in an array useState but I always get this error:
When I do it like this:
useEffect(() => {
socket.on("receive_message", message => {
setChat([...chat, {message}]);
});
}, [socket])
it works but it doesn't save the information (it always has only 1 value which is the latest input text).
You can do it like in the second approach you mentioned, using the previous State:
useEffect(() => {
socket.on("receive_message", message => {
setChat(prevState => [...prevState, {message}]);
});
}, [socket])
You try
useEffect(() => {
socket.on("receive_message", ({ message }) => {
if(!!message){
setChat(prev => [ ...prev, { message } ])
}
})
return () => socket.disconnect()
},[ socket ])

What is Problem TypeError: action.value is undefined? (React)

This is the file that I retrieved data from Firebase's table. I have no problem here. Here I have logged the console to see all the data. My problem is in the quiz.js file
useQuestion.js
import { getDatabase, ref, query, orderByKey, get } from "firebase/database";
import { useEffect, useState } from "react";
export default function useQuestions(yID) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [questions, setQuestions] = useState();
useEffect(() => {
// get data from database
async function fetchQuestion() {
const db = getDatabase();
const questionRef = ref(db, "quiz/" + yID + "/questions");
const questionQuery = query(questionRef, orderByKey());
try {
setError(false);
setLoading(true);
const snapshot = await get(questionQuery);
setLoading(false);
if (snapshot.exists()) {
setQuestions((prevQuestions) => {
return [...prevQuestions, ...Object.values(snapshot.val())];
});
}
} catch (e) {
setLoading(false);
console.log(e);
setError("You are Press Wrong Questions.");
}
}
fetchQuestion();
}, [yID]);
return {
loading,
error,
questions,
};
}
The problem with this file is useReducer. I set the dispatch value but showing action.value is undefined. why this problem in my project.
quiz.js
import { useEffect, useReducer, useState } from "react";
import useQuestions from "../../hooks/useQuestion";
import { useParams } from "react-router";
import Answare from "../../answare";
import MiniPlayer from "../../miniPlayer";
import PogressBar from "../../pogressBar";
import _ from "lodash";
const initialState = null;
const reducer = (state, action) => {
switch (action.type) {
case "questions":
action.value.forEach((question) => {
question.options.forEach((option) => {
option.checked = false;
});
});
return action.value;
case "answer":
const questions = _.cloneDeep(state);
questions[action.questionID].options[action.optionIndex].checked =
action.value;
return questions;
default:
return state;
}
};
function Quiz() {
const { id } = useParams();
const [currentQuestions, setCurrentQuestions] = useState(0);
const { loading, error, questions } = useQuestions(id);
const [qna, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({
type: "questions",
value: questions,
});
}, [questions]);
const handleAnswareChange = (e, index) => {
dispatch({
type: "answer",
QuestionsID: currentQuestions,
optionIndex: index,
value: e.target.checked,
});
};
return (
<>
{loading && <div>Loading.....</div>}
{error && <div>There was a problem.</div>}
{!loading && !error && qna && qna.length > 0 && (
<>
<h1>{qna[currentQuestions].title}</h1>
<h4>Question can have multiple answers</h4>
<Answare
options={qna[currentQuestions].options}
handleChange={handleAnswareChange}
/>
<PogressBar />
<MiniPlayer />
</>
)}
</>
);
}
export default Quiz;
At my point of view you should console out e.target.checked before dispatching it. it may be undefined

How to wait until context value is set?

I'm trying to render a header.
First, in InnerList.js, I make an API call, and with the data from the API call, I set a list in context.
Second, in Context.js, I take the list and set it to a specific data.
Then, in InnerListHeader.js, I use the specific data to render within the header.
Problem: I currently get a TypeError undefined because the context is not set before rendering. Is there a way to wait via async or something else for the data to set before loading?
My code block is below. I've been looking through a lot of questions on StackOverflow and blogs but to no avail. Thank you!
InnerList.js
componentDidMount() {
const { dtc_id } = this.props.match.params;
const {
setSpecificDtcCommentList,
} = this.context;
MechApiService.getSpecificDtcCommentList(dtc_id)
.then(res =>
setSpecificDtcCommentList(res)
)
}
renderSpecificDtcCommentListHeader() {
const { specificDtc = [] } = this.context;
return (
<InnerDtcCommentListItemHeader key={specificDtc.id} specificDtc={specificDtc} />
)
}
Context.js
setSpecificDtcCommentList = (specificDtcCommentList) => {
this.setState({ specificDtcCommentList })
this.setSpecificDtc(specificDtcCommentList)
}
setSpecificDtc = (specificDtcCommentList) => {
this.setState({ specificDtc: specificDtcCommentList[0] })
}
InnerListHeader.js
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
In general, you should always consider that a variable can reach the rendering stage without a proper value (e.g. unset). It is up to you prevent a crash on that.
For instance, you could rewrite you snippet as follows:
render() {
const { specificDtc } = this.props;
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{Boolean(specificDtc.dtc_id) && specificDtc.dtc_id.dtc}
</div>
</div>
);
}
When you make an api call you can set a loader while the data is being fetched from the api and once it is there you show the component that will render that data.
In your example you can add a new state that will pass the api call status to the children like that
render() {
const { specificDtc, fetchingData } = this.props;
if (fetchingData){
return <p>Loading</p>
}else{
return (
<div>
<div className="InnerDtcCommentListItemHeader__comment">
{specificDtc.dtc_id.dtc}
</div>
</div>
);
}
}
``
in my case, i am calling external api to firebase which lead to that context pass undefined for some values like user. so i have used loading set to wait untile the api request is finished and then return the provider
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
GoogleAuthProvider,
signInWithPopup,
updateProfile
} from 'firebase/auth';
import { auth } from '../firebase';
import { useNavigate } from 'react-router';
import { create_user_db, get_user_db } from 'api/UserAPI';
import { CircularProgress, LinearProgress } from '#mui/material';
import Loader from 'ui-component/Loader';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState();
const [user_db, setUserDB] = useState();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
const createUser = async (email, password) => {
const user = await createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const googleSignIn = async () => {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider)
.then(() => setIsAuthenticated(true))
.catch(() => setIsAuthenticated(false));
};
const logout = () => {
setUser();
return signOut(auth).then(() => {
window.location = '/login';
});
};
const updateUserProfile = async (obj) => {
await updateProfile(auth.currentUser, obj);
return updateUser(obj);
};
const updateUser = async (user) => {
return setUser((prevState) => {
return {
...prevState,
...user
};
});
};
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
setLoading(true);
if (currentUser) {
const user_db = await get_user_db({ access_token: currentUser.accessToken });
setUserDB(user_db);
setUser(currentUser);
setIsAuthenticated(true);
}
setLoading(false);
});
return () => {
unsubscribe();
};
}, []);
if (loading) return <Loader />;
return (
<UserContext.Provider value={{ createUser, user, user_db, isAuthenticated, logout, signIn, googleSignIn, updateUserProfile }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};

Categories