change variable value with axios, useeffect, and usestate - javascript

i'm newbie here, i'm stuck. i want to change value from false to true, to stop shimmering when data sucessfully to load.
i have action like this
import axios from "axios";
import { CONSTANT_LINK } from "./constants";
import { GET } from "./constants";
import { ERROR } from "./constants";
import { connect } from 'react-redux';
export const addData = () => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
dispatch(addDataSuccess(res.data));
})
.catch((err) => {
dispatch(errorData(true));
console.log("error");
});
};
};
const addDataSuccess = (todo) => ({
type: GET,
payload: todo,
});
const errorData = (error) => ({
type: ERROR,
payload: error,
});
and this is my homepage which influential in this matter
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setShimmerValue(true)
dispatch(addData());
}, []);
<ShimmerPlaceholder visible={shimmerValue} height={20}>
<Text style={styles.welcomeName}>Welcome,Barret</Text>
</ShimmerPlaceholder>
i dont understand how it works

You can pass callback like this
const [shimmerValue, setShimmerValue] = useState(false);
const updateShimmerValue = () => {
setShimmerValue(true);
}
useEffect(() => {
// setShimmerValue(true) // remove this from here
dispatch(addData(updateShimmerValue)); // pass callback as param here
}, []);
Callback call here like
export const addData = (callback) => {
return (dispatch) => {
axios
.get(CONSTANT_LINK)
.then((res) => {
....
callback(); // trigger callback like this here
})
.catch((err) => {
....
});
};
};

you can use it:
const [shimmerValue, setShimmerValue] = useState(false)
useEffect(() => {
setState(state => ({ ...state, shimmerValue: true }));
dispatch(addData());
}, [shimmerValue]);

Related

Why useEffect() didn't report the warning when it performed an unmounted component?

I followed a Reat.js teching video by a YouTube uploarder The Net Ninjia YouTube
In this video, the author indecated a runtime warning:Warning:Can't perform a React state update on an unmounted component.
But according to the following code I can't reproduce this waring.My code works just fine.
So what happened to this following code? Is it because React.js updated something?
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// const abortCont = new AbortController();
setTimeout(() => {
fetch(url)
.then((res) => {
// console.log(res);
if (!res.ok) {
throw Error('could not fetch data for some reason');
} else {
return res.json();
}
})
.then((res) => {
setData(res);
setIsPending(false);
setError(null);
})
.catch((err) => {
console.log(err);
});
}, 2000);
}, [url]);
return { data, isPending, error };
};
export default useFetch;
However, I followed the video, changed the code as below, it raised a AbortError.But according to the author, this error won't happen.
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isPending, setIsPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
setTimeout(() => {
fetch(url, { signal: abortCont.signal })
.then((res) => {
if (!res.ok) {
throw Error('could not fetch data for some reason');
} else {
return res.json();
}
})
.then((res) => {
setData(res);
setIsPending(false);
setError(null);
})
.catch((err) => {
if (err.name === 'AbortError') {
console.log(err.name);
} else {
setIsPending(false);
setError(err.message);
}
});
}, 2000);
return () => abortCont.abort();
}, [url]);
return { data, isPending, error };
};
export default useFetch;

Updating useReducer state from another function - React Pagination

I'm making a movie and I want to add pagination in the MoviesFromGenre component. Initially, MoviesFromGenre gets rendered based on the id from GenresList.
I want to add Pagination but I don't know how to update the state in useReducer when I click next/prev buttons?
import React, { useEffect, useReducer, useState } from 'react';
import { useParams } from 'react-router-dom'
import axios from 'axios';
import { API_URL, API_KEY } from '../api/config.js';
import Movies from './Movies'
const initialState = {
loading: true,
error: '',
movies: []
};
const reducer = (state, action ) => {
switch(action.type) {
case 'FETCH_SUCCESS':
return {
loading: false,
movies: action.payload,
error: '',
};
case 'FETCH_ERROR':
return {
loading: false,
movies: [],
error: 'Error'
};
default:
return state;
}
};
function MoviesFromGenre () {
const [state, dispatch] = useReducer(reducer, initialState);
const { id } = useParams();
const [pageNumber, setPageNumber] = useState(1)
useEffect(() => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}`
)
.then(response => {
dispatch({
type: 'FETCH_SUCCESS',
payload: response.data
})
})
.catch(err => {
dispatch({
type: 'FETCH_ERROR'
})
})
}, [])
const nextPage = () => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
console.log(response.data)
})
setPageNumber(pageNumber+1)
}
const prevPage = () => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
console.log(response.data)
})
setPageNumber(pageNumber-1)
}
return (
<div>
<Movies state={state}/>
<button onClick={prevPage}>Prev</button>
<button onClick={nextPage}>Next</button>
</div>
)
}
export default MoviesFromGenre;
I created a repository on GitHub.
I want to update the movies state when I click on next or prev buttons.
A Reddit user managed to solve my problem.
He suggested that I include pageNumber as a dependency in my useEffect hook, so it will run whenever pageNumber changes.
function MoviesFromGenre () {
const [state, dispatch] = useReducer(reducer, initialState);
const { id } = useParams();
const [pageNumber, setPageNumber] = useState(1)
useEffect(() => {
axios
.get(
`${API_URL}discover/movie?api_key=${API_KEY}&language=en-US&with_genres=${id}&page=${pageNumber}`
)
.then(response => {
dispatch({
type: 'FETCH_SUCCESS',
payload: response.data
})
})
.catch(err => {
dispatch({
type: 'FETCH_ERROR'
})
})
}, [pageNumber])
const nextPage = () => {
setPageNumber(pageNumber+1)
}
const prevPage = () => {
setPageNumber(pageNumber-1)
}
//....
}

Reuse my custom hook `useFetch` in React?

I've created a simple useFetch custom hook which allows me to call any Url I want :
import React, { useState, useEffect } from 'react';
export default function useFetch(url) {
console.log(url)
const [data, setData] = useState([]);
useEffect(() => {
fetch(url)
.then((response) => {
if (response.ok) return response.json();
setData([]);
})
.then((data) => {
setData(data)})
.catch((err) => {
console.error(err);
setData([]);
});
}, [url]);
return { data} ;
}
In my Main component I'm loading a static list of items.( via useEffect with [] becuase it's static)
I currently do it via :
export function Courses() {
const [langs, setLangs] = useState([]);
useEffect(() => {
getData(config.url).then((f) => setLangs(f));
}, []);
...
Where getData is:
export function getData(uri) {
return fetch(uri).then(response =>
response.json()
);
}
The problem is that I can't (don't know how) I can use my useFetch here becuase it can't be inside useEffect , and that's why I've created the additional getData method.
ps -
In other "details" component I use useFetch perfectly fine :
export default function Details({ langId }) {
const { data: teachers } = useFetch(`${config.url}/${langId}`);
...
The problem is only in the main component where I don't want to fetch manually . I want to use my useFetch. How can I do that ?
I want that Courses will load the static list only once via useFetch
One possible option is to cache the fetch response (I didn't test the code)
import React, { useState, useEffect } from 'react';
const cache = {};
export default function useFetch(url, useCache=false) {
console.log(url)
const [data, setData] = useState([]);
useEffect(() => {
if (useCache && cache[url]) {
setData(cache[url]);
} else {
fetch(url)
.then((response) => {
if (response.ok) {
const responseData = await response.json();
if (useCache) cache[url] = responseData;
return responseData;
}
setData([]);
})
.then((data) => {
setData(data)})
.catch((err) => {
console.error(err);
setData([]);
});
}
}, [url]);
return { data} ;
}
You can use useRef if you want to keep the hooks as a pure function

[Redux][Axios][React] Adding redux state inside of a axios / action file

I want to add/change a redux state when the data is received from the backend. This state controls a loading spinner.
The code below is what I thought that should work.
What am I missing?
CouriersActions.js
import axios from "axios";
import { toastOnError } from "../../utils/Utils";
import { GET_COURIERS, ADD_STATE_LOADING } from "./CouriersTypes";
export const addStateLoading = (state_loading) => ({
type: ADD_STATE_LOADING,
state_loading,
});
export const getCouriers = () => dispatch => {
var tempx = {show: true};
addStateLoading(tempx);
axios
.get("/api/v1/couriers/")
.then(response => {
dispatch({
type: GET_COURIERS,
payload: response.data
});
var tempx = {show: false};
addStateLoading(tempx);
})
.catch(error => {
toastOnError(error);
});
};
A simple way of solving this kind issue, create custom hook for all services and where ever you need it.
export const useCouriers = () => {
const dispatch = useDispatch();
const getCouriers = async () => {
try {
dispatch(addStateLoading({ show: true }));
const response = await axios.get("/api/v1/couriers/");
dispatch({
type: GET_COURIERS,
payload: response.data,
// I think this should be response.data.data
});
} catch (error) {
toastOnError(error);
} finally {
dispatch(addStateLoading({ show: false }));
}
};
return { getCouriers };
};
Inside component
const { getCouriers } = useCouriers();
// call where you need
If you want to use redux, check redux-toolkit, it helps a lot the development with redux.
https://redux-toolkit.js.org/
#rahul-sharma answer helped me to find this answer. I just called addStateLoading inside of dispatch.
CouriersActions.js
import axios from "axios";
import { toastOnError } from "../../utils/Utils";
import { GET_COURIERS, ADD_STATE_LOADING } from "./CouriersTypes";
export const addStateLoading = (state_loading) => ({
type: ADD_STATE_LOADING,
state_loading,
});
export const getCouriers = () => dispatch => {
var tempx = {show: true};
addStateLoading(tempx);
axios
.get("/api/v1/couriers/")
.then(response => {
dispatch({
type: GET_COURIERS,
payload: response.data
});
dispatch(addStateLoading({ show: false }));
})
.catch(error => {
toastOnError(error);
});
};

Not able to mock a function inside useEffect

I have a custom hook as below
export const useUserSearch = () => {
const [options, setOptions] = useState([]);
const [searchString, setSearchString] = useState("");
const [userSearch] = useUserSearchMutation();
useEffect(() => {
if (searchString.trim().length > 3) {
const searchParams = {
orgId: "1",
userId: "1",
searchQuery: searchString.trim(),
};
userSearch(searchParams)
.then((data) => {
setOptions(data);
})
.catch((err) => {
setOptions([]);
console.log("error", err);
});
}
}, [searchString, userSearch]);
return {
options,
setSearchString,
};
};
and I want to test this hook but am not able to mock userSearch function which is being called inside useEffect.
can anybody help?
this is my test
it('should set state and test function', async () => {
const wrapper = ({ children }) => (
<Provider store={store}>{children}</Provider>
)
const { result } = renderHook(
() => useUserSearch(),
{ wrapper }
)
await act(async () => {
result.current.setSearchString('abc5')
})
expect(result.current.options).toEqual(expected)
})
useUserSearchMutation
import {createApi, fetchBaseQuery} from '#reduxjs/toolkit/query/react';
export const userSearchAPI = createApi({
reducerPath: 'userSearchResult',
baseQuery: fetchBaseQuery({baseUrl: process.env.REACT_APP_BASE_URL}),
tagTypes: ['Users'],
endpoints: build => ({
userSearch: build.mutation({
query: body => ({url: '/org/patient/search', method: 'POST', body}),
invalidatesTags: ['Users'],
}),
}),
});
export const {useUserSearchMutation} = userSearchAPI;
Because it's a named export you should return an object in the mock
it("should set state and test function", async () => {
jest.mock("./useUserSearchMutation", () => ({
useUserSearchMutation: () => [jest.fn().mockResolvedValue(expected)],
}));
const wrapper = ({ children }) => (
...
});
I have created a smaller example based on your code, where I am mocking a hook inside another hook.
hooks/useUserSearch.js
import { useEffect, useState } from "react";
import useUserSearchMutation from "./useUserSearchMutation.js";
const useUserSearch = () => {
const [text, setText] = useState();
const userSearch = useUserSearchMutation();
useEffect(() => {
const newText = userSearch();
setText(newText);
}, [userSearch]);
return text;
};
export default useUserSearch;
hooks/useUSerSearchMutation.js
I had to move this to its own file to be able to mock it when it was called
inside of the other hook.
const useUserSearchMutation = () => {
return () => "Im not mocked";
};
export default useUserSearchMutation;
App.test.js
import { render } from "react-dom";
import useUserSearch from "./hooks/useUserSearch";
import * as useUserSearchMutation from "./hooks/useUserSearchMutation";
import { act } from "react-dom/test-utils";
let container;
beforeEach(() => {
// set up a DOM element as a render target
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
// cleanup on exiting
document.body.removeChild(container);
container = null;
});
function TestComponent() {
const text = useUserSearch();
return <div>{text}</div>;
}
test("should mock userSearch", async () => {
const mockValue = "Im being mocked";
jest
.spyOn(useUserSearchMutation, "default")
.mockImplementation(() => () => mockValue);
act(() => {
render(<TestComponent />, container);
});
expect(container.textContent).toBe(mockValue);
});

Categories

Resources