React Context loosing state on page refresh - reactjs

So basicaly i have a subreddit context where i get a bunch of subreddis from the api
import axios from "axios";
import { createContext, useCallback, useEffect, useState } from "react";
import { getUniqueObjects } from "../Helpers/Helpers";
import { ChildrenType } from "../Types/ProviderChildrenType";
import { Subreddit, SubredditsResponse } from "../Types/Subreddits";
type InitialState = {
subredditsData?: Subreddit[];
subredditsLoading?: boolean;
subredditsError?: boolean;
subredditsHasMore?: boolean;
getSubreddits?: (arg0: string) => void;
};
export const SubredditContext = createContext<InitialState>({});
export const SubredditContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Subreddit[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [hasMore, setHasMore] = useState(true);
const UseSubreddits = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response: SubredditsResponse) => {
setData(getUniqueObjects([...data, ...response.data]));
setHasMore(response.data.length > 0);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => apiCall(), [apiCall]);
};
return (
<SubredditContext.Provider
value={{
subredditsData: data,
subredditsLoading: loading,
subredditsError: error,
subredditsHasMore: hasMore,
getSubreddits: UseSubreddits,
}}
>
{children}
</SubredditContext.Provider>
);
};
In my home page I trigger the custom hook of the context "UseSubreddits" which I pass it as the "getSubreddits" prop,
function Homepage() {
const navigate = useNavigate();
const [pageNumber, setPageNumber] = useState(1);
const {
subredditsData,
subredditsLoading,
subredditsError,
subredditsHasMore,
getSubreddits,
} = useContext(SubredditContext);
getSubreddits!(`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits?page=${pageNumber}&limit=16`)
window.addEventListener("scroll", () => {
if (
window.scrollY + window.innerHeight >=
document.documentElement.scrollHeight &&
subredditsHasMore
) {
setPageNumber(pageNumber + 1);
}
});
return (
<>
<Navbar pageTitle="subreddits" />
<div className="homepage">
<div className="homepage__subreddits">
{subredditsData?.map((item) => {
return (
<div key={item.id}>
<SubredditCard
key={item.id}
onClick={() => navigate(`/posts/${item.id}`)}
title={item.title}
description={item.description}
/>
</div>
);
})}
</div>
</div>
<div className="homepage__text">
{subredditsLoading && <h2>Loading...</h2>}
{subredditsError && (
<h2>An error has occured please refresh your page.</h2>
)}
</div>
</>
);
}
export default Homepage;
I have the same kind of context file where I get the posts of the selected subreddit
type InitialState = {
postData?: Post[];
postLoading?: boolean;
postError?: boolean;
getPost?: (arg0: string) => void;
voteHandler?: (
arg0: string,
arg1: string,
arg2: boolean,
arg3: boolean
) => void;
};
export const PostContext = createContext<InitialState>({});
export const PostContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const UsePosts = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => {
apiCall();
}, [apiCall]);
};
return (
<PostContext.Provider
value={{
postData: data,
postLoading: loading,
postError: error,
getPost: UsePosts,
}}
>
{children}
</PostContext.Provider>
);
};
and then I do the same thing in the Post component as I do in the Homepage
function Posts() {
const { subredditId } = useParams();
const [urlParam, setUrlParam] = useState("");
const {
postData,
postLoading,
postError,
getPost,
voteHandler,
} = useContext(PostContext);
const { subredditsData } = useContext(SubredditContext);
const selectedSubreddit = useMemo(
() => subredditsData!.find((subreddit) => subreddit.id === subredditId),
[subredditsData]
);
const navigate = useNavigate();
const sortByTitle = "?sortBy=title";
getPost!(
`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits/${subredditId}/posts${urlParam}`
);
return (
<>
<Navbar pageTitle={selectedSubreddit!.title} />
<div className="posts-screen">
<div className="posts-screen__left-panel">
<div className="posts-screen__left-panel-content">
<SortBy onClick={() => setUrlParam(sortByTitle)} />
<div className="posts-screen__posts-container">
{postData?.map((post) => {
return (
<PostCard
key={post.id}
id={post.id}
title={post.title}
description={post.body}
user={post.user}
voteCount={post.upvotes - post.downvotes} ...
Everything works fine except that in the post screen if I reload the page the subreddit state is lost and the posts screen gives and error. I know that the state is lost on refresh but shouldn't it make the api call again for the subreddits?
I'm new at Context so I don't know how to handle it better

Related

Reactjs, typescript

I am trying to make Searchbar where I can fetch the authors and show the author Id and picture, Why I'm getting this error (any Property 'toLowerCase' does not exist on type 'never') in my code?
import { useEffect, useState } from 'react';
import axios from 'axios';
function Searchbar() {
const [query, setQuery] = useState('');
const [data, setData] = useState([]);
// Add one more state to store the authors being searched for
const [searchResults, setSearchResults] = useState([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get(`https://picsum.photos/v2/list`);
setData(res.data);
};
fetchData();
}, []);
useEffect(() => {
setSearchResults(
data.filter((authorData) =>
authorData['author'].toLowerCase().includes(query)
)
);
}, [query, data]);
return (
<div className='app'>
<input
className='search'
placeholder='Search...'
onChange={(e) => setQuery(e.target.value.toLowerCase())}
/>
<div>
{searchResults.map((author) => (
<div>{author}</div>
))}
</div>
</div>
);
}
export default Searchbar;
The problem seems to be you are relying on typescript to infer types of an api call. (Instead typescript inferred it as never which would always error if you try anything with it)
To fix this, simply set a type for the fetch data
type fetchData = {
id: number,
author: string,
width: number,
height: number,
url: string,
download_url: string
}
Then set your states as such
const [data, setData] = useState<fetchData[]>([]);
const [searchResults, setSearchResults] = useState<fetchData[]>([]);
Full code
import { useEffect, useState } from "react";
import axios from "axios";
type fetchData = {
id: number,
author: string,
width: number,
height: number,
url: string,
download_url: string
}
function Searchbar() {
const [query, setQuery] = useState("");
const [data, setData] = useState<fetchData[]>([]);
// Add one more state to store the authors being searched for
const [searchResults, setSearchResults] = useState<fetchData[]>([]);
useEffect(() => {
const fetchData = async () => {
const res = await axios.get(`https://picsum.photos/v2/list`);
setData(res.data);
};
fetchData();
}, []);
useEffect(() => {
setSearchResults(
data.filter((authorData : fetchData) =>
authorData.author.toLowerCase().includes(query)
)
);
}, [query, data]);
return (
<div className="app">
<input
className="search"
placeholder="Search..."
onChange={(e) => setQuery(e.target.value.toLowerCase())}
/>
<div>
{searchResults.map((author) => (
<div key={author.id}>{author.author}</div>
))}
</div>
</div>
);
}
export default Searchbar;

React Hooks setState function is not a function error

I'm getting this error in React Hooks. The function exists but every time I type something in to the search bar I get this TypeError.
TypeError : setSearchField is not a function
Here's the code for reference :
export default function StudentAPI() {
const [searchField, setSearchField] = ('');
const [students, setStudents] = useState([]);
const getStudents = async () => {
return axios
.get("https://api.hatchways.io/assessment/students")
.then((res) => {
setStudents(res.data.students);
})
.catch((err) => console.log(err));
};
useEffect(() => {
getStudents();
}, []);
const handleChange = (e) => {
setSearchField(e.target.value);
}
const filteredStudents = students.filter((student) => {
console.log(student.firstName);
// return student.firstName.toLowerCase().includes(search.toLowerCase()) ||
// student.lastName.toLowerCase().includes(search.toLowerCase());
})
return (
<div className="container">
<SearchBox
placeholder={'Search by name'}
handleChange={handleChange}
value={searchField}
/>
{filteredStudents.length > 0 ? filteredStudents.map((student) => {
return <Student key={student.id} student={student}/>;
}) : students.map((student) => {
return <Student key={student.id} student={student}/>;
})}
</div>
);
};
You have to use the hook useState
const [searchField, setSearchField] = usestate('');
You must have the state declaration above
const [searchField,setSearchField]=useState()
You have an error because useState is not written!
You must change
const [searchField, setSearchField] = ('');
to
const [searchField, setSearchField] = useState('');

Fetching data with input parameter with SWR Hook

function DayOne() {
const [country, setCountry] = useState("");
const url = `${process.env.REACT_APP_BASE_URL}/dayone/all/total/country/${country}`;
const { data, error } = useSWR(url, fetcher);
let value = useRef("");
const onClick = async (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
return setCountry(value.current);
};
const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
value.current = e.target.value;
};
let index = 1;
if (error) return <div>failed to load</div>;
if (!data) return <Loading />;
const { name } = data;
return (
<div>
{console.log(data)}
<ContainerComp>
<NavBar />
{console.log(country)}
<InputCountryForm myRef={value} onChange={onChange} onClick={onClick} />
<div>{country}</div>
{name &&
name.map((n: IDayOne) => {
<CustomCard icon={""} title={country} value={n.Active} />;
})}
</ContainerComp>
</div>
);
}
export default DayOne;
the fetcher
export const fetcher = (url: string) => fetch(url).then((res) => res.json());
Im trying to display a card list with values coming after picking a country a submitting to the endpoint. How can i improve this and actually make it work ?

Set an empty array in state

In my application I have a initial state that start with some values.
In a process, I need to change this state for a empty array, (that is a return of my Api).
I am trying do this, but the value of the state don't change.
What can I do?
My code
import React, {useEffect, useState} from "react";
import "./style.scss";
import Services from "../../services/Services";
export default function Acoplamento({history, match}) {
const [veiculos, setVeiculos] = useState([]);
const [loading, setLoading] = useState(false);
const [type, setType] = useState(1);
const getAllVeiculos = async () => {
setLoading(true);
await Services.VeiculoServices.getAll().then(result => {
setVeiculos(result.data); // Here, i have a array with some objects
}).catch(error => {
error(error.message);
}).finally(() => setLoading(false));
return true;
}
const getOneVeiculos = async () => {
setLoading(true);
await Services.VeiculoServices.get().then(result => {
setVeiculos(result.data); // Here, my return is a empty array, but my state don't chage
}).catch(error => {
error(error.message);
}).finally(() => setLoading(false));
return true;
}
useEffect(() => {
if (type === 1) {
getAllVeiculos();
}
if (type === 2) {
getOneVeiculos();
}
}, [type]);
return (
<div>
<button onClick={() => setType(2)}>Click</button>
{veiculos.map(item => (
<div>{item.name}</div>
))}
</div>
);
}
I guess the problem arises because you did not convert the incoming data to json format. It can solve the following code problem.
import React, {useEffect, useState} from "react";
import "./style.scss";
import Services from "../../services/Services";
export default function Acoplamento({history, match}) {
const [veiculos, setVeiculos] = useState([]);
const [loading, setLoading] = useState(false);
const [type, setType] = useState(1);
const getAllVeiculos = async () => {
setLoading(true);
const response = await Services.VeiculoServices.getAll();
const result = await response.json();
if (result.data) setVeiculos(result.data);
setLoading(false);
}
const getOneVeiculos = async () => {
setLoading(true);
const response = await Services.VeiculoServices.get();
const result = await response.json();
if (result.data) setVeiculos(result.data);
setLoading(false);
}
useEffect(() => {
if (type === 1) {
getAllVeiculos();
}else if (type === 2) {
getOneVeiculos();
}
}, [type]);
return (
<div>
<button onClick={() => setType(2)}>Click</button>
{veiculos.map(item => (
<div>{item.name}</div>
))}
</div>
);
}

React Load More using Hooks

I try to do Load More on a list of data as written below:
import React, { useState, useEffect } from "react";
import { render } from "react-dom";
import axios from "axios";
import "./style.css";
const App = () => {
const LIMIT = 2;
const [data, setData] = useState([]);
const [isLoading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const loadData = async (skip = 1, limit = LIMIT) => {
const URL = "https://reqres.in/api/users";
const headers = {
"Content-Type": "application/json",
Accept: "application/json"
};
const params = {
page: skip,
per_page: limit
};
const a = await axios.get(URL, { params, headers });
// const b = [...new Set([...data, ...a.data.data])]; <-- setting this will thrown error
setData(a.data.data);
setLoading(false);
};
useEffect(() => {
setLoading(true);
loadData(page);
}, [page]);
useEffect(() => {
console.log("page", page, "data", data.length);
}, [page, data]);
const doReset = evt => {
evt.preventDefault();
setPage(1);
};
const doLoadMore = evt => {
evt.preventDefault();
setPage(page + 1);
};
return (
<div className="container">
<h1>Listing</h1>
<button className="btn text-primary" onClick={evt => doReset(evt)}>
Reset
</button>
<button className="btn text-primary" onClick={evt => doLoadMore(evt)}>
Load More
</button>
{isLoading && <p>Loading..</p>}
{!isLoading && (
<ul>
{data.map(a => (
<li key={a.id}>
{a.id}. {a.email}
</li>
))}
</ul>
)}
</div>
);
};
render(<App />, document.getElementById("root"));
a fully working example in here.
i think this code should be working, but is not.
const a = await axios.get(URL, { params, headers });
const b = [...new Set([...data, ...a.data.data])];
setData(b);
so please help, how to do Load More in React Hooks?
after a few try, i think this is the best thing i can do. make the code working but also not let the compiler warning:
import React, { useState, useEffect, useCallback } from "react";
import axios from "axios";
import Navbar from "./Navbar";
const App = () => {
const LIMIT = 2;
const [tube, setTube] = useState([]);
const [data, setData] = useState([]);
const [isLoading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const loadData = useCallback(
async (limit = LIMIT) => {
setLoading(true);
const URL = "https://reqres.in/api/users";
const headers = {
"Content-Type": "application/json",
Accept: "application/json"
};
const params = {
page,
per_page: limit
};
const a = await axios.get(URL, { params, headers });
if (!a.data.data) {
return;
}
setData(a.data.data);
setLoading(false);
},
[page]
);
useEffect(() => {
if (!isLoading) {
return;
}
setTube([...new Set([...tube, ...data])]);
}, [data, isLoading, tube]);
useEffect(() => {
loadData();
}, [loadData]);
useEffect(() => {
console.log("page", page, "data", data.length);
}, [page, data]);
const doLoadMore = evt => {
evt.preventDefault();
setPage(page + 1);
};
return (
<>
<Navbar />
<main role="main" className="container">
<div className="starter-template text-left">
<h1>Listing</h1>
<button className="btn text-primary" onClick={evt => doLoadMore(evt)}>
Load More
</button>
<ul>
{tube &&
tube.map(a => (
<li key={a.id}>
{a.id}. {a.email}
</li>
))}
</ul>
{isLoading && <p>Loading..</p>}
</div>
</main>
</>
);
};
export default App;
also i found, it could be much easier just apply this eslint-disable-next-line react-hooks/exhaustive-deps to let the compiler ignore the warning. something like this.
useEffect(() => {
setConfig({...config, params: {...params, skip}});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [skip]);
for information can be found on this:
how-to-fix-missing-dependency-warning-when-using-useeffect-react-hook
https://stackoverflow.com/a/55844055/492593
react #14920
I got your example to work by changing to this:
const b = [...data, ...a.data.data];
setData(b);

Resources