Next.js Cannot read property 'xx' of undefined - reactjs

I am trying to create a simple quiz app, using react context and multistep form. Here is my code handling form data
import { useState, createContext, useContext } from "react";
export const QuizContext = createContext();
export default function QuizProvider({ children }) {
const [data, setData] = useState({});
const setQuizValues = (values) => {
setData((prevValues) => ({
...prevValues,
...values,
}));
};
return (
<QuizContext.Provider value={{ data, setQuizValues }}>
{children}
</QuizContext.Provider>
);
}
export const useQuizData = () => useContext(QuizContext);
Now, going into my first step component, I get a TypeError: Cannot read property 'setQuizValues' of undefined:
import { Card } from "../../stories/Card";
import { useQuizData } from "../../context/index"
export const TacoCathegories = ({quizStep, prevQuizStep, nextQuizStep}) => {
const { setQuizValues } = useQuizData();
const handleSubmit = (values) => {
setQuizValues(values);
prevQuizStep();
nextQuizStep();
};
return (
<div className="max-w-7xl mx-auto py-24 px-4 bg-white sm:px-6 lg:px-8">
<div className="mt-24 space-y-12 lg:space-y-0 lg:grid lg:grid-cols-3 lg:gap-x-8">
<Card />
</div>
</div>
);
}
I am actually going through the same steps I went through for a similar project.

It looks like the useQuizData hook is returning undefined. This would be the case if you haven't included a provider for the quiz context above the component calling this hook, because the default value for the context is undefined. Try wrapping your component tree inside <QuizProvider> ... </QuizProvider>.

Related

search filter in React giving error while trying to use state hooks

I am try to add search feature to an existing lists of robot names.
In order to do so I am trying to useState hooks. I have an App component and Header component which has the input tag for search field.
Error I am getting is 'InputEvent' is assigned a value but never used.
Below is the code for App component (main component).
import "./App.css";
import Header from "./Header";
import Robo from "./Robo";
import { robots } from "./robots";
import { useState } from "react";
function App() {
const [query, setQuery] = useState("");
const InputEvent = (e) => {
const data = e.target.value;
setQuery(data);
const extraction = robots
.filter((curElem, index) =>
robots[index].name.toLowerCase().includes(query)
)
.map((curElem, index) => {
return (
<Robo
key={robots[index].id}
id={robots[index].id}
name={robots[index].name}
email={robots[index].email}
/>
);
});
return (
<div className="App">
<Header query={query} InputEvent={InputEvent} />
<div className="robo-friends-container">{extraction};</div>
</div>
);
};
}
export default App;
Child component
import React from "react";
import "./header.css";
const Header = ({ query, InputEvent }) => {
return (
<>
<div className="headerText">ROBO FRIENDS</div>
<div>
<input
type="text"
id="lname"
name="lname"
placeholder="Search"
value={query}
onChange={InputEvent}
/>
</div>
</>
);
};
export default Header;
Here is my answer in stackblitz app
https://stackblitz.com/edit/stackoverflow-robots-filter?file=App.tsx,Robo.tsx,Header.tsx,robots.ts
I have altered the code a bit.. you can fork the project and play with it..
You can add debounce option to your input, which prevents unwanted re-renders
Adding the changes:
function App() {
const [query, setQuery] = useState(undefined);
const [filteredRobots, setFilteredRobots] = useState([]);
useEffect(() => {
console.log(query);
const filteredRobots = robots.filter((robot) => {
return robot.name.includes(query);
});
if (filteredRobots.length) {
setFilteredRobots(filteredRobots);
}
}, [query]);
const onQueryChange = (e) => {
const data = e.target.value;
setQuery(data);
};
const renderRobots = () => {
if (!query || !query.length) {
return <p>{'Search to find Robots'}</p>;
}
if (filteredRobots && filteredRobots.length && query && query.length) {
return filteredRobots.map((filteredRobot) => (
<Robo
key={filteredRobot.id} //id is unique key in your data
name={filteredRobot.name}
id={filteredRobot.id}
email={filteredRobot.email}
/>
));
}
return <p>{'No Robots Found'}</p>;
};
return (
<div className="App">
<Header query={query} InputEvent={onQueryChange} />
{renderRobots()}
</div>
);
}
Problems in your code:
Const InputChange is a function that can be used as prop for any React component .. but you have added InputChange inside the InputChange named function itself which is incorrect
Extraction is a jsx variable which is created from Array.filter.. on each item, filter passes a item[index] to the filter function.. you dont want to do robots[index].name.toLowerCase().includes(query).. instead you could have done curElem.name.toLowerCase().includes(query) and same applies for Array.map

Problem when rendering image in React component

I´m getting an error when trying to render an image in component.
I paste the code here.
Is it possible that I need a babel or webpack plugin?
In this component, the image rendering works fine:
import React from "react";
function ProductItem({ product }) {
return product ? (
<div>
<div>
<img src={product.images[0]} alt={product.title} />
</div>
<div>
{product.title}
<br />
${product.price}
</div>
<p>{product.description}</p>
</div>
) : <p>Loading Product... </p>;
};
export default ProductItem;
In this other component is where I have the problem.
ProductDetail.js
import React from "react";
import useGetProducts from "../hooks/useGetProducts";
const API = 'https://api.escuelajs.co/api/v1/products';
function ProductDetail() {
const data = useGetProducts(`${API}/6`);
return (
<>
{data.products
?
<>
<h3>{data.products.title}</h3>
<p>{data.products.description}</p>
<div>
<img src={data.products.images[0]} alt="title" />
</div>
</>
: <h4>Loading...</h4>
}
</>
);
}
export default ProductDetail;
Custom Hook with useEffect, the useGetProducts function is responsible for bringing the data from the API with the Axios library
import { useEffect, useState } from "react";
import axios from "axios";
const useGetProducts = (API) => {
const [products, setProducts] = useState([])
const [error, setError] = useState("");
const [loaded, setLoaded] = useState(false);
useEffect(() => {
(async () => {
try {
const response = await axios(API);
setProducts(response.data);
} catch (error) {
setError(error.message);
} finally {
setLoaded(true);
}
})();
}, []);
return { products, error, loaded };
};
export default useGetProducts
Your default state for products is [], so the conditional render data.products in ProductDetail.js always return true so you can change default state for products is null
const [products, setProducts] = useState(null);
The first answer is correct, so I will not duplicate it, but I see room for improvement in your code/example.
Your useGetProducts hook is very easy to break and hard to reuse. If you will pass the wrong URL or the structure of the API will change it will break your code. Also, the hook is not very generic, cause you will need to create similar fn for each entity. My suggestion. Use react-query and separate functions for calling API. So it will look like this.
import { useQuery } from 'react-query'
import axios from 'axios'
export default function ProductPage() {
const productResponse = useQuery('exchanges', () => getProduct('6'))
const { isLoading, isError, data: product } = productResponse
return (
<div>
{isLoading && <div>Loading...</div>}
{isError && <div>Something went wrong :(</div>}
{product && (
<div>
<h1>Product title: {product.title}</h1>
<p>
{product.images.map(imageSrc => (
<img key={imageSrc} src={imageSrc} alt="" />
))}
</p>
</div>
)}
</div>
)
}
interface Product {
id: string
title: string
images: string[]
}
function getProduct(id: string): Promise<Product> {
return axios
.get(`https://api.escuelajs.co/api/v1/products/${id}`)
.then(r => r.data)
}
PS. react-query requires additional configuration ( context provider, config, etc ). Please look into docs on how to use it.

useContext causing blank screen

The app uses useContext for state management and axios for a get request to an API to receive data. Originally I was not using useContext but later realized state will be needed in multiple components later down the road and props would be messy. The app was working perfectly prior to using useContext now I am receiving a blank screen and no error messages.
ThemeContext.js
import {useState, useEffect, createContext} from 'react'
import axios from 'axios'
const ThemeContext = createContext()
const ThemeContextProvider = props => {
const [students, setStudents] = useState([])
const [loading, setLoading] = useState(false)
useEffect(()=>{
getStudents()
},[])
const getStudents = async () => {
try {
const res = await axios.get('https://api.hatchways.io/assessment/students')
setStudents(res.data.students)
setLoading(true)
}
catch (err) {
console.log(err.message)
}
}
return (
<ThemeContextProvider.Provider value={{students, loading}}>
{props.children}
</ThemeContextProvider.Provider>
)
}
export {ThemeContextProvider, ThemeContext}
Students.js
import {useContext} from 'react'
import {ThemeContext} from './themeContext'
const Students = props => {
const {students, loading} = useContext(ThemeContext)
return (
<div>
{loading &&
students.map((student) =>(
<div className="student-profile-container">
<div className="student-profile-image">
<img key={student.id} src={student.pic} alt="student profile avatar"/>
</div>
<div className="student-profile-info">
<h1 className="student student-name">{student.firstName} {student.lastName}</h1>
<p className="student student-info">Email: {student.email}</p>
<p lassName="student student-info">Company: {student.company}</p>
<p className="student student-info">Skill: {student.skill}</p>
<p className="student student-info">Average: {student.average}%</p>
</div>
</div>
))
}
</div>
);
}
export default Students;
It appears you are mixing up ThemeContext and ThemeContextProvider. Changing the return value of ThemeContextProvider should fix your issue.
<ThemeContext.Provider value={{students, loading}}>
{props.children}
</ThemeContext.Provider>

passing state in react with useState

I'm new to react and trying to learn by building this simple app. I want to expose data from parent app in a child component but my result in the component appears to be empty.
what should I do to get the result to appear and re-render in the child component?
import React, { useState, useEffect } from "react";
import socketIOClient from "socket.io-client";
import Main from "./Main";
import Response from "./response"
const ENDPOINT = "http://127.0.0.1:4001";
function App() {
const [response, setResponse] = useState([]);
useEffect(() => {
const socket = socketIOClient(ENDPOINT);
socket.on("tick", (data) => {
setResponse(data);
});
}, []);
console.log(response);
return (
<>
<div style={{ textAlign: "center" }}>
<Main
responseCurrency = {response.map(memb => <div>{memb.currencyPairName}</div>)}
dataObject = {response}
/>
</div>
</>
);
}
export default App;
import './Main.css';
function Main(props) {
const [result, setResult] = React.useState(props.dataObject);
React.useEffect(() => {
console.log(result)
setResult(result);
}, [result]);
return <div className="container">
{result[0]}
<div id="palceholder1" className="placeholder">hello</div>
<div id="palceholder2" className="placeholder"></div>
<div id="palceholder3" className="placeholder"></div>
</div>;
}
export default Main;
I am a beginner in ReactJS, so I'm lost at the moment.
Hope someone could shed a light..
Thanks in advance
Gali
function Main(props) {
const [result, setResult] = React.useState(props.dataObject);
The problem is that you're copying props into state. The above line of code says to create a new state for Main, who's initial value is props.dataObject. When props.dataObject changes later, nothing will happen to this state.
Instead, you should use the prop directly:
function Main(props) {
return (
<div className="container">
{props.dataObject[0]}
<div id="palceholder1" className="placeholder">
hello
</div>
<div id="palceholder2" className="placeholder"></div>
<div id="palceholder3" className="placeholder"></div>
</div>
);
}

React Component State and API fetch

I have been studying React for past few days. In my blog project, I am using Axios to get data from API. Here is my component:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState({});
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
Above code works fine though I noticed the first time it tries to render the component with empty ({}) post object (Due to the default value in 'useState'). However, it causes issues in my child component cause it is directly using 'post' object properties. For example: 'post.content'. Here is my 'SinglePost' component's code:
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>{post.content}</div>
);
};
It returns undefined error for {post.content} object. To resolve the issue I had to use something like {post && <SinglePost post={post} />}, But it doesn't feel right. Is there any better way to handle such scenarios.
Consider revising the PostContent component's rendering logic to account for the case where no post data is present during the network request.
You could for instance initialise your post state to null, and then update the rendered result to prevent the SinglePost component from being rendered while post is null.
Once the network request is completed and the post state is set, the component will re-render causing SinglePost to be rendered with the non-null post state:
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState(null); /* Update: "Initial data" set to null */
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
{ /* <React.Fragment> Not needed */ }
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{ /* Update: If post is null, assume network request is
still in progress so rendering loading message instead
of single post component */ }
{ post === null ? <p>Loading</p> : <SinglePost post={post} /> }
</div>
</div>
</div>
);
};
export default PostContent;
This approach is generally the simplest and more common pattern to async requests and rendering.
There are some other approaches that you might want to consider such as this declarative approach to data fetching, or the use of Reacts suspense feature for asynchronous rendering
You need to make post's initial value an array:
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [post, setPost] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPost(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
<SinglePost post={post} />
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;
and in single post map through the array
const SinglePost = props => {
const { post } = props;
console.log(post);
return (
<div>
{post.map((post, key) => (
<div key={key}>{post.content}</div>
))}
</div>
);
};
You can do something like
import React, { useState, useEffect } from "react";
import axios from "axios";
import { apiConstants } from "../../constants";
import SinglePost from "./SinglePost";
const PostContent = props => {
const {
match: { params }
} = props;
const [posts, setPosts] = useState([]);
useEffect(() => {
axios
.get(apiConstants.singlePost + `${params.post_slug}`)
.then(function(response) {
setPosts(response.data);
})
.finally(function() {
// always executed
});
}, []);
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-lg-8 col-md-10 mx-auto">
{this.state.posts.map(post => (<SinglePost post={post} key={post.id} />))
</div>
</div>
</div>
</React.Fragment>
);
};
export default PostContent;

Resources