I have a simple search component and handleSearch function:
const { data, loading, error } = useQuery(QUERY_GET_ELEMENTS);
const client = useApolloClient();
<input
onChange={handleSearch}
placeholder="🔎 Search..."
/>
function handleSearch(e) {
const { value } = e.target;
const matchingElements = data.filter(({ name }) =>
name.toLowerCase().includes(value.toLowerCase())
);
client.writeData({
data: {
elements: matchingElements
}
});
}
// rendering the elements looks something like this:
data.elements.map(el => <div>{el.name}</div>
The data comes from a useQuery hook.
The problem is that the search only works in one direction as once the elements are filtered I lose the original list. I need to keep a store of all of the elements that I can filter and render only the filtered ones while persisting the original list.
I'm using apollo for state management and cannot seem to get this working. My first thought was to use client.writeData to duplicate the elements and that would never be modified, however this did not work as expected.
Any help is much appreciated.
You should be able to accomplish this with the useState hook. This example works for me:
import React, { useState, useEffect } from 'react';
import gql from 'graphql-tag';
import { useQuery } from '#apollo/react-hooks'
const QUERY_GET_ELEMENTS = gql`
{
elements {
id
name
}
}
`;
export default function Test() {
const [isDisplayDataSet, setIsDisplayDataSet] = useState(false);
const [displayData, setDisplayData] = useState([]);
const { data, loading, error } = useQuery(QUERY_GET_ELEMENTS);
useEffect(() => {
if (!loading && !isDisplayDataSet) {
setDisplayData(data.elements);
setIsDisplayDataSet(true);
}
}, [isDisplayDataSet, displayData, data, loading])
function handleSearch(e) {
const { value } = e.target;
const matchingElements = data.elements.filter(({ name }) =>
name.toLowerCase().includes(value.toLowerCase())
);
setDisplayData(matchingElements);
}
if (error) {
console.error(error);
return <h1>There was an error</h1>
}
if (isDisplayDataSet) {
return (
<>
<input
className="form-control mb-3"
onChange={handleSearch}
placeholder="🔎 Search..."
/>
<ul className="list-group">
{displayData.map(el => <li className="list-group-item" key={el.id}>{el.name}</li>)}
</ul>
</>
);
} else {
return '';
}
}
I added some bootstrap classes for styling :)
And here is the quick-and-dirty apollo-server I setup to load some data in:
const { ApolloServer } = require('apollo-server');
const gql = require('graphql-tag');
const fetch = require('node-fetch');
const typeDefs = gql`
type Element {
id: ID!
name: String!
}
type Query {
elements: [Element]!
}
schema {
query: Query
}
`;
const resolvers = {
Query: {
async elements() {
const res = await fetch('https://reqres.in/api/users');
const { data } = await res.json();
const elements = data.map(({ id, first_name, last_name }) => ({ id, name: `${first_name} ${last_name}` }))
console.log('elements', elements);
return elements;
}
}
}
const server = new ApolloServer({
typeDefs,
resolvers
});
server.listen().then(({ url }) => {
console.log('server ready on ' + url);
});
Related
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 }
}
i am trying to query data for dbUser restaurant and basket but im getting error type possible unhandled promise rejection (id:17)
type error: o.userID is not a function. (o.userID('eq', dbUser.id)
type error null is not an object ( evaluating restaurant.id).
type here
import { createContext, useState, useEffect, useContext } from "react";
import { DataStore } from "aws-amplify";
import { Basket, BasketDish } from "../models";
import { useAuthContext } from "./AuthContext";
const BasketContext = createContext({});
const BasketContextProvider = ({ children }) => {
const { dbUser } = useAuthContext();
const [restaurant, setRestaurant] = useState(null);
const [basket, setBasket] = useState(null);
const [basketDishes, setBasketDishes] = useState([]);
const totalPrice = basketDishes.reduce(
(sum, basketDish) => sum + basketDish.quantity * basketDish.Dish.price,
restaurant?.deliveryFee
);
useEffect(() => {
DataStore.query(Basket, (b) =>
b.restaurantID("eq", restaurant.id).userID("eq", dbUser.id)
).then((baskets) => setBasket(baskets[0]));
}, [dbUser, restaurant]);
useEffect(() => {
if (basket) {
DataStore.query(BasketDish, (bd) => bd.basketID("eq", basket.id)).then(
setBasketDishes
);
}
}, [basket]);
const addDishToBasket = async (dish, quantity) => {
// get the existing basket or create a new one
let theBasket = basket || (await createNewBasket());
// create a BasketDish item and save to Datastore
const newDish = await DataStore.save(
new BasketDish({ quantity, Dish: dish, basketID: theBasket.id })
);
setBasketDishes([...basketDishes, newDish]);
};
const createNewBasket = async () => {
const newBasket = await DataStore.save(
new Basket({ userID: dbUser.id, restaurantID: restaurant.id })
);
setBasket(newBasket);
return newBasket;
};
return (
<BasketContext.Provider
value={{
addDishToBasket,
setRestaurant,
restaurant,
basket,
basketDishes,
totalPrice,
}}
>
{children}
</BasketContext.Provider>
);
};
export default BasketContextProvider;
export const useBasketContext = () => useContext(BasketContext);
please help me out. https://www.youtube.com/live/WFo_IxhBxF4?feature=share I'm doing ubereat by Vadim
I have a NextJS application that is using the ShopifyBuy SDK. I have been successfully able to implement a solution where I am able to fetch the products from Store and display them to the User. The user is also able to go to a product page and add the product to the cart.
However, when the user refreshes the page, the cart is reset, and the data does not persist. The code is below:
context/cart.js:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
import Cookies from "js-cookie";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState = {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const savedState = Cookies.get("cartState");
const [state, dispatch] = useReducer(reducer, savedState || initalState);
useEffect(() => {
Cookies.set("cartState", state, { expires: 7 });
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>
{children}
</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
products/[handle].tsx:
import React, { useState, useEffect } from "react";
import client from "../../lib/client";
import { useCartDispatch, useCartState } from "../../context/cart";
import Link from "next/link";
import cookie from "js-cookie";
export const getStaticPaths = async () => {
const res = await client.product.fetchAll();
const paths = res.map((product: any) => {
return {
params: { handle: product.handle.toString() },
};
});
return {
paths,
fallback: false,
};
};
export const getStaticProps = async (context: any) => {
const handle = context.params.handle;
const res = await client.product.fetchByHandle(handle);
const product = JSON.stringify(res);
return {
props: {
product,
},
};
};
function Product({ product }: any) {
const { state } = useCartState();
const { setCart } = useCartDispatch();
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
setCart(res);
};
product = JSON.parse(product);
return (
<div>
<div className=" flex-col text-2xl font-bold m-8 flex items-center justify-center ">
<h1>{product.title}</h1>
<button onClick={addToCart}>Add to Cart</button>
<Link href="/cart">Checkout</Link>
</div>
</div>
);
}
export default Product;
pages/cart/index.tsx:
import React, { useEffect } from "react";
import { useCartState, useCartDispatch } from "../../context/cart";
import client from "../../lib/client";
function Cart() {
const { state } = useCartState();
return (
<div>
<h1>Cart</h1>
{state.lineItems &&
state.lineItems.map((item: any) => {
return (
<div key={item.id}>
<h2>{item.title}</h2>
<p>{item.variant.title}</p>
<p>{item.quantity}</p>
</div>
);
})}
</div>
);
}
export default Cart;
I have tried using a library called js-cookie and also localStorage. I'm not sure where the problem lies or if the solutions that I've tried are wrong.
P.S.: I'm fairly new to NextJS and Typescript so go easy on the syntax. This code is for a personal project. Thanks in advance!
Answering this because I ended up coming up with a solution that works for me, at least.
Here it is:
const getCart = async () => {
try {
const checkoutId = Cookies.get("checkoutId");
let cart;
if (checkoutId) {
cart = await client.checkout.fetch(checkoutId);
} else {
cart = await client.checkout.create();
Cookies.set("checkoutId", cart.id);
}
setCart(cart);
} catch (err) {
console.log(err);
}
};
From my understanding, what this does is the following:
Check the cookies to see if one exists called "checkoutId"
If it exists, fetch the cart using that checkoutId
Otherwise, create a new cart and create a cookie using the cart.id that is returned in the response
Then, inside my individual Product page ([handle].tsx), I'm doing the following:
const addToCart = async () => {
const checkoutId = state.id;
const lineItemsToAdd = [
{
variantId: product.variants[0].id,
quantity: 1,
},
];
const res = await client.checkout.addLineItems(checkoutId, lineItemsToAdd);
console.log(res);
if (cookie.get("checkoutId") === undefined) {
cookie.set("checkoutId", res.id);
}
setCart(res);
};
Using cookies to store your object cart, as far as I know, is not a good idea. You could use localStorage, like so:
import { createContext, useContext, useEffect, useReducer } from "react";
import client from "../lib/client";
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const SET_CART = "SET_CART";
const initalState =
typeof localStorage !== "undefined" && localStorage.getItem("cartState")
? JSON.parse(localStorage.getItem("cartState"))
: {
lineItems: [],
totalPrice: 0,
webUrl: "",
id: "",
};
const reducer = (state, action) => {
switch (action.type) {
case SET_CART:
return { ...state, ...action.payload };
default:
throw new Error(`Unknown action: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initalState);
useEffect(() => {
localStorage.set("cartState", JSON.stringify(state));
}, [state]);
useEffect(() => {
getCart();
}, []);
const setCart = (payload) => dispatch({ type: SET_CART, payload });
const getCart = async () => {
try {
const cart = await client.checkout.create();
setCart(cart);
} catch (err) {
console.log(err);
}
};
return (
<CartDispatchContext.Provider value={{ setCart }}>
<CartStateContext.Provider value={{ state }}>{children}</CartStateContext.Provider>
</CartDispatchContext.Provider>
);
};
export const useCartState = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
I am building a react application and part of the application is a quiz section. At the end of the quiz there is a button which can save the user score in the quiz to the database.
This is my express route
// #route Put api/profile/saveScore/:id
// #desc Save users quiz score to profile
// #access Private
router.put('/saveScore/:topic_id', checkObjectId('topic_id'), auth, async (req, {params: {topic_id } }, res) => {
const score = req.body.score
const topic = topic_id
const newUserTopic = {
score,
topic,
}
try {
const profile = await Profile.findOne({ user: req.user.id });
profile.topics.unshift(newUserTopic);
await profile.save();
res.json(profile)
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
})
The express route works no bother in postman so thinking the issue must be more on the react side.
This is my action route
// Save Quiz Score to users profile
export const saveScore = (topicId, payload) => async (dispatch) => {
try {
const res = await api.put(`/profile/saveScore/${topicId}`, payload);
dispatch({
type: GET_PROFILE,
payload: res.data
});
dispatch(setAlert('Topic Saved', 'success'));
} catch (err) {
const errors = err.response.data.errors;
if(errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')))
}
dispatch({
type: PROFILE_ERROR,
payload: { msg: err.response.statusText, status: err.response.status }
});
}
};
This is my component
import React, { useEffect, useState, Fragment } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Spinner from '../layout/Spinner';
import QuizItem from './QuizItem';
import { getTopicById } from '../../actions/topic';
import { saveScore} from '../../actions/profile';
import { SaveScoreForm } from './SaveScoreForm';
const Quiz = ({ getTopicById, saveScore, topic: { topic, loading }, match }) => {
useEffect(() => {
getTopicById(match.params.id);
}, [getTopicById, match.params.id])
const [currentIndex, setCurrentIndex] = useState(0);
const [score, setScore] = useState(0);
const [showAnswers, setShowAnswers] = useState(false)
const [formData, setFormData] = useState({ score })
const handleAnswer = (answer) => {
if(!showAnswers) {
if(answer === topic[currentIndex].correct_answer) {
setScore(score + 1);
}
}
setShowAnswers(true);
};
const handleNextQuestion = () => {
setShowAnswers(false);
setCurrentIndex(currentIndex + 1);
}
console.log(currentIndex)
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value })
}
const onSubmit = (e) => {
e.preventDefault();
const payload = new FormData();
payload.append('score', formData.score)
saveScore(payload, match.params.id);
}
return topic.length > 0 ? (
<div className='container'>
{currentIndex >= topic.length ? (
<Fragment>
<SaveScoreForm topic={topic} score={score} />
<form
onSubmit={e => onSubmit(e)}
>
<input
type='hidden'
value={score}
onChange={(e) => onChange(e)}
/>
<input type='submit' className='btn btn-primary1 my-1' />
</form>
</Fragment>
) : (
<QuizItem
key={topic.question}
topic={topic[currentIndex]}
showAnswers={showAnswers}
handleNextQuestion={handleNextQuestion}
handleAnswer={handleAnswer}
/>
)}
</div>
) : (
<Spinner/>
)
}
Quiz.prototype = {
getTopicById: PropTypes.func.isRequired,
topic: PropTypes.object.isRequired
}
const mapStateToProps = state => ({
topic: state.topic,
showAnswers: state.showAnswers,
handleNextQuestion: state.handleNextQuestion,
handleAnswer: state.handleAnswer
})
export default connect(mapStateToProps, { getTopicById })(Quiz)
Child component
import React from 'react'
export const SaveScoreForm = ({ score, topic, }) => {
return (
<div>
<div className='bg-primary1 p-2 my-4'>
<h1 className='large'>Review Your Score</h1>
<p className="lead">Quiz ended! Your score is: {(score/topic.length) * 100}%</p>
<p>Save your score to your profile or take the quiz again!</p>
</div>
</div>
);
};
export default SaveScoreForm;
TypeError: saveScore is not a function
Any help or pointers in the right direction would be very much appreciated.
Thanks
You are importing import { saveScore} from '../../actions/profile';
But then you have this prop
const Quiz = ({ getTopicById, saveScore
// ----------------------------^
which is overriding saveScore in your components context. Unless you are passing a saveScore prop while initialising <Quiz> it'll be undefined.
If you want to import the saveScore module just remove this prop variable.
import React, { useEffect, useState } from "react";
import { dbService } from "./myFirebase";
function Editor({ userObj }) {
const [myContent, setMyContent] = useState("");
const [myContents, setMyContents] = useState([]);
const getValue = (event) => {
const { name, value } = event.target;
setMyContent({ ...myContent, [name]: value });
};
useEffect(() => {
console.log("1", myContents);
dbService.collection("contents").onSnapshot((snapshot) => {
const communityArray = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setMyContents(communityArray);
console.log("2", myContents);
});
console.log("3", myContents);
}, []);
console.log("4", myContents);
const addContent = async (event) => {
event.preventDefault();
await dbService.collection("contents").add({
content: myContent,
createdAt: Date.now(),
});
setMyContent("");
};
return (
<div>
<button type="submit" onClick={addContent}>
{" "}
input{" "}
</button>
</div>
);
}
export default Editor;
When data is entered in this way, I coded it to be saved and received in Firebase.
If I print the log, it comes out as an empty array and then the value is entered.
So when I get it, if I say console.log("10", myContents[0].id);, it is properly received, but when I refresh it, TypeError: Cannot read property'id' of undefined appears.
I think it's because of that empty array, how do I fix it?
Are you using async await for useEffect? I tried, but it was the same, was it wrong?