I have a functional component that designed to search tasks and show in a task resultbox, the async Task data is set via useState hook and passed to the child component "TaskResultBox". The state is not changed and values are not rendered into the child component. I have verified the values retrieved from API using debug. but not re-rendering the data in the child component.
import React from 'react'
import {useState} from 'react'
function SearchTask({onAddTask}) {
const [searchInputValue, setSearchInputValue] = useState('');
const [tasks, setTasks] = useState('');
const getTasks = () => {
return tasks;
};
const onSearchInputValueChange = (e) => {
setSearchInputValue(e.target.value);
};
const onSearch = async(e) => {
const theRequestOpts = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ searchString: searchInputValue })
};
const response = await fetch('/api/searchTasks', theRequestOpts);
const data = await response.json();
setTasks(data);
};
return (
<InputBox onSearchInputValueChange={onSearchInputValueChange}/>
<Button title="Search Tasks:" onClick={onSearch}/>
<TaskResultBox taskResults={getTasks}/>
)
}
export default SearchTask
// TaskTesultBox.js
import React from 'react'
function TaskResultBox({taskResults}) {
return (
<div>
<h1>Task Result:</h1>
<textarea value={taskResults}/>
</div>
)
}
export default TaskResultBox
getTasks is a function that returns a tasks object, so to get the return variable, you would need to invoke it
So change to:
<TaskResultBox taskResults={getTasks()}/> //<-- Invoke it
But I wonder why do you need a function just to return that variable but not put it directly to the props?
Like so:
<TaskResultBox taskResults={tasks}/>
Related
I am new to React.
I am trying to run a fetch call to get some data when my react page loads. I am not sure I am calling the event correctly though.
Here is how I am calling it:
export default function GetRecipe(props) {
let { name } = useParams()
const [searchQuery, setSearchQuery] = useState('')
const [recipeName, setRecipeName] = useState('')
const [methodStepsList, setMethodStepsList] = useState([])
const [ingredients, setIngredients] = useState([])
const retrieveRecipe = function (e) {
console.log('getting recipe')
e.preventDefault()
console.log(searchQuery.length)
let queryString
if (searchQuery.length) {
queryString = `http://localhost:3001/getbasicrecipe/?name=${searchQuery}`
} else {
queryString = `http://localhost:3001/getbasicrecipe/?name=${name}`
}
fetch(queryString, {
method: 'GET',
headers: { 'Content-type': 'application/json' },
})
.then((resp) => resp.json())
.then((json) => {
console.log(json)
let result = json
let recipeName = result[0].recipe_name
let recipeMethod = result[0].recipe_method.split(/\r?\n/)
console.log(recipeMethod)
setRecipeName(recipeName)
setMethodStepsList(recipeMethod)
setIngredients(json)
})
}
return (
<div>
<div className="recipe-form-container">
<form className="recipe-form">
[...]
</div>
</form>
</div>
</div>
)
I read about componentDidMount() but could not figure out how to include it in my code.
Thanks!
If you are using a class component then, as you mentioned you can use the componentDidMount() lifecycle method like this:
componentDidMount() {
// Runs after the first render() lifecycle
retrieveRecipe();
}
...
render(){
...
}
Docs: https://reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class
However if you are using a function component you should use instead the useEffect hook like this:
useEffect(() => {
retrieveRecipe();
}, []); // by using an empty array as dependency this useEffect will act as the componentDidMount function
...
return (
...
)
Docs: https://reactjs.org/docs/hooks-effect.html
If you are using Class based component
componentDidMount(){
//Your code here
}
render(){
return ...
}
If using function component
useEffet(()=>{
//your code here
}, [])
return (...)
this is a classic case of using useEffect hook -
as in before the return function use -
useEffect(()=>{ retrieveRecipe(); },[]);
it comes instead of componentDidMount
I recommend to play a bit with react.js tutorial to get the hang of it before creating your own projects.
import React, { useEffect } from "react";
function YourComponent() {
useEffect(() => {
retrieveRecipie(e).then((res)=>{
console.log(res);
}
)
}, []);
return (
<div></div>
);
}
export default YourComponent;
mak you retrieveRecipie function
const retrieveRecipie= (yourParams) => {
return fetch(
`URL from where you are fetching data`,
{
method: "GET",
headers: {
Accept: "application/json",
"Content-type": "application/json",
},
body: JSON.stringify(yourParams),
}
)
.then((response) => {
return response.json();
})
.catch((err) => {
console.log(err);
});
};
As you are using Functional Component of React JS, you don't need to handle the component lifecycle method manually. For functional component, there is useEffect hook which will handle the lifecycle of the component.
import React, { useEffect } from 'react';
export default function GetRecipe(props) {
useEffect(() => {
retrieveRecipe();
},[]);
return <></>;
}
The above code will call the retrieveRecipe function only once after rendering this component.
I am currently learning React, and I am trying to build a small weatherapp to practice with apis, axios and react generally. I built an input component where it's duty is getting the data from the API, and I am holding the data in the useState hook and I want to use the data in the main App component? I am able to pass data from parent App component to input component if I take the functionality in the app component, but this time I start to have problems with input text rendering problems. Here is the code:
this is the input component where I search and get the data from the API, and I am trying to pass the weatherData into the main App component and render it there. How is it possible to achieve this?
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const key = process.env.REACT_APP_API_KEY;
function SearchLocation() {
const [text, textChange] = useState('');
const [weatherData, setWeatherData] = useState([]);
const handleText = (e) => {
textChange(e.target.value);
};
const fetchData = async () => {
const { data } = await axios.get(
`https://api.weatherapi.com/v1/current.json`,
{
params: {
key: key,
q: text,
lang: 'en',
},
}
);
setWeatherData(data);
};
useEffect(() => {
try {
fetchData();
} catch (error) {
console.log(error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [text]);
console.log(weatherData);
return (
<div>
<form>
<input
onChange={handleText}
className="locationInput"
type="text"
value={text}
required
></input>
</form>
</div>
);
}
export default SearchLocation;
EDIT:
After moving the states to main component and passing them to children as props I receive 3 errors, GET 400 error from the API, createError.js:16 Uncaught (in promise) Error: Request failed with status code 400 and textChange is not a function error. Here are how components look like. This is the input component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const key = process.env.REACT_APP_API_KEY;
function SearchLocation({ weatherData, setWeatherData, text, textChange }) {
const handleText = (e) => {
textChange(e.target.value);
};
const fetchData = async () => {
const { data } = await axios.get(
`https://api.weatherapi.com/v1/current.json`,
{
params: {
key: key,
q: text,
lang: 'en',
},
}
);
setWeatherData(data);
};
useEffect(() => {
try {
fetchData();
} catch (error) {
console.log(error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [text]);
console.log(weatherData);
return (
<div>
<form>
<input
onChange={handleText}
className="locationInput"
type="text"
value={text}
required
></input>
</form>
</div>
);
}
export default SearchLocation;
this is the parent app component:
import React from 'react';
import { useState } from 'react';
import './App.css';
import './index.css';
import SearchLocation from './components/Input';
function App() {
const [weatherData, setWeatherData] = useState([]);
const [text, textChange] = useState('');
return (
<div className="App">
<SearchLocation
setWeatherData={setWeatherData}
lastData={weatherData}
inputText={text}
/>
</div>
);
}
export default App;
You'll still need to store the state in the parent component. Pass the setter down as a prop. This is a React pattern called Lifting State Up.
Example:
const App = () => {
const [weatherData, setWeatherData] = useState([]);
...
return (
...
<SearchLocation setWeatherData={setWeatherData} />
...
);
};
...
function SearchLocation({ setWeatherData }) {
const [text, textChange] = useState('');
const handleText = (e) => {
textChange(e.target.value);
};
const fetchData = async () => {
const { data } = await axios.get(
"https://api.weatherapi.com/v1/current.json",
{
params: {
key,
q: text,
lang: 'en',
},
}
);
setWeatherData(data);
};
useEffect(() => {
try {
// Only request weather data if `text` is truthy
if (text) {
fetchData();
}
} catch (error) {
console.log(error);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [text]);
return (
<div>
<form>
<input
onChange={handleText}
className="locationInput"
type="text"
value={text}
required
/>
</form>
</div>
);
}
There are two solutions to your problem:-
Firstly you can create the states const [text, textChange] = useState('');
const [weatherData, setWeatherData] = useState([]);, inside your parent component and pass text, textChange, weatherData, setWeatherData as props to your child component.
I would recommend the second way, i.e, implement redux for this and store text, and weatherData into your redux and try to access them from redux.
redux reference:- https://react-redux.js.org/introduction/getting-started
Case
I want to make isLoading (global state using React Context) value and changeIsLoading function (its changing function from IsLoadingContext.js file) becomes accessible to all files (function components and simple javascript functions).
I know that React Hooks can only be called inside of the body of a function component.
Question: So in my case here, how could I called isLoading and changeIsLoading inside a util file (non-function component or just a simple javascript function)?
What should I change from the code?
Code flow
(location: SummariesPage.js) Click the button inside SummariesPage component
(location: SummariesPage.js) Call onApplyButtonIsClicked function in SummariesPage component
(location: SummariesPage.js) Change isLoading global state into true then call fetchAPISummaries function
(location: fetchAPISummaries.js) Call fetchAPICycles function
(location: fetchAPICycles.js) Call exportJSONToExcel function
(location: exportJSONToExcel.js) Export the JSON into an Excel file then change isLoading global state into false
IsLoadingContextProvider component will be rerendered and the isLoading value in SummariesPage will be true
Error logs
Uncaught (in promise) Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
The code
IsLoadingContext.js:
import React, { useState } from 'react'
const IsLoadingContext = React.createContext()
const IsLoadingContextProvider = (props) => {
const [isLoading, setIsLoading] = useState(false)
const changeIsLoading = (inputState) => {
setIsLoading(inputState)
}
return(
<IsLoadingContext.Provider
value={{
isLoading,
changeIsLoading
}}
>
{props.children}
</IsLoadingContext.Provider>
)
}
export { IsLoadingContextProvider, IsLoadingContext }
SummariesPage.js:
import React, { useContext } from 'react'
// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'
// COMPONENTS
import Button from '#material-ui/core/Button';
// UTILS
import fetchAPISummaries from '../../utils/export/fetchAPISummaries'
const SummariesPage = () => {
const { isLoading, changeIsLoading } = useContext(IsLoadingContext)
const onApplyButtonIsClicked = () => {
changeIsLoading(true)
fetchAPISummaries(BEGINTIME, ENDTIME)
}
console.log('isLoading', isLoading)
return(
<Button
onClick={onApplyButtonIsClicked}
>
Apply
</Button>
)
}
export default SummariesPage
fetchAPISummaries.js:
// UTILS
import fetchAPICycles from './fetchAPICycles'
const fetchAPISummaries = (inputBeginTime, inputEndTime) => {
const COMPLETESUMMARIESURL = .....
fetch(COMPLETESUMMARIESURL, {
method: "GET"
})
.then(response => {
return response.json()
})
.then(responseJson => {
fetchAPICycles(inputBeginTime, inputEndTime, formatResponseJSON(responseJson))
})
}
const formatResponseJSON = (inputResponseJSON) => {
const output = inputResponseJSON.map(item => {
.....
return {...item}
})
return output
}
export default fetchAPISummaries
fetchAPICycles.js
// UTILS
import exportJSONToExcel from './exportJSONToExcel'
const fetchAPICycles = (inputBeginTime, inputEndTime, inputSummariesData) => {
const COMPLETDEVICETRIPSURL = .....
fetch(COMPLETDEVICETRIPSURL, {
method: "GET"
})
.then(response => {
return response.json()
})
.then(responseJson => {
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson))
})
}
const formatResponseJSON = (inputResponseJSON) => {
const output = inputResponseJSON.map(item => {
.....
return {...item}
})
return output
}
export default fetchAPICycles
exportJSONToExcel.js
import { useContext } from 'react'
import XLSX from 'xlsx'
// CONTEXTS
import { IsLoadingContext } from '../../contexts/IsLoadingContext'
const ExportJSONToExcel = (inputSummariesData, inputCyclesData) => {
const { changeIsLoading } = useContext(IsLoadingContext)
const sheetSummariesData = inputSummariesData.map((item, index) => {
let newItem = {}
.....
return {...newItem}
})
const sheetSummaries = XLSX.utils.json_to_sheet(sheetSummariesData)
const workBook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workBook, sheetSummaries, 'Summaries')
inputCyclesData.forEach(item => {
const formattedCycles = item['cycles'].map((cycleItem, index) => {
.....
return {...newItem}
})
const sheetCycles = XLSX.utils.json_to_sheet(formattedCycles)
XLSX.utils.book_append_sheet(workBook, sheetCycles, item['deviceName'])
})
XLSX.writeFile(workBook, `......xlsx`)
changeIsLoading(false)
}
export default ExportJSONToExcel
I believe the real problem you are facing is managing the asynchronous calls. It would be much readable if you use async/await keywords.
const onApplyButtonIsClicked = async () => {
changeIsLoading(true)
await fetchAPISummaries(BEGINTIME, ENDTIME)
changeIsLoading(false)
}
You will need to rewrite fetchAPICycles to use async/await keywords instead of promises.
const fetchAPICycles = async (
inputBeginTime,
inputEndTime,
inputSummariesData
) => {
const COMPLETDEVICETRIPSURL = ...;
const response = await fetch(COMPLETDEVICETRIPSURL, {
method: "GET",
});
const responseJson = await response.json();
exportJSONToExcel(inputSummariesData, formatResponseJSON(responseJson));
};
I want to make a weather app. But the string does not appear on the component I want.
How can innerText be used in React?
This is my Weather.jsx
import React from "react";
const Weather = () => {
const API_KEY = '123456789';
const COORDS = "coords";
const weather = document.getElementById("weather");
const getWeather = (lat, lng) => {
fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${API_KEY}&units=metric`
)
.then(function (response) {
return response.json();
})
.then(function (json) {
const temperature = json.main.temp;
const place = json.name;
weather.innerText = `${temperature}℃,\n ${place}`;
});
};
};
export default Weather;
This is the screen code for the weather to appear.
import React from "react";
import styled from "styled-components";
import Weather from "../../Components/Weather";
const DetailPresenter = () => {
return (
<>
<DetailContainer>
<div id="weather">{Weather()}</div>
</DetailContainer>
</>
);
};
export default DetailPresenter;
This isn't how react renders UI. We don't invoke the functions directly, we render them into the JSX "template" for React to invoke when necessary. Nothing is invoking your getWeather function to actually fetch the weather data. Also, direct DOM manipulation is an anti-pattern in react.
Save the text you want displayed into local component state.
Render it. Your Weather component currently returns undefined, which isn't valid for React, components must return valid JSX.
Weather
const Weather = () => {
const API_KEY = '123456789';
const COORDS = "coords";
const [weather, setWeather] = React.useState('');
const getWeather = (lat, lng) => {
fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${API_KEY}&units=metric`
)
.then(function (response) {
return response.json();
})
.then(function (json) {
const temperature = json.main.temp;
const place = json.name;
setWeather(`${temperature}℃,\n ${place}`);
});
};
React.useEffect(() => {
getWeather(......lat & long);
}, []); // <-- run effect once on component mount
return weather; // <-- string is valid JSX
};
DetailPresenter
import React from "react";
import styled from "styled-components";
import Weather from "../../Components/Weather";
const DetailPresenter = () => {
return (
<DetailContainer>
<Weather /> // <-- render component, don't invoke
</DetailContainer>
);
};
I am trying to initialize a custom React context with data from back end, using a GET API request. However, the context is loaded before the API call finishe the data fetching.
What I've tried is to use a consumer to send data to the child component but I can only access the default value of the context which is set then the context is created.
Here is how I am trying to set my context data
import React, {useState,useEffect} from "react";
import {callAffiliateApi} from "./services/affiliateService/transactionContext";
export const AffiliateContext = React.createContext("Raaaaaaaaaaaa");
export const AffiliateProvider = ({children}) => {
const [data, setData] = useState(null);
useEffect(()=> {
async function fetchData() {
const newText = await callAffiliateApi();
setData(newText)
};fetchData()
},[])
console.log("Data in the context", data);
if(data != null){
return (
<AffiliateContext.Provider value={data}>
{children}
</AffiliateContext.Provider>
)}
else {
return (
<AffiliateContext.Provider value={"Loading..."}>
{children}
</AffiliateContext.Provider>)
}
}
And here is how I'm trying to access it in the child component
import {AffiliateContext} from "../../../../AffiliateContext";
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
text: this.props.text,
user: this.props.user,
}
}
render() {
return (
<AffiliateContext.Consumer>
{data =>
<div>
{data}
</div>}
</AffiliateContext.Consumer>
)
}
}
export default Profile;
However, the only thing that gets displayed in the page is "Raaaaaaa", the default value of the component. How can I make the child component wait until the data finishes fetching from the API request?
try to use useContext its cleaner and try not to use the async inside the useEffect!
their related issues
import React, { useState,useEffect,useContext } from "react";
import { callAffiliateApi } from "./services/affiliateService/transactionContext";
const Context = React.createContext({});
const AffiliateContext = init => useContext(Context);
export const AffiliateProvider = ({children}) => {
const [data, setData] = useState(null);
const [loading,setLoading]=useState(false);
const getAffiliates = async ()=>{
setLoading(true)
const newText = await callAffiliateApi();
setData(newText)
setLoading(false)
}
useEffect(()=> {
getAffiliates()
},[])
return (
<AffiliateContext.Provider value={{loading,list:data}}>
{children}
</AffiliateContext.Provider>
)
}