const [searchText, setSearchText] = useState('')
const searchHandler = async (searchTextInput) => {
console.log('search is:::', searchTextInput)
setSearchText(() => searchTextInput)
}
const searchOnSubmitHandler = async () => {
await loadShopsCount()
await loadShops()
}
const searchClearHandler = async () => {
setSearchText(() => "")
console.log('onClear-----------search is:::', searchText)
await loadShopsCount()
}
On calling searchClearHandler after searching for something, searchText returns the previous state value instead of empty string. Hence, my component is not able to re-render.
How can I set searchText to empty string and re-render the component on calling searchClearHandler?
Use useEffect.
useEffect(() => {
Here your function which you want to call.
}, [searchText]);
Related
i've been solving this problem without any progress for the pas 2 hours or so, here is code:
export const useFetchAll = () => {
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value);
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
console.log(searchItem);
};
useEffect(() => {
const searchRepo = async () => {
setLoading(true);
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
if (searchItem) searchRepo();
}, [searchItem]);
the problem is that when i enter characters in input and set state to event.target.value it doesn't pick up last character. here is an image:
enter image description here
BTW this is a custom hook, i return the onchange function here:
const HomePage = () => {
const { searchResult, loading, searchItem, handleChange, listToDisplay } =
useFetchAll();
and then pass it as a prop to a component like so:
<Stack spacing={2}>
<Search searchItem={searchItem} handleChange={handleChange} />
</Stack>
</Container>
any help? thanks in advance.
You are handling the searchItem and searchResult state variables as if their state change was synchronous (via setSearchItem and setSearchResult) but it isn't! React state setters are asynchronous.
The useEffect callback has a dependency on the searchItem state variable. Now every time the user types something, the state will change, that change will trigger a re-rendering of the Component and after that render finishes, the side-effect (the useEffect callback) will be executed due to the Components' lifecycle.
In our case, we don't want to initiate the fetch request on the next render, but right at the moment that the user enters something on the search input field, that is when the handleChange gets triggered.
In order to make the code work as expected, we need some a more structural refactoring.
You can get rid of the useEffect and handle the flow through the handleChange method:
export const useFetchAll = () => {
const [ loading, setLoading ] = useState( false );
const [ searchItem, setSearchItem ] = useState( "" );
const [ listToDisplay, setListToDisplay ] = useState( [] );
const handleChange = async ( e ) => {
const { value } = e.target;
// Return early if the input is an empty string:
setSearchItem( value );
if ( value === "" ) {
return setListToDisplay( [] );
}
setLoading( true );
const { data } = await axios.get( "https://api.github.com/repositories" );
setLoading( false );
const valueLowercase = value.toLowerCase(); // Tiny optimization so that we don't run the toLowerCase operation on each iteration of the filter process below
setListToDisplay(
data.filter(({ name }) => name.toLowerCase().includes(valueLowercase))
);
};
return {
searchItem,
handleChange,
loading,
listToDisplay,
};
};
function used for updating state value is asynchronous that why your state variable is showing previous value and not the updated value.
I have made some change you can try running the below code .
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [searchItem, setSearchItem] = useState("");
const [listToDisplay, setListToDisplay] = useState([]);
// const debouncedSearch = useDebounce(searchItem, 300);
const handleChange = (e) => {
setSearchItem(e.target.value); // this sets value asyncronously
console.log("e.target.value :" + e.target.value); // event.target.value does not omitting last character
console.log("searchItem :" + searchItem); // if we check the value then it is not set. it will update asyncronously
};
const setList = async () => {
if (searchItem === "") {
setListToDisplay([]);
} else {
setListToDisplay(
searchResult.filter((item) => {
return item.name.toLowerCase().includes(searchItem.toLowerCase());
})
);
}
};
const searchRepo = async () => {
const { data } = await axios.get("https://api.github.com/repositories");
setSearchResult(data);
setLoading(false);
};
// this useeffect execute its call back when searchItem changes a
useEffect(() => {
setList(); // called here to use previous value stored in 'searchResult' and display something ( uncomment it if you want to display only updated value )
if (searchItem) searchRepo();
}, [searchItem]);
// this useeffect execute when axios set fetched data in 'searchResult'
useEffect(() => {
setList();
}, [searchResult]);
// this useeffect execute when data is updated in 'listToDisplay'
useEffect(() => {
console.log("filtered Data") // final 'listToDisplay' will be availble here
console.log(listToDisplay)
}, [listToDisplay]);
I have a function that filters through some state and renders out the result for a search request.
const handleSearch = (value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
};
I am trying to work with lodash.throttle library to cause a delay before the request is sent. So we don't have a request go out every time a user types.
const handleSearch = useCallback(throttle((value: string) => {
const searchResultData = users.filter((userId) => user.id.startsWith(value));
setSearchResult(searchResultData);
}, 2500), []);
This works in delaying input as expected but for some reason, the user.filter method doesn't run, and so the state isn't updated with the search result. I believe the problem might be from the useCallback hook, but the throttle function is dependent on it to run. Any ideas on how I can work around this problem?
If your throttled/debounced handler uses props or state, like this:
const { fetcherFunctionFromProps } = props;
const eventHandler = async () => {
const resp = await fetcherFunctionFromProps();
};
const debouncedEventHandler = useMemo(
() => throttle(eventHandler, 300)
), [fetcherFunctionFromProps]);
And it doesn't work,
you can refactor it to the following:
const { fetcherFunctionFromProps } = props;
const eventHandler = async (fetcher) => {
const resp = await fetcher();
};
const debouncedEventHandler = useMemo(() => throttle(eventHandler, 300), []);
...
<Component onClick={() => debouncedEventHandler(fetcherFunctionFromProps)}>
I am trying to execute a function to update a setState but it as well needs other state to load first.
const [chatsIds, setChatsIds] = useState([]);
const [chats, setChats] = useState([]);
useEffect(() => {
getChatsIds();
}, []);
useEffect(() => {
getChats();
}, [chats]);
the "getChats" needs the value from "chatsIds" but when the screen is loaded the value isn't , only when i reload the app again it gets the value.
Here are the functions :
const getChatsIds = async () => {
const ids = await getDoc(userRef, "chats");
setChatsIds(ids);
}
const getChats = async () => {
const chatsArr = [];
chatsIds.forEach(async (id) => {
const chat = await getDoc(doc(db, "Chats", id));
chatsArr.push(chat);
console.log(chatsArr);
});
setChats(chatsArr);
}
I've tried with the useEffect and useLayoutEffect hooks, with promises and async functions, but i haven't found what i'm doing wrong :(
The problem is in your useEffect hook dependency. It should depends on chatsIds not chats.
useEffect(() => {
getChats();
}, [chatsIds]);
Which mean fetching chatsIds should depend on first mount and fetching chats should depend on if chatsIds is chnaged.
You simply change the useEffect hook to like below.
useEffect(() => {
getChatsIds();
}, [chatsIds]);
I Think getChat() is depend on chatIds...
so you use useEffect with chatIds on dependency
const [chatsIds, setChatsIds] = useState([]);
const [chats, setChats] = useState([]);
useEffect(() => {
getChatsIds();
}, []);
useEffect(() => {
getChats(chatsIds);
}, [chatsIds]);
const getChatsIds = async () => {
const ids = await getDoc(userRef, "chats");
setChatsIds(ids);
}
const getChats = async (chatsIds) => {
const chatsArr = [];
chatsIds.forEach(async (id) => {
const chat = await getDoc(doc(db, "Chats", id));
chatsArr.push(chat);
console.log(chatsArr);
});
setChats(chatsArr);
}
The code below is a simplified example of my problem. There is a lot more going on in the actual codebase, so let's just assume that my useHook function must be asynchronous and we cannot just fetch the data inside the useEffect hook.
It currently renders {}, but I want it to render "Data to be displayed"
const fetch = async () => {
/* This code can't be changed */
return "Data to be displayed"
}
const useFetch = async () => { // This function must be asynchronous
let data = await fetch();
return data;
};
const App = () => {
const data = useFetch();
const [state, setState] = useState(data);
useEffect(() => {
setState(data);
}, [data]);
return <h1>{JSON.stringify(state)}</h1>
}
export default App;
change
const data = useFetch(); to const data = await useFetch();
Move called inside the useEffect like this:
const App = () => {
const [state, setState] = useState({});
useEffect(() => {
const fetchData = async () => {
const data = await useFetch();
setState(data);
}
fetchData()
}, []); // [] load first time
return <h1>{JSON.stringify(state)}</h1>
}
I tried to create a function for fetching data from the server, and it works.
But I am not sure if that the right way?
I created a function component to fetching data, using useState, useEffect and Async/Await :
import React, { useState, useEffect } from "react";
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
let res = await fetch(
"https://api.coindesk.com/v1/bpi/currentprice.json" //example and simple data
);
let response = await res.json();
setData(response.disclaimer); // parse json
console.log(response);
};
fetchData();
}, []);
return <div>{data}</div>;
};
export default Fetch; // don't run code snippet, not working, this component must be imported in main
Where I am not sure is a place where to call the fetchData function. I do that inside useEffect? Right place? And, this call will happen only one? Because i use []?
Generally, how would you do something like this?
Overall, you are heading in the right direction. For fetching data, you'd wanna use useEffect and pass [] as a second argument to make sure it fires only on initial mount.
I believe you could benefit from decoupling fetchJson function and making it more generic, as such:
const fetchJson = async (url) => {
const response = await fetch(url);
return response.json();
};
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchJson("https://api.coindesk.com/v1/bpi/currentprice.json")
.then(({ disclaimer }) => setData(disclaimer));
}, []);
return <div>{data}</div>;
};
Another option is to use a self invoking function:
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
let res = await fetch(
"https://api.coindesk.com/v1/bpi/currentprice.json" //example and simple data
);
let response = await res.json();
setData(response);
})();
}, []);
return <div>{data}</div>;
};
The suggestion to separate out the fetch logic to a separate function is a good idea and can be done as follows:
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
let response= await fetchData("https://api.coindesk.com/v1/bpi/currentprice.json");
setData(response);
})();
}, []);
return <div>{data}</div>;
};
const fetchData = async (url) => {
const response = await fetch(url);
const json = await response.json();
return json;
};
And yet another option is to create a wrapper function around useEffect that triggers the async function for you similar to this:
export function useAsyncEffect(effect: () => Promise<any>) {
useEffect(() => {
effect().catch(e => console.warn("useAsyncEffect error", e));
});
}