How to set a components state from another component? - reactjs

Here I have a weather app which uses an API in it's own component to fetch weather data. I'm at the point now where I'm trying to append the retrieved data values to the DOM/webpage but am having trouble. I have declared state in my Header component (working with only the Temp right now until it works) but can't figure how to manipulate it.
Things I've tried and the outcomes: I have tried putting the state into the top level as well as the API function (so it was all in App.js) but this didn't work because I couldn't call the nested function and if I make it global, it doesn't recognize the state variable. I also tried creating a function that explicitly changes state and using /importing that but I run into the same issue.
I have also theorized about maybe instead of trying to send the state to other components, to send the data to the component that holds the state (header) but I'm not sure on this since it's to my understanding that data should only live in one place? I appreciate any help.
APP.js
`
import logo from './logo.svg';
import './App.css';
import Header from './components/header'
import Body from './components/body'
import Footer from './components/footer'
import { useState } from 'react';
function App() {
return (
<div className="container">
<Header />
<Body />
<Footer />
</div>
);
}
export default App;
api.js
import {Header} from '../components/header'
export async function FetchAPI(location) {
try {
let result = await fetch ('http://api.openweathermap.org/data/2.5/weather?q=' + location +'&APPID=bd3dd8d1151b1e784fcf021aa29927c5',
{mode: 'cors'});
let final = await result.json()
return console.log(final.cod)
}
catch(err) {
alert(err)
}
}
export async function processData(data) {
console.log(data)
}
Header.js
import {processData} from "../api/api"
import {useState} from 'react'
export function Header({tempValue, locationValue}) {
const handleTemp = (newValue) => { setTemp(newValue) }
const [tempDOM, setTemp] = useState(2)
return (
<header className="header">
<div>
<h1 value={setTemp}> </h1>
<h2></h2>
</div>
<div>
<h1>Time</h1>
<h2>Windspeed</h2>
</div>
</header>
);
}
export default Header;
`
I have tried using state and changing that using a function as well as nesting the state and function that renders the data together in a top level component.

While not necessary for this example, I would suggest you check out react stores. My personal favorite is MobX - they can make managing your data a lot easier and reduce the amount of states and things you need to pass through to each components.
Now for your code here are a few notes:
You don't seem to call FetchAPI from anywhere. You should either call it on page load (in App.js) or call it inside your Header component when the location changes
Your FetchAPI also seems to return a console.log() not an actual value. I would suggest you return final.cod or whatever it is that you need returned. Perhaps even the entire json (return final)
Inside your h1 tag, you are setting the value to a set method of the state - this won't work. in useState, if you save it to an array, the first value (tempDOM) is the value of the state, and the second value is a method that is used to change the state value. So instead, as a comment has already suggested, use <h1>{tempDOM}</h1>. This will display the initial value you set (2) and nothing else since you are not changing the state value via setTemp() method
This is what I would expect the code to look like (haven't tested):
import './App.css';
import Header from './components/header'
import Body from './components/body'
import Footer from './components/footer'
function App() {
return (
<div className="container">
<Header location='London'/>
<Body />
<Footer />
</div>
);
}
export default App;
import {FetchAPI} from "../api/api"
import {useState, useEffect} from 'react'
export function Header({location}) {
const [apiData, setApiData] = useState({});
useEffect(()=>{
async function getApiData() {
const data = await FetchAPI(location);
setApiData(data);
}
getApiData();
// including location in useEffect dependency array,
// which means this effect will be called every time location property changes
}, [location]);
return (
<header className="header">
<div>
{
// I don't know what your API model looks like.
// adjust the property that you are accessing based on your knowledge
}
<h1>{apiData.temp}</h1>
<h2></h2>
</div>
<div>
<h1>Time</h1>
<h2>Windspeed</h2>
</div>
</header>
);
}
export default Header;
export async function FetchAPI(location) {
try {
const result = await fetch ('http://api.openweathermap.org/data/2.5/weather?q=' + location +'&APPID=bd3dd8d1151b1e784fcf021aa29927c5',
{mode: 'cors'});
const final = await result.json()
return final
}
catch(err) {
alert(err)
}
}
As mentioned, I haven't tested this, but this is more along the lines of what I would expect a working application to look like.
More on useEffect

Related

How can I use useEffect correctly in a function?

I don't think I am using useEffect correctly in the function I have written. If I do the same with classes there is no problem and I am able to do ComponentDidMount(). But I don't know Hooks very well. So I think I am going wrong somewhere.
//import { onValue } from 'firebase/database';
import React, { useEffect } from "react";
import {db} from '../../firebase';
import {ref,onValue} from 'firebase/database'
import Grid from '#mui/material/Grid';
import Box from '#mui/material/Box';
export default function NiftyChart() {
React.useEffect(()=>{
const dbRef=ref(db,"1rh1Ta-8dqZKmh1xy5ans2lOqReoiVAT81WyDKqRaxl0/Nifty");
onValue(dbRef,(snapshot)=>{
let records=[];
snapshot.forEach(childSnapshot=>{
let keyName=childSnapshot.key;
let data=childSnapshot.val();
console.log(snapshot.val());
records.push({"key":keyName,"data":data})
console.log(records[records.length-1])
});
this.setState();
})
},[]);
return(
<div>
{this.state.map((row, index)=>{
return(
<Box component="span" sx={{}}>
<Grid > {row.Close}</Grid>
</Box>
)
})}
</div>
)
}
Also, no value is printed for row.Close. In the console I seem to be getting Cannot read properties of undefined (reading 'state') this error.
Any help is appreciated.
You need to use useState:
const [records, setRecords] = useState([]);
useEffect(() => ..get records and set to state, []);
So code would look like this:
export default function MyComponent() {
const [records, setRecords] = useState([]);
useEffect(() => {
// ... other code is omitted for the brevity
setRecords(records)
}, [])
return(
<div>
{records &&
records.map((row, index)=>{ //...other code is omitted
// for the brevity }
</div>
)
}
What's hook? The hook is:
A Hook is a special function that lets you “hook into” React features.
For example, useState is a Hook that lets you add React state to
function components
[] in useEffect means:
If you want to run an effect and clean it up only once (on mount and
unmount), you can pass an empty array ([]) as a second argument. This
tells React that your effect doesn’t depend on any values from props
or state, so it never needs to re-run. This isn’t handled as a special
case — it follows directly from how the dependencies array always
works.
Read more about API call in this great article

React JS coponent not rendering using map function

I hava a component called videoRow i try to render this component using dummy values now i get data from a useEffect Hook i have to use that data to render my component but when i try to do so it dont show anything. I even try console log to check weather i get my data or not it print my data on console means my useEffect is working But when i try this data on my videoRow component it not show anything
import React, { useState, useEffect } from "react";
import "../css/searchPage.css";
import TuneSharpIcon from "#mui/icons-material/TuneSharp";
import ChannelRow from "./ChannelRow";
import VideoRow from "./VideoRow";
import { selectInput } from "../features/inputSlice";
import { useSelector } from "react-redux";
import Axios from "axios";
function SearchPage() {
const getQuery = useSelector(selectInput);
const API_URL = `https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=4&key=APIKEY&type=video&q=${getQuery.input}`;
const [data, setData] = useState([]);
useEffect(() => {
async function fetchData() {
let request = await Axios.get(API_URL);
setData(request);
}
fetchData();
}, [API_URL]);
console.log(data);
return (
<div className="searchPage">
<div className="filter">
<TuneSharpIcon></TuneSharpIcon>
<h2>FILTERS</h2>
</div>
<hr></hr>
<ChannelRow
image="https://images.indianexpress.com/2022/01/Republic-Day_1200_AP2022.jpg"
channelName="Dummy"
verified
subs="670k"
noOfVideos={567}
desc="You can find awesome programming lessons here! Also, expect programming tips and tricks that will take your coding skills to the ..."
></ChannelRow>
<hr></hr>
{data?.data?.items?.forEach((item) => {
console.log(item.snippet.title);
console.log(item?.snippet.thumbnails.high.url)
console.log(item?.snippet.publishedAt)
console.log(item?.snippet.description)
console.log(item?.snippet.channelTitle)
return(<VideoRow
image={item?.snippet.thumbnails.high.url}
channelName={item?.channelTitle}
timestamp={item?.snippet.publishedAt}
title={item?.snippet.title}
desc={item?.snippet.description}
views="1.4M"
subs="1.4M"
></VideoRow>)
})}
</div>
);
}
export default SearchPage;
Change data?.data?.items?.forEach to data?.data?.items?.map. forEach returns nothing. So, even if you return the component from the callback, forEach will just ignore it. But, map will return all transformed results as an array.
You can read more about lists in react here.

React Apollo useQuery - how it works

Trying to understand how useQuery executes in background. When the Booklist component is rendered first , booklist function is called and usequery hook being called it returns loading set to true and starts executing the graphql request in the background. When it retrieves response from server it updates the properties and the component gets rerendered again , the function BookList is being called again from the begining. What happens when useQuery being called again does it place another request or takes the data from cache? How does it work.
Pasted the code below
import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import { useQuery } from "#apollo/react-hooks";
import gql from "graphql-tag";
function BookList() {
console.log('Inside booklist')
const { loading, error, data } = useQuery(gql`
{
countries {
full_name_english
full_name_locale
}
}
`);
console.log('Inside booklist1')
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
console.log('iam here22');
console.log(data);
// return (<div>completed</div>);
return data.countries.map(({ full_name_english, full_name_locale },index) => (
<div key={index}>
<p>
{ full_name_english}: {full_name_locale}
</p>
</div>
));
}
export default BookList;
App.js
function App() {
return (
<ApolloProvider client={client}>
<div id="main">
<h3> TEST APP</h3>
<MyTest />
<BookList/>
</div>
</ApolloProvider>
);
}
export default App;
useQuery is a custom hook provided by apollo and it uses react hooks internally.
Now the way react hooks work is that when the first time they are rendered they use the values passed as arguments to them and store the result in memory
Now whenever the component renders is future, the arguments from the hooks are not used but the value from cache is returned. Any update to the value using the setter methods is being applied to the in memory results
Now useQuery also works on the same principle, the query passed to it is only used once and on future renders the value from cache is returned
You can check in the react docs about lazy initial state

Why the data not displayed in nextjs?

I am making a very very simple nextjs application where I am trying to fetch the data from api.
My requirement is I should display the data in layout.js file and this layout.js file is a children in index.js file.
index.js:
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js:
import React from "react";
import fetch from "isomorphic-unfetch";
function Layout(props) {
return (
<div>
<p>Preact has {props.stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
Layout.getInitialProps = async () => {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
return { stars: json.stargazers_count };
};
export default Layout;
So as per the above given code, I have called the layout page inside index.js page (in my real application I need to call like this only so no changes in calling layout inside index)..
But when I made a console.log() in the function Layout.getInitialProps in layout, it doesn't print anything and hence the api data not fetched..
Complete working demo here with code
Why can't I fetch the data inside the layout.js while calling as a children from index.js?
Also provide me the right updated solution to achieve this.. I really searched for many questions but none solved my issue and I couldn't understand those solutions clearly so please help me with the above given example.
That because getInitialProps can only be added to the default component exported by a page, adding it to any other component won't work.
You should use componentDidMount() or useEffect instead, or move getInitialProps in the index and then pass the result to the component. something like (not tested) :
index.js :
import Layout from "./layout";
import React from "react";
class Home extends React.Component {
render() {
return (
<div>
<Layout />
<h4> Main content will be displayed here !! </h4>
</div>
);
}
}
export default Home;
layout.js
import React from "react";
import fetch from "isomorphic-unfetch";
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
stars: false
};
}
async componentDidMount() {
console.log("comes into layout getinitial props");
const res = await fetch("https://api.github.com/repos/developit/preact");
const json = await res.json(); // better use it inside try .. catch
this.setState({ stars: json.stargazers_count });
}
render() {
const { stars } = this.state;
return (
<div>
<p>Preact has {stars} ⭐</p>
<p> Why I couldn't get the above "props.star" ? </p>
</div>
);
}
}
export default Layout;
Edit:
Example with class component
Bonus: If you want to add the layout for all the pages of your app this isn't the best approach, instead you should take a look to custom _app.js, example

React.js - how to pass event handlers to deeply nested component without props drilling?

I have the structure of components (nested) that seems like this:
Container
ComponentA
ComponentB
ComponentC(want to handle event here with state that lives on container)
Do I need to pass as props all the way from Container, ComponentA, ComponentB and finally ComponentC to have this handler? Or is there another way like using Context API?
I'm finding a bit hard to handle events with react.js vs vue.js/angular.js because of this.
I would recommend using either Context API (as you mentioned) or Higher Order Components (HoC)
Context Api is your data center. You put all the data and click events that your application needs here and then with "Consumer" method you fetch them in any component regardless of how nested it is. Here is a basic example:
context.js //in your src folder.
import React, { Component, createContext } from "react";
import { storeProducts } from "./data"; //imported the data from data.js
const ProductContext = createContext(); //created context object
class ProductProvider extends Component {
state = {
products: storeProducts,
};
render() {
return (
<ProductContext.Provider
//we pass the data via value prop. anything here is accessible
value={{
...this.state,
addToCart: this.addToCart //I wont use this in the example because it would
be very long code, I wanna show you that, we pass data and event handlers here!
}}
>
// allows all the components access the data provided here
{this.props.children},
</ProductContext.Provider>
);
}
}
const ProductConsumer = ProductContext.Consumer;
export { ProductProvider, ProductConsumer };
Now we set up our data center with .Consumer and .Provider methods so we can access
here via "ProductConsumer" in our components. Let's say you want to display all your products in your home page.
ProductList.js
import React, { Component } from "react";
import Product from "./Product";
import { ProductConsumer } from "../context";
class ProductList extends Component {
render() {
return (
<React.Fragment>
<div className="container">
<div className="row">
<ProductConsumer>
//we fetch data here, pass the value as an argument of the function
{value => {
return value.products.map(product => {
return <Product key={product.id} />;
});
}}
</ProductConsumer>
</div>
</div>
</React.Fragment>
);
}
}
export default ProductList;
This is the logic behind the Context Api. It sounds scary but if you know the logic it is very simple. Instead of creating your data and events handlers inside of each component and prop drilling which is a big headache, just put data and your event handlers here and orchestrate them.
I hope it helps.

Resources