React Hooks setState function is not a function error - reactjs

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('');

Related

React Context loosing state on page refresh

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

React HOC Using useEffect()

So i am using a HOC for general error handling purposes in react like this:
import React, { useState, useEffect } from 'react'
import Modal from '../../UI/Modal/Modal'
const WithErrorHandler = (WrappedComponent, axios) => {
const NewComponent = props => {
console.log('UseState')
const [error, setError] = useState(null)
console.log('runs')
useEffect(() => {
const req = axios.interceptors.request.use(config => {
console.log('request intercepted')
return config
})
const res = axios.interceptors.response.use(null, error => {
setError(error)
return Promise.reject(error)
})
return () => {
axios.interceptors.request.eject(req)
axios.interceptors.response.eject(res)
}
}, [])
return (
<div>
{console.log('render')}
{error ? (
<Modal clickHandler={() => setError(null)}> {error.message}</Modal>
) : null}
<WrappedComponent {...props} />
</div>
)
}
return NewComponent
}
export default WithErrorHandler
The problem i have run into is that i have a component which fires an axios request in it's useEffect().
When i try to wrap this component with my WithErrorHandler the useEffect of the wrapped component fires first then the useEffect of HOC withErrorHandler runs. This causes the axios request to be made faster than the HOC could register the axios interceptors. Any ideas on how to fix this would be aprreciated.
You can define an intermediate state which prevents from rendering wrapped component.
const WithErrorHandler = (WrappedComponent, axios) => {
const NewComponent = (props) => {
const [ready, setReady] = useState(false); // HERE
console.log("UseState");
const [error, setError] = useState(null);
console.log("runs");
useEffect(() => {
const req = axios.interceptors.request.use((config) => {
console.log("request intercepted");
return config;
});
const res = axios.interceptors.response.use(null, (error) => {
setError(error);
return Promise.reject(error);
});
setReady(true); // HERE
return () => {
axios.interceptors.request.eject(req);
axios.interceptors.response.eject(res);
};
}, []);
if (!ready) return null; // HERE
return (
<div>
{console.log("render")}
{error ? (
<Modal clickHandler={() => setError(null)}> {error.message}</Modal>
) : null}
<WrappedComponent {...props} />
</div>
);
};
return NewComponent;
};
What it does is that it makes sure that axios interceptor is initialized and it is good to render wrapped component.
Instead of if (!ready) return null; you can return a more sensible state from your HOC for instance, if (!ready) return <p>Initializing...</p>
You need an extra render for the NewComponent callback to run, adding a conditional rendering on WrappedComponent should do the trick.
Notice that we set isFirstRender on promise success, change it dependenly on your use case.
const WithErrorHandler = (WrappedComponent, axios) => {
const NewComponent = (props) => {
const [isFirstRender, setIsFirstRender] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (isFirstRender) {
const req = axios.interceptors.request.use((config) => {
return config;
});
// Check req success
if (req.isSuccess) { setIsFirstRender(false); }
const res = axios.interceptors.response.use(null, (error) => {
setError(error);
return Promise.reject(error);
});
return () => {
axios.interceptors.request.eject(req);
axios.interceptors.response.eject(res);
};
}
}, [isFirstRender]);
return (
<div>
{error ? (
<Modal clickHandler={() => setError(null)}> {error.message}</Modal>
) : null}
{!isFirstRender && <WrappedComponent {...props} />}
</div>
);
};
return NewComponent;
};

React useState hook not fucntioning properly

EDIT:
By console.loging outside the async function, I can view the data. I am also trying to pass this data through a Redirect. I skipped a lot of part initially, so here is the full function:
const SearchForm = () => {
const [keyword, setKeyword] = useState('')
const [fetchedData, setFetchedData] = useState([])
const [redirect, setRedirect] = useState(false)
async function fetchData() {
const { data } = await axios.post('http://127.0.0.1:8000/api/posts/search/', {keyword});
setFetchedData(data);
}
const handleSubmit = e => {
e.preventDefault();
fetchData();
setRedirect(true);
}
if (redirect) {
return <Redirect to={{ pathname: '/search', fetchedData }} />
}
return (
<div>
<form onSubmit={ handleSubmit }>
<div className='input-field'>
<input placeholder="Search whatever you wish"
type="text"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
</div>
</form>
</div>
)
}
The fetchedData is not being passed through the Redirect. When I check, its just an empty array (and that is what the initial value was).
This setFetchedData() is an asynchronous process. Try using with a callback to get the right result:
setFetchedData(data, () => {
console.log(fetchedData)
});
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script></script>
<div id="root"></div>
<script type="text/babel">
const { useState } = React
const App = () => {
const [keyword, setKeyword] = useState('Change in a sec...');
const [fetchedData, setFetchedData] = useState([]);
setTimeout(() => {
setKeyword("Hello, changed!", () => {
console.log(keyword);
});
}, 1000);
return (
<div>
{keyword}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
</script>
setstate in async operation you can find your updated state like this
const SearchForm = () => {
const [keyword, setKeyword] = useState('')
const [fetchedData, setFetchedData] = useState([])
async function fetchData() {
const { data } = await axios.post('http://127.0.0.1:8000/api/posts/search/', { keyword });
setFetchedData(data);
}
console.log(fetchedData)
if (redirect) {
return <Redirect to={{ pathname: '/search', data: { fetchedData } }} />
}
return (
)
in your search component console like this :
this.props.location.data
For calling an async function you should use await. So add an IIFE for the async part within the submit handler.
const SearchForm = () => {
const [keyword, setKeyword] = useState('')
const [fetchedData, setFetchedData] = useState([])
const [redirect, setRedirect] = useState(false)
const handleInputChange = ({ target: { value }}) => {
setKeyword(value)
}
const handleSubmit = (e) => {
e.preventDefault()
;(async => {
const { data } = await axios.post(`http...`, { keyword })
setFetchedData(data)
})()
}
if (redirect) {
return <Redirect to={{ pathname: '/search', fetchedData }} />
}
return (
<div>
<form onSubmit={handleSubmit}>
<input onChange={handleInputChange} />
</form>
</div>
)
}
Try this,
function fetchData() {
axios.post('http://127.0.0.1:8000/api/posts/search/', {keyword}).then(data=>{
setFetchedData(data);
console.log(fetchedData)
});

React Hooks state not updating variable in view

I'm trying to set a state, which I fetch from an API in the form of an array.
Tried this in every way possible, doesn't work.
Any ideas on how to fix this?
instance is an axios.create that creates the instance to a localhost django server which has CORS-ALLOW-CROSS-ORIGIN True
import React, { useState } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const response = await instance.get(`/one/list/`);
setOne(response.data);
};
fetchText();
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
Do it like this,
import React, { useState } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const response = await instance.get(`/one/list/`);
setOne(response.data);
};
useEffect(() => {
fetchText();
},[ any variable you want it to fetch that again ]);
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
This looks to be a good use case for a useEffect hook. Also, you need to await async functions within useEffect statement. The useEffect hook cannot be an async function in itself. Also, the original implementation would result in an infinite loop. The setState function would trigger a re-render which would then trigger the fetch function to fire off again. Consider the following:
import React, { useState, useEffect } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const fetchText = async () => {
const request= await instance.get(`/one/list/`);
request.then( r => setOne(r.data))
};
useEffect(() => {
(async () => {
await fetchText();
})();
}, []);
return (
<>
<div>Hello World.</div>
{one.forEach(o => (
<p>o.text</p>
))}
</>
);
};
export default OneList;
Based on you guys' advice, I came up with the below code which works fine so far.
import React, { useState, useEffect } from "react";
import { instance } from "../../stores/instance";
const OneList = () => {
const [one, setOne] = useState([]);
const [el, setEl] = useState(null);
const fetchText = async () => {
let res = await instance.get("/one/list/");
setOne(res.data);
};
useEffect(() => {
(async () => {
await fetchText();
})();
}, []);
useEffect(() => {
const handleClick = e => {
let newEl = document.createElement("input");
newEl.value = e.target.innerHTML;
newEl.id = e.target.id;
newEl.addEventListener("keypress", e => handleInput(e));
newEl.addEventListener("focusout", e => handleInput(e));
e.target.parentNode.replaceChild(newEl, e.target);
newEl.focus();
};
const handleInput = async e => {
console.log(e.type);
if (
e.target.value !== "" &&
(e.key === "Enter" || e.type === "focusout")
) {
let payload = { text: e.target.value };
try {
e.preventDefault();
let res = await instance.put(`/one/update/${e.target.id}`, payload);
let text = res.data.text;
e.target.value = text;
let newEl = document.createElement("span");
newEl.innerHTML = text;
newEl.addEventListener("click", e => handleClick(e));
newEl.id = e.target.id;
e.target.parentNode.replaceChild(newEl, e.target);
} catch (e) {
console.error(e);
}
}
};
setEl(
one.map(o => (
<p>
<span id={o.id} onClick={e => handleClick(e)}>
{o.text}
</span>
</p>
))
);
}, [one]);
return <>{el}</>;
};
export default OneList;

Putting fetch function in a separate component

I'm trying to take out the fetchImages function from the following component and put it inside a new component:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import UnsplashImage from './UnsplashImage';
const Collage = () => {
const [images, setImages] = useState([]);
const [loaded, setIsLoaded] = useState(false);
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
useEffect(() => {
fetchImages();
}, []);
return (
<div className="image-grid">
{loaded
? images.map(image => (
<UnsplashImage
url={image.urls.regular}
key={image.id}
alt={image.description}
/>
))
: ''}
</div>
);
};
export default Collage;
For this, I created a new component called api.js, removed the entire fetchImage function from the above component and put it in to api.js like this:
api.js
const fetchImages = (count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey =
'<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
setImages([...images, ...res.data]);
setIsLoaded(true);
});
};
export default fetchImages;
Next I took setIsLoaded(true); from api.js and paste it inside Collage component like this:
useEffect(() => {
fetchImages();
setIsLoaded(true);
}, []);
Now I can import fetchImages in to Collage component.
However, I don't know what should I do with this line inside the fetchImages function? This needs to go to Collage component, but res.data is not defined inside Collage component.
setImages([...images, ...res.data]);
How should I handle it?
There is many way to do that, but in your case.
You should use
const fetchImages = (afterComplete, count = 10) => {
const apiRoot = 'https://api.unsplash.com';
const accessKey = '<API KEY>';
axios
.get(`${apiRoot}/photos/random?client_id=${accessKey}&count=${count}`)
.then(res => {
console.log(res);
afterComplete(res.data);
});
};
export default fetchImages;
And in your Collage component:
const afterComplete = (resData) =>{
setImages([...images, ...resData]);
setIsLoaded(true);
}
useEffect(() => {
fetchImages(afterComplete);
}, []);
What you can do is create a custom hook ( sort of like a HOC)... Since I don't have an unsplash API key I'll give you an example with a different API but the idea is the same:
Here is your custom hook:
import { useState, useEffect } from 'react';
export const useFetch = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchUser = async () => {
const response = await fetch(url);
const data = await response.json();
const [user] = data.results;
setData(user);
setLoading(false);
};
useEffect(() => {
fetchUser();
}, []);
return { data, loading };
};
Here is how you can use it in your component:
import { useFetch } from './api';
const App = () => {
const { data, loading } = useFetch('https://api.randomuser.me/');
return (
<div className="App">
{loading ? (
<div>Loading...</div>
) : (
<>
<div className="name">
{data.name.first} {data.name.last}
</div>
<img className="cropper" src={data.picture.large} alt="avatar" />
</>
)}
</div>
);
};
Here is a live demo: https://codesandbox.io/s/3ymnlq59xm

Resources