React Query only display on tab refresh - javascript

I find myself in a bit of a pickle and can't seem to find an answer on Google.
I'm trying to use the React query library with TSX and display the returning data in a simple list. However it seems that the fetchng and displaying is done only by leaving the tab and coming back to it.
Here's the component
import React, { ChangeEvent, useState, ReactElement } from "react";
import { useQuery, UseQueryResult } from "react-query";
import axios from "axios";
import { API_URL } from "../../settings";
import SearchBar from "../../components/search-bar";
const Employees = (): ReactElement => {
type Employee = Record<string, any>;
const [name, setName] = useState("");
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
console.log(name, "I'm the direct input");
const e = eventData.target.value;
setName(e);
getEmployeesList(name);
}
async function getEmployeesList(name: string) {
const { data } = await axios.get(
API_URL + "employees?q[firstname_or_lastname_cont]=" + name
);
console.log(data);
return data;
}
const {
data,
error,
isError,
isLoading,
}: UseQueryResult<Employee[], Error> = useQuery("employees", () =>
getEmployeesList(name)
);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return (
<div>
`Error!
{error?.message}`
</div>
);
}
if (data) {
console.log(data, "I'm the query data");
}
return (
<div className="findEmployees">
<SearchBar
placeholder=""
value={name}
onValueChange={(event: ChangeEvent<HTMLInputElement>) =>
getValue(event)
}
/>
<div className="listContainer">
<h1>Employees</h1>
{data?.map((employee, index: number) => (
<li key={employee.id}>
{employee.firstname} {employee.lastname}
<p>{employee.role}</p>
</li>
))}
</div>
</div>
);
};
export default Employees;
Here's the child component
import React, {
ChangeEventHandler,
MouseEvent,
ReactElement,
ReactEventHandler,
} from "react";
import { SearchBarContainer, SearchBarInput } from "./styled-components";
import Icon from "../icon";
interface Props {
placeholder: string;
value: string;
onValueChange: ChangeEventHandler<HTMLInputElement>;
}
const SearchBar = ({
onValueChange,
placeholder,
value,
}: Props): ReactElement => (
<SearchBarContainer>
<SearchBarInput
onChange={onValueChange}
placeholder={placeholder}
value={value}
/>
<Icon color="grey700" name="search" />
</SearchBarContainer>
);
export default SearchBar;
So far I haven't found the problem. I tried a custom hook to get and set the data but that obviously didn't change the problem. If anyone has an idea I'll be thankful.
Have a great day

function getValue(eventData: ChangeEvent<HTMLInputElement>) {
const e = eventData.target.value;
setName(e);
console.log(name); // still you should receive previous value
getEmployeesList(e);
}
setState isn't synchronous. It only updates the state value at end of the function call.

thanks for your inputs. I actually solved the problem with a custom hooks
import React, { ChangeEvent, useState, ReactElement } from "react";
import { useQuery, UseQueryResult } from "react-query";
import axios from "axios";
import { API_URL } from "../../settings";
import SearchBar from "../../components/search-bar";
const Employees = (): ReactElement => {
type Employee = Record<string, any>;
const [name, setName] = useState<string>("");
const [eData, setEData] = useState<Employee[]>([]);
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
setName(eventData.target.value);
getEmployeesList(eventData.target.value);
}
async function getEmployeesList(name: string) {
const { data } = await axios.get(
API_URL + "employees?q[firstname_or_lastname_cont]=" + name
);
setEData(data);
return data;
}
const {
data,
error,
isError,
isLoading,
}: UseQueryResult<Employee[], Error> = useQuery("employees", () =>
getEmployeesList(name)
);
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return (
<div>
`Error!
{error?.message}`
</div>
);
}
return (
<div className="findEmployees">
<SearchBar
placeholder=""
value={name}
onValueChange={(event: ChangeEvent<HTMLInputElement>) =>
getValue(event)
}
/>
<div className="listContainer">
<h1>Employees</h1>
{eData?.map((employee, index: number) => (
<li key={employee.id}>
{employee.firstname} {employee.lastname}
<p>{employee.role}</p>
</li>
))}
</div>
</div>
);
};
export default Employees;
As you can see I changed "data" by the hook value "eData". I noticed that my axios query was updated in real time so I took this entry and stocked it in a custom hook which I then mapped on my JSX. Got a real time fetch that way. Furthermore I updated the
function getValue(eventData: ChangeEvent<HTMLInputElement>) {
setName(eventData.target.value);
getEmployeesList(eventData.target.value);
}
part which was requesting with one letter fewer in the first version.

Related

Content provider data no being loaded into a component

I have the main page 'feed' where i used to have three functions, but I moved it into custom context. The console logs in context file output all the objects correctly, but nothing is visible in feed when i concole.log them.
context file:
import React, { useEffect, createContext, useContext, useState } from 'react'
import { useMemo } from 'react';
import getPosts from '../api/getPosts';
import filterImportedPosts from '../utils/filterImportedPosts';
export const ItemContext = createContext({
postData: {}, setPostData: () => { }
});
export const FilteredItemsContext = createContext({ filteredItems: [], setFilteredItems: () => { } })
export const FilterContext = createContext({ filter: '', setFilter: () => { } })
export function useItemContext() {
return useContext(ItemContext)
}
export function useFilteredItemsContext() {
return useContext(FilteredItemsContext)
}
export function useFilterContext() {
return useContext(FilterContext)
}
export default function PostProvider({ children }) {
const [postData, setPostData] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [filter, setFilter] = useState('');
useEffect(() => {
getPosts(setPostData)
console.log('postData: ', postData)
}, []);
useEffect(() => {
// console.log(filter);
const tempFiltItems = filterImportedPosts(postData, filter);
setFilteredItems(tempFiltItems);
console.log('tempFiltItems: ', filteredItems)
}, [filter, postData]);
const filteredItemsState = useMemo(() => {
return { filteredItems, setFilteredItems }
}, [filteredItems, setFilteredItems])
return (
<FilterContext.Provider value={{ filter, setFilter }}>
<FilteredItemsContext.Provider value={filteredItemsState}>
<ItemContext.Provider value={{ postData, setPostData }}>
{children}
</ItemContext.Provider>
</FilteredItemsContext.Provider >
</FilterContext.Provider>
)
}
and here the feed file:
import React, { useState, useEffect } from 'react';
import SinglePost from '../components/singlePost/singlePost';
import FilterPane from '../components/filterPane/filterPane.feedPost';
import { Box, Spinner, Text } from '#chakra-ui/react';
import getPosts from '../api/getPosts';
import Loader from '../../common/Loader';
import filterImportedPosts from '../utils/filterImportedPosts';
import PostProvider, { useFilterContext, useFilteredItemsContext, useItemContext } from './../context/PostDataContext';
export default function Feed() {
//-----------------IMPORT DATA FROM SERVER----------------------
const [error, setError] = useState(null);
const { postData, setPostData } = useItemContext();
const { filter, setFilter } = useFilterContext();
const { filteredItems, setFilteredItems } = useFilteredItemsContext();
useEffect(() => {
console.log(filteredItems)
}, [filteredItems, setFilteredItems])
// this helps while the data is loaded
// if (postData.length === 0) {
// return (
// <Box pos='absolute' top='45vh' left='40%'>
// <Loader />
// </Box>
// )
// }
// console.log(filteredItems);
return (
<PostProvider>
<Box mt={'7vh'} mb={'7vh'} ml={'3vw'} mr={'3vw'} zIndex={200}>
<FilterPane
setFilter={setFilter}
filter={filter}
filteredItems={filteredItems}
/>
{error && (
<div>Error occurred while loading profile info. Details: {error}</div>
)}
{!error && (
<>
{filteredItems.map((item, index) => {
return <SinglePost key={index} item={item} />;
})}
</>
)}
</Box>
</PostProvider>
);
}
console window printscreen. As you can see the filteredItems in context exist but nothing gets shown in the actual feed - the objects are empty. Could someone assist please?

Update child component while using filter on parent component in React

I have seen this asked before but I can't seem to be able to wrap my head around it with my situation.
I am using a search bar to filter the data down and it works but the image will not update. The URL passing to the child works fine but it's just not changing its state. I just don't really understand how to implement it.
PokemonList.jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import PokemonSprite from "./PokemonSprite";
import Card from "#material-tailwind/react/Card";
import CardBody from "#material-tailwind/react/CardBody";
import CardFooter from "#material-tailwind/react/CardFooter";
import H6 from "#material-tailwind/react/Heading6";
import Paragraph from "#material-tailwind/react/Paragraph";
import Button from "#material-tailwind/react/Button";
// const baseURL = "https://pokeapi.co/api/v2/pokemon?limit=898";
const baseURL = "https://pokeapi.co/api/v2/pokemon?limit=20";
export default function PokemonList() {
const [post, setPost] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
axios.get(baseURL).then((response) => {
setPost(response.data.results);
});
}, []);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<input type="text" placeholder="Search..." onChange={e => {setSearchTerm(e.target.value)}}/>
{post.filter((data) => {
if (searchTerm == "") {
return data;
} else if (data.name.toLowerCase().includes(searchTerm.toLowerCase())) {
console.log(data);
return data;
}
}).map((data, idx) => (
<div className="p-5">
<Card key={idx}>
<PokemonSprite url={data.url} />
<CardBody>
<H6 color="gray">{data.name}</H6>
<Paragraph color="gray">
Don't be scared of the truth because we need to restart the human
foundation in truth And I love you like Kanye loves Kanye I love
Rick Owens’ bed design but the back is...
</Paragraph>
</CardBody>
<CardFooter>
<Button color="lightBlue" size="lg" ripple="light">
Read More
</Button>
</CardFooter>
</Card>
</div>
))}
</div>
);
}
PokemonSprite.jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import CardImage from "#material-tailwind/react/CardImage";
export default function PokemonList(url) {
const [post, setPost] = useState();
console.log(url);
useEffect(() => {
axios.get(url.url).then((response) => {
//console.log(response.data);
setPost(response.data);
});
}, []);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<CardImage
src={post.sprites.front_default}
alt="Card Image"
/>
</div>
);
}
Please rewrite your PokemonSprite component like this to enable re rendering on updates to the Url...
import axios from "axios";
import React, { useEffect, useState } from "react";
import CardImage from "#material-tailwind/react/CardImage";
export default function PokemonList(url) {
const [post, setPost] = useState();
console.log(url);
const getUpdatedImage = async (imageUrl) => {
const response = await axios.get(imageUrl);
setPost(response.data);
return post;
}
useEffect(() => {
getUpdatedImage(url.url);
}, [url]);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<CardImage
src={post.sprites.front_default}
alt="Card Image"
/>
</div>
);
}

Not able to convert react custom hook to be a context

I have a custom hook(useData) that takes query as an argument and then returns data and runtime(time to fetch the data from the API). But I need access to the runtime to my Editor component when I click on the run button. Right now what is happening is when I click on run button(inside Editor.js), it sets the query to the App component using the setter function and then it passes that query to the Table component and then calls the custom hook using that query and then table make use of that data. but I want the runtime in the Editor component, not in the Table component. I know I can call useData hook in the Editor component but my editor component gets rerender every time when we write on the editor, so It calls the useData() hook on each change.
If I create a context using this hook then I can able to access the runtime and data wherever I want.
Anyone, please help me how to convert that to context!
App.js code
import React, { useState } from "react";
import "./assets/output.css";
import Footer from "./components/layouts/Footer";
import Navbar from "./components/layouts/Navbar";
import Sidebar from "./components/layouts/Sidebar";
import TableSection from "./components/table/TableSection";
import Editor from "./components/editor/Editor";
const App = () => {
const [query, setQuery] = useState("");
const [value, setValue] = useState("select * from customers");
return (
<>
<div className="grid grid-cols-layout-desktop grid-rows-layout-desktop bg-gray-600 h-screen">
<Navbar />
<Sidebar setQuery={setQuery} setValue={setValue} />
<Editor setQuery={setQuery} value={value} setValue={setValue} />
{query ? <TableSection query={query} /> : null}
<Footer />
</div>
</>
);
};
export default App;
Editor.js
import React from "react";
import AceEditor from "react-ace";
import "ace-builds/src-min-noconflict/ext-language_tools";
import "ace-builds/src-min-noconflict/mode-mysql";
import "ace-builds/src-noconflict/theme-github";
import useData from "../../hooks/useData";
const Editor = ({ setQuery, value, setValue }) => {
const { runtime } = useData();
const onChange = (newValue) => {
setValue(newValue);
};
const onSubmit = () => {
var Z = value.toLowerCase().slice(value.indexOf("from") + "from".length);
setQuery(Z.split(" ")[1]);
};
return (
<div className="col-start-2 col-end-3 row-start-2 row-end-3 m-6">
<AceEditor
aria-label="query editor input"
mode="mysql"
theme="github"
name={Math.floor(Math.random() * 100000).toString()}
fontSize={16}
minLines={15}
maxLines={10}
width="100%"
showPrintMargin={false}
showGutter
placeholder="Write your Query here..."
editorProps={{ $blockScrolling: true }}
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
enableSnippets: true,
}}
value={value}
onChange={onChange}
showLineNumbers
/>
<div className="">
<button
className="bg-white text-gray-800 rounded-md font-semibold px-4 py-2 my-4"
onClick={onSubmit}
>
<i className="fas fa-play"></i> Run SQL
</button>
</div>
</div>
);
};
export default Editor;
Hook code:
import { useEffect, useState } from "react";
import alasql from "alasql";
import toast from "react-hot-toast";
import TABLE_NAMES from "../utils/tableNames";
const getURL = (name) =>
`https://raw.githubusercontent.com/graphql-compose/graphql-compose-examples/master/examples/northwind/data/csv/${name}.csv`;
const useData = (tableName) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [runtime, setRuntime] = useState("");
const convertToJson = (data) => {
alasql
.promise("SELECT * FROM CSV(?, {headers: false, separator:','})", [data])
.then((data) => {
setData(data);
toast.success("Query run successfully");
})
.catch((e) => {
toast.error(e.message);
});
};
const fetchData = (tableName) => {
setData([]);
const name = TABLE_NAMES.find((name) => name === tableName);
if (name) {
setError(false);
fetch(getURL(tableName))
.then((res) => res.text())
.then((data) => convertToJson(data));
} else {
setError(true);
toast.error("Please enter a valid query");
}
};
useEffect(() => {
let t0 = performance.now(); //start time
fetchData(tableName);
let t1 = performance.now(); //end time
setRuntime(t1 - t0);
console.log(
"Time taken to execute add function:" + (t1 - t0) + " milliseconds"
);
}, [tableName]);
return { data, runtime, error };
};
export default useData;
If you want to create a context and use it wherever you want, you can create a context, and add the state in this component and pass it to the value prop in the Provider component.
See the sample code.
import React, { createContext, useState } from "react";
export const UserContext = createContext({});
export interface User {
uid: string;
email: string;
}
export const UserProvider = ({ children }: any) => {
const [user, setUser] = useState<User>();
// you can defined more hooks at here
return (
// Pass the data to the value prop for sharing data
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
Then wrap components with the provider function like this
<UserProvider>
<MyComponment1>
</MyComponment1>
<MyComponment2>
</MyComponment2>
<MyComponment3>
</MyComponment3>
</UserProvider>
At This time, Whatever Component in the UserProvider can access the context right now and you can use useContext hook to access the data that you pass in the value props
export const MyComponment1 = () => {
const { user, setUser } = useContext<any>(UserContext);
...
}

I am using useEffect to hit api and return some response to display it.why this error is happening

I am using useEffect to hit an api and display some data from the response.It works well in console but when i try to display the data in a component it throws an error.I am checking for the loading state though.I am showing the data after a i get a response then where does this null coming from
App.js file:
import { useState, useEffect } from 'react';
import Details from './components/Details/Details';
import Header from './components/Header/Header';
import GlobalStyle from './globalStyles';
const API_KEY = 'Private';
// const URL = `https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`;
function App() {
const [ip, setIp] = useState('8.8.8.8');
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(
`https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`
);
const json = await res.json();
setResponse(json);
setIsLoading(false);
} catch (error) {
setError(error);
}
};
fetchData();
// return { response, error, isLoading };
}, [ip]);
return (
<>
<GlobalStyle />
<Header getIp={(q) => setIp(q)} />
<Details isLoading={isLoading} res={response} error={error} />
</>
);
}
export default App;
Header.js file:
import { useState } from 'react';
import { FaArrowRight } from 'react-icons/fa';
import React from 'react';
import { Form, FormInput, Head, HeadLine, Button } from './Header.elements';
// import { useFetch } from '../../useFetch';
const Header = ({ getIp }) => {
const [input, setInput] = useState('');
const onChange = (q) => {
setInput(q);
getIp(q);
};
return (
<>
{/* styled components */}
<Head>
<HeadLine>IP Address Tracker</HeadLine>
<Form
onSubmit={(e) => {
e.preventDefault();
onChange(input);
setInput('');
}}
>
<FormInput
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
placeholder='Search for any IP address or Domain'
/>
<Button type='submit'>
<FaArrowRight />
</Button>
</Form>
</Head>
</>
);
};
export default Header;
Details.js file:
import React from 'react';
import { Box, Location } from './Details.elements';
const Details = ({ res, error, isLoading }) => {
console.log(res);
return isLoading ? (
<div>loading...</div>
) : (
<>
<Box>
<Location>{res.location.city}</Location>
</Box>
</>
);
};
export default Details;
the error it shows:
That happens because on the first render, Details component will receive isLoading=false and res=null, so it will try to render the box so it's throwing the error.
You can initialize isLoading as true.
const [isLoading, setIsLoading] = useState(true);
Or render the Location if res has some value.
<Box>
{res && <Location>{res.location.city}</Location>}
</Box>
According to React documentation :
https://reactjs.org/docs/hooks-reference.html
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.
So your component is rendering at least once with isLoading as false before even the API call starts.
You have two choices here:
Set isLoading initial value to true
Add optional chaining res?.location.city
https://codesandbox.io/s/stackoverflow-67755606-uuhqk

Reducer/Context Api

So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example

Categories