How to change th value of a state property? - javascript

I am making a small application that obtains data, is displayed in the DOM, and chooses an item that displays the information of the chosen user, I handle all this through the state manager called UserState, where I also add the methods to display the users. And then as a component, I have UserList and UserProfile.
This is how should work, Capture 1
UserState.js
import React, {useState} from 'react';
import UserContext from './UserContext';
import axios from 'axios';
function UserState(props) {
const initialState = {
users:[],
selectedUser:null
}
const [state, setState] = useState(initialState)
const getUsers = async() =>{
const res = await axios.get("https://reqres.in/api/users")
const data = res.data.data
setState({users:data,
selectedUser:null})
}
const getProfile = async (id) => {
const res = await axios.get("https://reqres.in/api/users/"+id)
const {data} = await res.data;
console.log('Item Selected:',data)
console.log(setState({selectedUser:data}))
}
return (
<UserContext.Provider
value={{
users:state.users,
selectedUser: state.selectedUser,
getUsers,
getProfile
}}
>
{props.children}
</UserContext.Provider>
)
}
export default UserState
I export That state and its methods through the Hook useContext, the problem starts when I try to choose a user, and the console shows me the following error.
UserList.js
import React,{useContext,useEffect} from 'react'
import UserContext from "../context/User/UserContext"
function UserList(props) {
const userContext = useContext(UserContext)
useEffect(() => {
userContext.getUsers();
},[])
return (
<div>
<h1>UserList</h1>
{userContext.users.map(user=>{
return(
<a
key={user.id}
href="#!"
onClick={()=> userContext.getProfile(user.id)}
>
<img src={user.avatar} alt="" width="70"/>
<p>{user.first_name} {user.last_name}</p>
<p>{user.email}</p>
</a>)
}): null}
</div>
)
}
export default UserList
Profile.js
import React,{useContext} from 'react'
import UserContext from '../context/User/UserContext'
function Profile() {
const {selectedUser} = useContext(UserContext)
return (
<>
<h1>Profile</h1>
{selectedUser ?
(
<div>
<h1>Selected Profile</h1>
{/* <img
src={selectedUser.avatar}
alt=""
style={{width:150}}
/> */}
</div>
):(<div>
No User Selected
</div>)}
</>
)
}
export default Profile
Console Error
I tried to change the value of selectedUser but every time the console shows me that error.

In your getProfile function, you should use setState like that.
setState({...state, selectedUser:data })
If you use setState({selectedUser:data }) then users is removed from state.

It looks like it's an issue with the asynchronous portion of your code. Initially, you have no state.users object, so when you attempt to use the properties of the state.users object in the line like {userContext.users.map(user=>{... there is nothing to map, and since map uses the length property, you are getting that error. You should check first to see if that component has a userContext.users property and that the length is greater than or equal to 1 before attempting to map.
You're using the useState hook in a slightly odd way too, which confuses things a bit. Typically when using the useState hook, each element will have its own state rather than setting a single state to handle multiple elements. In this one, you'd set two separate states, one called users and one called selectedUser and set them independently. Otherwise you can have some odd re-renders.
By the way, React error codes are very descriptive. It tells you that state.users is undefined, that it can't access property map of undefined, and that it's on line 13 of your UserList.js component. All of which is true.

Related

How to pass state/data from one component to another in React.js (riot api specifically)

I am trying to pull information from one component's API call to then use that data in another API call in a separate component. However, I am unsure how to export and use the data from the first API call in the second component.
App.js
import './App.css';
import FetchMatch from './fetch-match/fetch.match';
import FetchPlayer from './fetch-player/fetch.player';
function App() {
return (
<div className="App">
<h1>Hello world</h1>
<FetchPlayer></FetchPlayer>
<FetchMatch></FetchMatch>
</div>
);
}
export default App;
fetch.player then makes the first API call to get a users specific ID which will be used in the second API call too fetch that users match history.
fetch.player.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const FetchPlayer = () => {
const [playerData, setPlayerData] = useState([]);
const userName = 'users name';
const userTagLine = '1234';
const apiKey = '???';
useEffect( () => {
axios.get(`https://americas.api.riotgames.com/riot/account/v1/accounts/by-riot-id/${userName}/${userTagLine}?api_key=${apiKey}`)
.then(response => {
console.log(response.data)
setPlayerData([response.data])
})
.catch(error => console.log(error))
}, []);
return (
<div>
{playerData.map( data => (
<div>
<p>{data.puuid}</p>
<p>{data.gameName}#{data.tagLine}</p>
</div>
))}
</div>
)
}
export default FetchPlayer;
not much here but just in case...
fetch.match.js
import React, { useState } from 'react';
// Somehow take in the puuid set in the state of fetch.player to make a second API call below
const FetchMatch = () => {
const [matchData, setMatchData] = useState([]);
return (
<div>
// players match list goes here
</div>
)
}
export default FetchMatch;
I am unsure if I should make a separate function instead which would allow me to create consts to handle both API calls in a single file. Or if there is a way to pass the state from fetch.player as a prop to fetch.match from App.js. I have tried to do the former but it either doesn't work or I am messing up the syntax (most likely this)
If you render both component parallelly in a parent component, they are called sibling components.
Data sharing in sibling components can be done by multiple ways (Redux, Context etc) but the easiest and simplest way (the most basic way without 3rd party API) involves the use of parent as a middle component.
First you create the state in the parent component and provide it as props to the child component which need the data from its sibling (in your case is FetchMatch).
import React from 'react';
import './App.css';
import FetchMatch from './fetch-match/fetch.match';
import FetchPlayer from './fetch-player/fetch.player';
function App() {
const [data,setData] = React.useState();
return (
<div className="App">
<h1>Hello world</h1>
<FetchPlayer></FetchPlayer>
<FetchMatch data={data} ></FetchMatch>
</div>
);
}
export default App;
Provide the function to setData as a props to the child component which will fetch the initial API (in your case is FetchPlayer)
<FetchPlayer onPlayerLoad={(data) => setData(data)} />
Then, in that child component when you finish calling the API and get the result, pass that result to the onPlayerLoad function which will call the setData function with the result as parameters. It will lead to state change and re-rendering of the second FetchMatch component feeding the props data with API results.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const FetchPlayer = ({onPlayerLoad}) => {
const [playerData, setPlayerData] = useState([]);
const userName = 'users name';
const userTagLine = '1234';
const apiKey = '???';
useEffect( () => {
axios.get(`https://americas.api.riotgames.com/riot/account/v1/accounts/by-riot-id/${userName}/${userTagLine}?api_key=${apiKey}`)
.then(response => {
console.log(response.data)
setPlayerData([response.data])
onPlayerLoad(response.data)
})
.catch(error => console.log(error))
}, []);
return <></>;
Coming to FetchMatch, you will have the data in its second rendering.
import React, { useState } from 'react';
// Somehow take in the puuid set in the state of fetch.player to make a second API call below
const FetchMatch = ({data}) => {
const [matchData, setMatchData] = useState([]);
//console.log(data);
return (
<div>
// players match list goes here
</div>
)
}
export default FetchMatch;
Now, you can do whatever you want with the shared data in second component which in your case is trigger match API. 🎉

How Do you display data from an api in React?

Weather.JS File
import { useEffect, useState } from "react"
import axios from 'axios'
import WeatherDisplay from './WeatherDisplay'
const Weather = ({capital, params}) => {
const [weather,setWeather] = useState([])
useEffect(async () => {
const result = await axios.get('http://api.weatherstack.com/current', {params})
console.log(result.data)
setWeather(result.data)
},
[params])
return(
<div>
<h2>Weather in {capital}</h2>
<WeatherDisplay current={weather.current}/>
</div>
)
}
export default Weather
WeatherDisplay.js File
const WeatherDisplay = ({weather}) => {
console.log(weather.current.temperature)
return (
<h1>{weather.current.temperature}</h1>
)
}
export default WeatherDisplay
Having issues display the data when i use {weather.current.temperature}, it keeps giving me an error pointed at temperuture saying it isnt defined but its apart of the data
You are passing weather.current as props. While the child component is expecting weather as prop. So, what you end up doing is weather.current.current.temperature which is undefined because it doesn't exist. Just pass weather to the child prop.
Make this change when calling your child component.
<WeatherDisplay weather={weather}/>

Redux: accessing dictionary as state object

I have implemented a shopping cart using Redux, I have used a dictionary as state object (product id being the key and quantity in cart being the value). Here is how my cart.js looks like:
import React from 'react';
import ReactDOM from 'react-dom';
export const AddItemToCart = (productID) => {
return {
type: 'ADDITEMTOCART',
productID
}
}
export const DeleteItemFromCart = (productID) => {
return {
type: 'DELETEITEMFROMCART',
productID
}
}
export const Counter = (state = {}, action) => {
switch (action.type) {
case 'ADDITEMTOCART':
console.log(action);
return {
...state,
[action.productID]: ( state[action.productID] || 0 ) + 1
}
case 'DELETEITEMFROMCART':
return {
...state,
[action.productID]: ( state[action.productID] || 1 ) - 1
}
}
}
I'm adding an item from App.js like this:
return products.map(products =>
<div key={products.ProductID}>
<h2>{products.ProductName}</h2>
<h2>{products.ProductDescription}</h2>
<h2>{products.ProductQuantity} units available</h2>
<button onClick={() => { store.subscribe(() => console.log(store.getState()));
store.dispatch(AddItemToCart(products.ProductID));}}>Add to cart</button>
</div>
Everything is working just fine but the problem is, I can't render the contents of the cart for user to see. I have tried:
function ShowCartContents() {
var items = Object.keys(store.getState()).map(function(key){
return store.getState()[key];
});
return (
<div>
<h2>{items}</h2>
</div>
);
}
This function throws exception when called:
TypeError: Cannot convert undefined or null to object
Clearly the store itself is not null or undefined, because the change of state is successfully printed to the browser console. So, how would I access all the values in dictionary without keys? And how would I access one specific value by key? Any advise would be highly appreciated. Thanks.
Your Counter reducer has no default case, so your state will be undefined on the first render.
That's the source of your error "TypeError: Cannot convert undefined or null to object".
You need to return the existing state when neither action matches. Every reducer needs a default case because they will be called with actions like the {type: '##redux/INIT'} action which is used to initialize the store.
default:
return state;
You are trying to access the store directly with store.subscribe(), store.getState() and store.dispatch(). This is not the correct way to interact with a Redux store in React. You should use the react-redux package.
You want to wrap your entire app in a Provider component that provides the store instance. Something like this:
import { render } from "react-dom";
import { Provider } from "react-redux";
import App from "./components/App";
import store from "./store";
const rootElement = document.getElementById("root");
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
);
In your components, use the hook useSelector to select values from the state and useDispatch to access the dispatch function. (You can also use the connect higher-order component, but the hooks are preferred).
I'm not sure if this reducer is your entire state or if you are using combineReducers and have multiple reducers like cart, products, etc. This selector is assuming that it's the entire state.
function ShowCartContents() {
const productIds = useSelector(state => Object.keys(state))
return (
<div>
<h2>Ids In Cart: {productIds.join(", ")}</h2>
</div>
);
}
function ProductsList({ products }) {
const dispatch = useDispatch();
return (
<div>
{products.map((product) => (
<div key={product.ProductID}>
<h2>{product.ProductName}</h2>
<h2>{product.ProductDescription}</h2>
<h2>{product.ProductQuantity} units available</h2>
<button onClick={() => dispatch(AddItemToCart(product.ProductID))}>
Add to cart
</button>
</div>
))}
</div>
);
}

I am having trouble understanding why I am not able to .map over the data I am return in the state that I set up in my React program

I am currently getting the error message TypeError: movieData.map is not a function which to me does not make any sense when I grab the data from the API it is an array of objects. I am loading the data from API in an useEffect() and loading once and setting it to the state that I want in the Promise.
What am I missing/not understanding about Axios and receiving data from it?
import React, { useState, useEffect } from 'react';
import Axios from 'axios';
import { Container, Row, Col } from 'react-bootstrap';
// import Poster from './modules/Poster';
function Home() {
const [movieData, setMovieData] = useState([]);
// const [baseURL, setBaseURL] = useState([]);
useEffect(() => {
Axios.get('https://api.themoviedb.org/3/discover/movie?api_key=ae6887d6afcef7f295ee5ce27afa2389&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1')
.then((res) => setMovieData(res.data))
.catch(err => console.log)
}, [])
// useEffect(() => {
// Axios.get('https://api.themoviedb.org/3/configuration?api_key=ae6887d6afcef7f295ee5ce27afa2389')
// .then((res) => setBaseURL(res.data))
// }, [])
// console.log(movieData)
return (
<div>
<h1>Home Page</h1>
<Container>
<Row>
{movieData.map((movie) => <div>{movie.title}</div>)}
</Row>
</Container>
<img src={`http://image.tmdb.org/t/p/w500${movieData.poster_path}`} alt="poster"/>
</div>
)
}
export default Home;
If you console.log the res.data you're getting back from your axios request, you'll likely find that it is an object, not an array.
You might perhaps want to use res.data.results to get the array of results. Or alternatively try mapping over movieData.results if you do want to store the whole object in state. If you're doing that though, you should probably provide a better default to state than an empty array.
Check whether the movieData is array. Or the movieData definitely be null.

React Hooks: Dispatch an action on componentDidMount

I have three pages, PageA, PageB and PageC, that contain a form element formField.
State in globalReducer.js
import { fromJS } from 'immutable';
const initialState = fromJS({
userInteractionBegun: false,
pageActive: '',
refreshData: true,
})
I want to dispatch an action that sets pageActive to corresponding page value(One of A, B or C) when the component(page) mounts and refreshes formField to blank if userInteractionBegun === false.
For every page component, to get pageActive state in props from globalReducer, I do,
function PageA(props) {
//.....
}
// globalState is defined in conigureStore, I am using immutable.js. Link provided below this code.
const mapStateToProps = state => ({
pageActive: state.getIn(['globalState', 'pageActive']),
})
export default connect(mapStateToProps, null)(PageA);
Link to immutable.js getIn()
store.js
import globalReducer from 'path/to/globalReducer';
const store = createStore(
combineReducers({
globalState: globalReducer,
//...other reducers
})
)
I want to abstract the logic to update pageActive every time a component(page) mounts.
I know how to abstract this logic using an HOC, but I don't know how to do it using react hooks, so that every time pageA, pageB or pageC mounts, an action to setPageActive is dispatched and formField is set to blank if userInteractionBegun is false.
For instance, I would do in pageA.js
import usePageActive from 'path/to/usePageActive';
const [pageActive, setPageActive] = useReducer(props.pageActive);
usePageActive(pageActive);
Then in usePageActive.js
export default usePageActive(pageActive) {
const [state, setState] = useState(pageActive);
setState(// dispatch an action //)
}
I haven't had much time to dip my toes into react hooks yet, but after reading the docs and playing with it for a minute, I think this will do what you're asking. I'm using built-in state here, but you could use redux or whatever else you like in the effect. You can see a working example of this code here The trick is using a hook creator to create the custom hook. That way the parent and children can keep a reference to the same state without the useEffect affecting the parent.
import React, { useState, useEffect } from 'react';
import ReactDOM from "react-dom";
const activePageFactory = (setActivePage) => (activePage) => {
useEffect(() => {
setActivePage(activePage)
return () => {
setActivePage('')
}
}, [activePage])
return activePage
}
function App() {
const [activePage, setActivePage] = useState('');
const [localPage, setLocalPage] = useState('Not Selected');
const selectedPage = () => {
switch(localPage) {
case 'A':
return <PageA useActivePage={activePageFactory(setActivePage)} />
case 'B':
return <PageB useActivePage={activePageFactory(setActivePage)} />
default:
return null;
}
}
return (
<div>
<p>Active page is {activePage}</p>
<button onClick={() => setLocalPage('A')}>
Make A Active
</button>
<button onClick={() => setLocalPage('B')}>
Make B Active
</button>
{
selectedPage()
}
</div>
);
}
function PageA({useActivePage}) {
useActivePage('A');
return (
<div>
<p>I am Page A</p>
</div>
)
}
function PageB({useActivePage}) {
useActivePage('B');
return (
<div>
<p>I am Page B</p>
</div>
)
}

Categories

Resources