I need help with this issue, my app component as in the image below. I want to store track object inselectedTrack in the state using useState when I click on the view details button. Then use it to display track details in instead of making another fetch from API to get tack details, but when I use useContext inside give me this error TypeError: Cannot read property 'selectedTrack' of undefined.
React Components
import React from 'react';
import Header from './Header';
import Search from '../tracks/Search';
import Tracks from '../tracks/Tracks';
import Footer from './Footer';
import TrackContextProvider from '../../contexts/TrackContext';
const Main = () => {
return (
<div>
<TrackContextProvider>
<Header />
<Search />
<Tracks />
<Footer />
</TrackContextProvider>
</div>
);
};
export default Main;
TrackContext.js
import React, { createContext, useState, useEffect } from 'react';
export const TrackContext = createContext();
const TrackContextProvider = props => {
const [tracks, setTracks] = useState([]);
const [selectedTrack, setSelectedTrack] = useState([{}]);
const API_KEY = process.env.REACT_APP_MUSICXMATCH_KEY;
useEffect(() => {
fetch(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
)
.then(response => response.json())
.then(data => setTracks(data.message.body.track_list))
.catch(err => console.log(err));
// to disable the warning rule of missing dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// state for heading
const [heading, setHeading] = useState(['Top 10 Tracks']);
return (
<TrackContext.Provider value={{ tracks, heading, selectedTrack, setSelectedTrack }}>
{props.children}
</TrackContext.Provider>
);
};
export default TrackContextProvider;
import React, { Fragment, useContext } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';
const TrackDetails = () => {
const { selectedTrack } = useContext(TrackContext);
console.log(selectedTrack);
return (
<Fragment>
<Link to="/">
<button>Go Back</button>
</Link>
<div>
{selectedTrack === undefined ? (
<p>loading ...</p>
) : (
<h3>
{selectedTrack.track.track_name} by {selectedTrack.track.artist_name}
</h3>
)}
<p>lyrics.............</p>
<div>Album Id: </div>)
</div>
</Fragment>
);
};
export default TrackDetails;
import React, { useState, useContext, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { TrackContext } from '../../contexts/TrackContext';
const Track = ({ trackInfo }) => {
const { selectedTrack, setSelectedTrack } = useContext(TrackContext);
const handleClick = e => {
setSelectedTrack(trackInfo);
};
console.log(selectedTrack);
return (
<li>
<div>{trackInfo.track.artist_name}</div>
<div>Track: {trackInfo.track.track_name}</div>
<div>Album:{trackInfo.track.album_name}</div>
<div>Rating:{trackInfo.track.track_rating}</div>
<Link to={{ pathname: `/trackdetails/${trackInfo.track.track_id}`, param1: selectedTrack }}>
<button onClick={handleClick}>> View Lyric</button>
</Link>
</li>
);
};
export default Track;
UPDATE: adding Tracks component
import React, { useContext, Fragment } from 'react';
import Track from './Track';
import { TrackContext } from '../../contexts/TrackContext';
const Tracks = () => {
const { heading, tracks } = useContext(TrackContext);
const tracksList = tracks.map(trackInfo => {
return <Track trackInfo={trackInfo} key={trackInfo.track.track_id} />;
});
return (
<Fragment>
<p>{heading}</p>
{tracks.length ? <ul>{tracksList}</ul> : <p>loading...</p>}
</Fragment>
);
};
export default Tracks;
I think the issue here is that since the selectedTrack is loaded asynchronously, when it is accessed from the context, it is undefined (you can get around the TrackContext being undefined by passing in a default value in the createContext call). Since the selectedTrack variable is populated anychronously, you should store it in a Ref with useRef hook, and return that ref as part of the context value. That way you would get the latest value of selectedTrack from any consumer of that context.
const selectedTracks = useRef([]);
useEffect(() => {
fetch(
`https://cors-anywhere.herokuapp.com/https://api.musixmatch.com/ws/1.1/chart.tracks.get?chart_name=top&page=1&page_size=10&country=fr&f_has_lyrics=1&apikey=${API_KEY}`
)
.then(response => response.json())
.then(data => {
selectedTrack.current = data.message.body.track_list;
})
.catch(err => console.log(err));
// to disable the warning rule of missing dependency
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Related
I have react project generated by vite, I get this error when I add eventListener to the DOM. I also use React context API. But I think there might be a problem with the StateProvider.jsx that contains the context API but I'm not sure.
The error says:
Cannot update a component (`StateProvider`) while rendering a different component (`DeltaY`). To locate the bad setState() call inside `DeltaY`, follow the stack trace as described in ...
Here is the snapshot of the error in the console:
Here is the code:
main.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';
import { StateProvider } from './StateProvider.jsx';
import reducer, { initialState } from './reducer';
ReactDOM.createRoot(document.getElementById('root')).render(
<StateProvider initialState={initialState} reducer={reducer}>
<App />
</StateProvider>,
);
App.jsx
import './App.css';
import DeltaY from './DeltaY';
import { useStateValue } from './StateProvider';
function App() {
return (
<>
<DeltaY />
<Desc />
</>
);
}
const Desc = () => {
const [{ deltaY, scrollMode }, dispatch] = useStateValue();
return (
<>
<h1> deltaY: {deltaY} </h1>
<h1> scroll: {scrollMode} </h1>
</>
);
};
export default App;
DeltaY.jsx
import { useEffect, useState } from 'react';
import { useStateValue } from './StateProvider';
const DeltaY = () => {
// ------------------------------ context API ------------------------------- //
const [{ state }, dispatch] = useStateValue();
// ------------------------------ context API ------------------------------- //
const [scrollMode, setScrollMode] = useState(false);
const [deltaY, setDeltaY] = useState(0);
useEffect(() => {
function handleWheel(e) {
setDeltaY(e.deltaY);
setScrollMode(true);
}
window.addEventListener('wheel', handleWheel);
return () => window.removeEventListener('wheel', handleWheel);
}, []);
useEffect(() => {
setTimeout(() => {
setScrollMode(true);
}, 1000);
}, []);
// ------------------------------ dispatch ------------------------------- //
dispatch({
type: 'GET_DELTAY',
value: deltaY,
});
dispatch({
type: 'GET_SCROLLMODE',
value: scrollMode,
});
// ------------------------------ dispatch ------------------------------- //
return null;
};
export default DeltaY;
StateProvider.jsx
import React, { createContext, useContext, useReducer } from 'react';
// prepare the daya layer
export const StateContext = createContext();
// Wrap our app and provide the data layer
export const StateProvider = ({ reducer, initialState, children }) => {
return (
<>
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
</>
);
};
// PUll the information from the data layer
export const useStateValue = () => useContext(StateContext);
Any Idea how to fix it ? thankyou
Here is the solution:
instead of using it as a component, I use it as a custom Hook
Here is the code:
useDeltaY.jsx
import { useEffect, useState } from 'react';
const useDeltaY = () => {
const [scrollMode, setScrollMode] = useState(false);
const [deltaY, setDeltaY] = useState(0);
useEffect(() => {
function handleWheel(e) {
setDeltaY(e.deltaY);
setScrollMode(true);
}
window.addEventListener('wheel', handleWheel);
return () => window.removeEventListener('wheel', handleWheel);
}, []);
useEffect(() => {
setTimeout(() => {
setScrollMode(false);
}, 1000);
}, [scrollMode]);
return [deltaY, scrollMode];
};
export default useDeltaY;
App.jsx
import './App.css';
import useDeltaY from './useDeltaY';
function App() {
return (
<>
<Desc />
</>
);
}
const Desc = () => {
const [deltaY, scrollMode] = useDeltaY();
return (
<>
<h1> deltaY: {deltaY} </h1>
{scrollMode ? (
<h1> scrollMode: active </h1>
) : (
<h1> scrollMode: inActive </h1>
)}
</>
);
};
export default App;
I can get the data out correctly and see it in the console as I want it to display but mapping and getting it through is the issue I seem to fail on..
This is my code
import React, {useState, useEffect} from 'react'
import axios from 'axios';
import server from '../backend/server.jsx';
import Home from './Home';
function ListImage() {
const [api, setApi] = useState('');
const getAllData = () => {
axios
.get(`${server}/images`)
.then((response) => {
console.log(response.data);
setApi(response.data);
})
.catch((error) => {
console.log(error);
});
}
useEffect(() => {
getAllData();
},[]);
return (
<>
{api ?
api.map((item) => {
console.log(item);
return <Home data={item.data} />;
}): <p>Not found</p>}
</>
)
}
export default ListImage
And then passing to this where i only get undefined in console
import React from 'react'
function Home({ data }) {
console.log(data)
return (
<>
<p>{data}</p>
</>
)
}
export default Home
Try with this. I think your map function gets called when the initial render. Make sure the response. data should return an array.
ps: You render your home component in an array. That's wrong. Pass the data to the component and map your array on the home component.
import React, {useState, useEffect} from 'react'
import axios from 'axios';
import server from '../backend/server.jsx';
import Home from './Home';
function ListImage() {
const [data, setData] = useState(null);
const getAllData = () => {
axios
.get(`${server}/images`)
.then((response) => {
console.log(response.data);
setData(response.data);
})
.catch((error) => {
console.log(error);
});
}
useEffect(() => {
getAllData();
},[]);
return (
<>
{api ? <Home data={data} /> : <p>Loading...</p>}
</>
)
}
export default ListImage
I am have been working on a little project to better understand react. I recently converted it to use hooks and I am trying to implement redux, with it. However I get the following error now.
TypeError: searchField.toLowerCase is not a function
looking at the docs, I stopped using connect from react-redux and switched to using useDispatch and useSelector. But I believe I have set up everything correctly but not sure as to why this error being raise.
This is my action.js
import { SEARCH_EVENT } from './searchfield_constants';
export const setSearchField = (payload) => ({ type: SEARCH_EVENT, payload });
This is my reducer
import { SEARCH_EVENT } from './searchfield_constants';
const initialState = {
searchField: '',
};
export const searchRobots = (state = initialState, action = {}) => {
switch (action.type) {
case SEARCH_EVENT:
return { ...state, searchField: action.payload };
default:
return state;
}
};
this is my index.js where I am using the Provider from react-redux
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { searchRobots } from './searchfield/searchfield_reducers';
import './styles/index.css';
import App from './App';
const store = createStore(searchRobots);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
finally here is my App.jsx
import { useState, useEffect, useCallback } from 'react';
import { setSearchField } from './searchfield/searchfield_actions';
import { useDispatch, useSelector } from 'react-redux';
import axios from 'axios';
import React from 'react';
import CardList from './components/CardList';
import SearchBox from './components/SearchBox';
import Scroll from './components/Scroll';
import Error from './components/Error';
import 'tachyons';
import './styles/App.css';
// const mapStateToProps = (state) => ({
// searchField: state.searchField,
// });
// const mapDispatchToProps = (dispatch) => ({
// onSearchChange: (e) => dispatch(setSearchField(e.target.value)),
// });
const App = () => {
const searchField = useSelector(state => state.searchField)
const dispatch = useDispatch();
const [robots, setRobots] = useState([]);
// const [searchField, setSearchField] = useState('');
const fetchUsers = useCallback(async () => {
try {
const result = await axios('//jsonplaceholder.typicode.com/users');
setRobots(result.data);
} catch (error) {
console.log(error);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
fetchUsers();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const filteredRobots = robots.filter((robot) => {
return robot.name.toLowerCase().includes(searchField.toLowerCase());
});
return !robots.length ? (
<h1 className='f1 tc'>Loading...</h1>
) : (
<div className='App tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={dispatch(setSearchField(e => e.target.value))} />
<Scroll>
<Error>
<CardList robots={filteredRobots} />
</Error>
</Scroll>
</div>
);
};
export default App;
what am I doing wrong?
So the solution was the following,
I created a function called on searchChange, which calls dispatch and then the setSearchField which uses the e.target.value as the payload.
const onSearchChange = (e) => {
dispatch(setSearchField(e.target.value));
};
so the final return looks like the following
return !robots.length ? (
<h1 className='f1 tc'>Loading...</h1>
) : (
<div className='App tc'>
<h1 className='f1'>RoboFriends</h1>
<SearchBox searchChange={onSearchChange} />
<Scroll>
<Error>
<CardList robots={filteredRobots} />
</Error>
</Scroll>
</div>
);
};
In you App.js, convert this line
const searchField = useSelector(state => state.searchField)
to
const { searchField } = useSelector(state => state.searchField)
basically de-structure out searchField from state.searchField
This is attributed to the fact how redux sets state.
In your reducer searchRobots the initial state provided by redux will be
state = {
...state,
searchField
}
and in this line return { ...state, searchField: action.payload };, you're adding
another property searchField to state.searchField object so you'll need to de-structure it out.
It looks like your searchField value is getting set to undefined or some non-string value.
I found this line to be incorrect
<SearchBox searchChange={dispatch(setSearchField(e => e.target.value))} />
It should be changed to
<SearchBox searchChange={() => dispatch(setSearchField(e => e.target.value))} />
So that on search change this function can be called. Currently you are directly calling dispatch and this may be setting your searchField to undefined
Also for safer side before using toLowerCase() convert it to string ie searchField.toString().toLowerCase()
In my Main.js I create a first global state with a username and a list of users I'm following.
Then, both the Wall component and FollowingSidebar render the list of follows and their messages (plus the messages of the main user).
So far so good. But in a nested component inside FollowingSidebar called FollowingUser I have an onClick to remove a user. My understanding is that, because I change the state, useEffect would take care of the Wall component to re-render it, but nothing happens... I've checked several examples online but nothing has helped my use case so far.
Needless to say I'm not overly experienced with React and Hooks are a bit complex.
The code here:
Main.js:
import React, { useEffect, useState } from "react";
import ReactDom from "react-dom";
import db from "./firebase.js";
// Components
import Header from "./components/Header";
import FollowingSidebar from "./components/FollowingSidebar";
import SearchUsers from "./components/SearchUsers";
import NewMessageTextarea from "./components/NewMessageTextarea";
import Wall from "./components/Wall";
// Context
import StateContext from "./StateContext";
function Main() {
const [mainUser] = useState("uid_MainUser");
const [follows, setFollows] = useState([]);
const setInitialFollows = async () => {
let tempFollows = [mainUser];
const user = await db.collection("users").doc(mainUser).get();
user.data().following.forEach(follow => {
tempFollows.push(follow);
});
setFollows(tempFollows);
};
useEffect(() => {
setInitialFollows();
}, []);
const globalValues = {
mainUserId: mainUser,
followingUsers: follows
};
return (
<StateContext.Provider value={globalValues}>
<Header />
<FollowingSidebar />
<SearchUsers />
<NewMessageTextarea />
<Wall />
</StateContext.Provider>
);
}
ReactDom.render(<Main />, document.getElementById("app"));
if (module.hot) {
module.hot.accept();
}
FollowingSidebar component:
import React, { useState, useEffect, useContext } from "react";
import db from "../firebase.js";
import StateContext from "../StateContext";
import FollowingUser from "./FollowingUser";
export default function FollowingSidebar() {
const { followingUsers } = useContext(StateContext);
const [users, setUsers] = useState(followingUsers);
useEffect(() => {
const readyToRender = Object.values(followingUsers).length > 0;
if (readyToRender) {
db.collection("users")
.where("uid", "in", followingUsers)
.get()
.then(users => {
setUsers(users.docs.map(user => user.data()));
});
}
}, [followingUsers]);
return (
<section id="following">
<div className="window">
<h1 className="window__title">People you follow</h1>
<div className="window__content">
{users.map((user, index) => (
<FollowingUser avatar={user.avatar} username={user.username} uid={user.uid} key={index} />
))}
</div>
</div>
</section>
);
}
FollowingUser component:
import React, { useState, useContext } from "react";
import db from "../firebase.js";
import firebase from "firebase";
import StateContext from "../StateContext";
export default function FollowingUser({ avatar, username, uid }) {
const { mainUserId, followingUsers } = useContext(StateContext);
const [follows, setFollows] = useState(followingUsers);
const removeFollow = e => {
const userElement = e.parentElement;
const userToUnfollow = userElement.getAttribute("data-uid");
db.collection("users")
.doc(mainUserId)
.update({
following: firebase.firestore.FieldValue.arrayRemove(userToUnfollow)
})
.then(() => {
const newFollows = follows.filter(follow => follow !== userToUnfollow);
setFollows(newFollows);
});
userElement.remove();
};
return (
<article data-uid={uid} className="following-user">
<figure className="following-user__avatar">
<img src={avatar} alt="Profile picture" />
</figure>
<h2 className="following-user__username">{username}</h2>
<button>View messages</button>
{uid == mainUserId ? "" : <button onClick={e => removeFollow(e.target)}>Unfollow</button>}
</article>
);
}
Wall component:
import React, { useState, useEffect, useContext } from "react";
import db from "../firebase.js";
import Post from "./Post";
import StateContext from "../StateContext";
export default function Wall() {
const { followingUsers } = useContext(StateContext);
const [posts, setPosts] = useState([]);
useEffect(() => {
console.log(followingUsers);
const readyToRender = Object.values(followingUsers).length > 0;
if (readyToRender) {
db.collection("posts")
.where("user_id", "in", followingUsers)
.orderBy("timestamp", "desc")
.get()
.then(posts => setPosts(posts.docs.map(post => post.data())));
}
}, [followingUsers]);
return (
<section id="wall">
<div className="window">
<h1 className="window__title">Latest messages</h1>
<div className="window__content">
{posts.map((post, index) => (
<Post avatar={post.user_avatar} username={post.username} uid={post.user_id} body={post.body} timestamp={post.timestamp.toDate().toDateString()} key={index} />
))}
</div>
</div>
</section>
);
}
StateContext.js:
import { createContext } from "react";
const StateContext = createContext();
export default StateContext;
The main issue is the setting of state variables in the Main.js file (This data should actually be part of the Context to handle state globally).
Below code would not update our state globally.
const globalValues = {
mainUserId: mainUser,
followingUsers: follows
};
We have to write state in a way that it get's modified on the Global Context level.
So within your Main.js set state like below:
const [globalValues, setGlobalValues] = useState({
mainUserId: "uid_MainUser",
followingUsers: []
});
Also add all your event handlers in the Context Level in Main.js only to avoid prop-drilling and for better working.
CODESAND BOX DEMO: https://codesandbox.io/s/context-api-and-rendereing-issue-uducc
Code Snippet Demo:
import React, { useEffect, useState } from "react";
import FollowingSidebar from "./FollowingSidebar";
import StateContext from "./StateContext";
const url = "https://jsonplaceholder.typicode.com/users";
function App() {
const [globalValues, setGlobalValues] = useState({
mainUserId: "uid_MainUser",
followingUsers: []
});
const getUsers = async (url) => {
const response = await fetch(url);
const data = await response.json();
setGlobalValues({
...globalValues,
followingUsers: data
});
};
// Acts similar to componentDidMount now :) Called only initially
useEffect(() => {
getUsers();
}, []);
const handleClick = (id) => {
console.log(id);
const updatedFollowingUsers = globalValues.followingUsers.filter(
(user) => user.id !== id
);
setGlobalValues({
...globalValues,
followingUsers: updatedFollowingUsers
});
};
return (
<StateContext.Provider value={{ globalValues, handleClick }}>
<FollowingSidebar />
</StateContext.Provider>
);
}
export default App;
I built a simple react app that fetch the users from database like mysql
After fetching data I want to pass data to child component and it works sometimes but when refresh page it throw error like data.map is not function
here error message image
my code of parent component
import React,{useEffect, useState ,Context, createContext} from 'react'
import Sidenav from '../components/Sidenav'
import {makeStyles} from '#material-ui/core/styles';
import Content from '../components/ContentParent'
import RightSideNav from '../components/RightSideNav'
import Backdrop from '#material-ui/core/Backdrop';
import Async from 'react-async';
import CircularProgress from '#material-ui/core/CircularProgress';
import { get } from 'js-cookie';
const bodyData = createContext()
const useStyles = makeStyles((theme) => ({
HomeDataContainer_parent:{
backgroundColor:'#F2F2F2',
display:'flex',
},
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: '#fff',
},
}))
function HomeDataContainer() {
const [data,setData] = useState('')
const [isload,setload] = useState(true)
useEffect(() =>{
async function get(){
fetch('/postsDrawer').then(async data => await data.json()).then(result =>{ setData(result)
setload(false)
})
}
get()
},[data])
const classes = useStyles()
return (
<div className = {classes.HomeDataContainer_parent}>
<Backdrop className={classes.backdrop} open={isload} >
<CircularProgress color="inherit" />
</Backdrop>
{/* sidenav */}
<Sidenav/>
{/* content */}
if(data){
<Content value = {data} />
}
{/* Right side updates */}
<RightSideNav />
</div>
)
}
export default HomeDataContainer
export { bodyData }
child component
import React from 'react'
import {makeStyles} from '#material-ui/core/styles'
import Avatar from '#material-ui/core/Avatar';
import ArrowDropDownTwoToneIcon from '#material-ui/icons/ArrowDropDownTwoTone';
import ArrowDropUpTwoToneIcon from '#material-ui/icons/ArrowDropUpTwoTone';
import LocalOfferTwoToneIcon from '#material-ui/icons/LocalOfferTwoTone';
import Chip from '#material-ui/core/Chip';
import QuestionAnswerIcon from '#material-ui/icons/QuestionAnswer';
import VisibilityIcon from '#material-ui/icons/Visibility';
import Logo from '../images/action.png'
import { BodyData } from '../components/HomeDataContainer'
export default function HomeRecentQuestion(props) {
const classes = useStyles()
const data = props.value
let count = 0
return (
data.map(content => {
let count = content.tags.split(',')
return (
<h1>{content.id}</h1>
)
})
)
}
how to resolve this
1:
Change your data state to this:
const [data,setData] = useState([])
Change useEffect to this:
useEffect(() =>{
if(!data.length)
fetch('/postsDrawer').then(res=> res.json())
.then(result => {
setData(result)
setload(false)
})
},[data])
Change your component to this:
<HomeRecentQuestion value={data} />
You can try using below on the child component.
data && data.map(content => {
let count = content.tags.split(',')
return (
<h1>{content.id}</h1>
)
})
you will change it state of types when you created
like these: const [data, setData] = useState([]);
just you can it than problem will close it.