How do I place this object inside an array: Dog API. Is an object inside another object. I'm trying to set setBreeds(breedsList.message) but does not work.
const basicUrl = `https://dog.ceo/api/breeds/`
const listUrl = `list/all`
const Home = () => {
// uses state to store the list of breeds
const [breeds, setBreeds] = useState([])
// fetch the list of breeds
const fetchBreeds = async () => {
let url
url = `${basicUrl}${listUrl}`
const response = await fetch(url)
const breedsList = await response.json()
setBreeds(breedsList)
}
// useeffect to mount the fetchBreeds function
useEffect(() => {
fetchBreeds()
}, [])
return (
<div>
{/* // maps */}
{breeds.map((breed) => console.log(breed))}
</div>
)
}
export default Home
You do like this
setBreeds(prevState => [...prevState, breedsList.message])
I'm not sure if that is what you are asking for but the following function turns the json response into a list of breed names. You could transform the object and then call setBreeds.
function transformToList(breedsResponse) {
const {message: breeds} = breedsResponse;
const breedMainNames = Object.keys(breeds);
return breedMainNames.reduce((acc, mainName) => {
const subNames = breeds[mainName];
if(subNames.length === 0) {
acc.push(mainName)
} else {
const combinedNames = subNames.map(name => `${name} ${mainName}`);
acc.push(...combinedNames);
}
return acc;
}, [])
};
Related
I'm struggling with this problem and I've already tried many solutions but none of them fit me.
I have a context that I use to share information that I get from an API. I will summarize the files for you:
file: useGetInfo.tsx
type InfoContextData = { ... }
type Props = { ... }
type InfoResponseProps = { ... }
export const InfoContext = createContext<InfoContextData>({} as InfoContextData)
export const InformationProvider = ({ children }: Props) => {
const isBrowser = typeof window !== `undefined`
const [infoStorage, setInfoStorage] = useState(
isBrowser && localStorage.getItem('info')
? String(localStorage.getItem('info'))
: undefined
)
const [result, setResult] = useState<InfoResponseProps | null>(null)
const getInfo = useCallback(async (value: string) => {
const url = `<URL_FROM_API${value}>`
await axios.get(url)
.then((response) => {
setResult(response.data)
})
.catch((_) => {
setResult(null)
})
})
useEffect(() => {
if (!infoStorage) {
return
}
getInfo(infoStorage)
}, [infoStorage, getInfo])
return (
<InfoContext.Provider
value={{
result,
setResult,
infoStorage,
setInfoStorage,
getInfo,
}}
>
{children}
</InfoContext.Provider>
)
}
Then in the component I call the context:
file: SomeComponent.tsx
const Component = () => {
const { setInfoStorage, getInfo, result } = useContext(InfoContext)
const [input, setInput] = useState('')
const handleInfoSubmit = useCallback(() => {
getInfo(input)
if (!result || !result?.ok) {
localStorage.removeItem('info')
setInfoStorage(undefined)
}
setInfoStorage(input)
localStorage.setItem('info', 'input')
setInput('')
}, [input, result, getInfo, setInfoStorage, setInput])
return (
...
<Form onSubmit={handleInfoSubmit}>
<input>
...
</Form>
)
}
Basically, the user inserts a code in the form and when he submits the form, it runs the handleInfoSubmit function. Then, the code runs the function getInfo() and after requesting the API it returns the information to the state result.
The problem is in the SomeComponent.tsx file: when I run the function getInfo(input) I need the information in the state result but at the time axios finishes the request to the API and the code goes to the if (!result || !result?.ok) line, the result state is not still fulfilled.
I know that React/Gatsby can't update immediately the state like what I need, but is there a way to overcome this problem? Thanks in advance.
I think the value of the result would always be stale inside the handleInfoSubmit function per your code.
Rewrite the getInfo and handleInfoSubmit like this
// Return data from getInfo so that we can use the value directly in handleInfoSubmit
const getInfo = useCallback(async (value: string) => {
const url = `<URL_FROM_API${value}>`
try {
const { data } = await axios.get(url);
setResult(data)
return data;
} catch {
setResult(null)
}
return null;
})
const handleInfoSubmit = useCallback(async () => {
// await getInfo and get the axios response data.
const result = await getInfo(input)
if (!result || !result?.ok) {
localStorage.removeItem('info')
setInfoStorage(undefined)
}
setInfoStorage(input)
localStorage.setItem('info', 'input')
setInput('')
}, [input, getInfo, setInfoStorage, setInput])
Description
I'm creating a state management tool for a small project, using mainly useSyncExternalStore from React, inspired by this video from Jack Herrington https://www.youtube.com/watch?v=ZKlXqrcBx88&ab_channel=JackHerrington.
But, I'm running into a pattern that doesn't look right, which is having to use 2 providers, one to create the state, and the other to initialise it.
The gist of the problem:
I have a property sessionId coming from an HTTP request. Saving it in my store wasn't an issue.
However, once I have a sessionId then all of my POST requests done with notifyBackend should have this sessionId in the request body. And I was able to achieve this requirement using the pattern above, but I don't like it.
Any idea how to make it better ?
Code
CreateStore.jsx (Not important, just providing the code in case)
export default function createStore(initialState) {
function useStoreData(): {
const store = useRef(initialState);
const subscribers = useRef(new Set());
return {
get: useCallback(() => store.current, []),
set: useCallback((value) => {
store.current = { ...store.current, ...value };
subscribers.current.forEach((callback) => callback());
}, []),
subscribe: useCallback((callback) => {
subscribers.current.add(callback);
return () => subscribers.current.delete(callback);
}, []),
};
}
const StoreContext = createContext(null);
function StoreProvider({ children }) {
return (
<StoreContext.Provider value={useStoreData()}>
{children}
</StoreContext.Provider>
);
}
function useStore(selector) {
const store = useContext(StoreContext);
const state = useSyncExternalStore(
store.subscribe,
() => selector(store.get()),
() => selector(initialState),
);
// [value, appendToStore]
return [state, store.set];
}
return {
StoreProvider,
useStore,
};
}
Creating the state
export const { StoreProvider, useStore } = createStore({
sessionId: "INITIAL",
notifyBackend: () => { },
});
index.jsx
<Router>
<StoreProvider>
<InitialisationProvider>
<App />
</InitialisationProvider>
</StoreProvider>
</Router
InitialisationContext.jsx
const InitialisationContext = createContext({});
export const InitializationProvider = ({ children }) {
const [sessionId, appendToStore] = useStore(store => store.session);
const notifyBackend = async({ data }) => {
const _data = {
...data,
sessionId,
};
try {
const result = await fetchPOST(data);
if (result.sessionId) {
appendToStore({ sessionId: result.sessionId });
} else if (result.otherProp) {
appendToStore({ otherProp: result.otherProp });
}
} catch (e) { }
};
useEffect(() => {
appendToStore({ notifyBackend });
}, [sessionId]);
return (
<InitialisationContext.Provider value={{}}>
{children}
</InitialisationContext.Provider>
);
}
I just tried out Zustand, and it's very similar to what I'm trying to achieve.
Feels like I'm trying to reinvent the wheel.
With Zustand:
main-store.js
import create from 'zustand';
export const useMainStore = create((set, get) => ({
sessionId: 'INITIAL',
otherProp: '',
notifyBackend: async ({ data }) => {
const _data = {
...data,
sessionId: get().sessionId,
};
try {
const result = await fetchPOST(data);
if (result.sessionId) {
set({ sessionId: result.sessionId });
} else if (result.otherProp) {
set({ otherProp: result.otherProp });
}
} catch (e) { }
},
}));
SomeComponent.jsx
export const SomeComponent() {
const sessionId = useMainStore(state => state.sessionId);
const notifyBackend = useMainStore(state => state.notifyBackend);
useEffect(() => {
if (sessionId === 'INITIAL') {
notifyBackend();
}
}, [sessionId]);
return <h1>Foo</h1>
};
This answer focuses on OPs approach to createStore(). After reading the question a few more times, I think there are bigger issues. I'll try to get to these and then extend the answer.
Your approach is too complicated.
First, the store is no hook! It lives completely outside of react. useSyncExternalStore and the two methods subscribe and getSnapshot are what integrates the store into react.
And as the store lives outside of react, you don't need a Context at all.
Just do const whatever = useSyncExternalStore(myStore.subscribe, myStore.getSnapshot);
Here my version of minimal createStore() basically a global/shared useState()
export function createStore(initialValue) {
// subscription
const listeners = new Set();
const subscribe = (callback) => {
listeners.add(callback);
return () => listeners.delete(callback);
}
const dispatch = () => {
for (const callback of listeners) callback();
}
// value management
let value = typeof initialValue === "function" ?
initialValue() :
initialValue;
// this is what useStore() will return.
const getSnapshot = () => [value, setState];
// the same logic as in `setState(newValue)` or `setState(prev => newValue)`
const setState = (arg) => {
let prev = value;
value = typeof arg === "function" ? arg(prev) : arg;
if (value !== prev) dispatch(); // only notify listener on actual change.
}
// returning just a custom hook
return () => useSyncExternalStore(subscribe, getSnapshot);
}
And the usage
export const useMyCustomStore = createStore({});
// ...
const [value, setValue] = useMyCustomStore();
i have loading screen for call all the data function.i used async function for all function call.
//NOTE: this screen loads all the data and store it in store so user will have a smother experience
const LoadingScreen = (props) => {
const gotToHomeScreen = () => {
props.navigation.replace("Home", { screen: HOME_SCREEN });
};
//NOTE: loading data here for home screen journey
const getRequiredAPIDataInStore = async () => {
GetAllFieldProp();
GetAllSalaryAPIResponse();
GetSalaryAPIResponse();
let { spinnerStateForm101 } = GetForm101API();
let { spinnerStateForm106 } = GetForm106API();
GetMessagesCountAPI();
GetMessagesAPI(props);
GetAllFormAPIResponse();
GetAllSpecificSalaryAPIResponse();
let { spinnerStateMonthly } = GetMonthlyAbsenceAPI(props);
let { spinnerStateWeekly } = GetWeeklyAbsenceAPI(props);
if (
spinnerStateMonthly &&
spinnerStateWeekly &&
spinnerStateForm106 &&
spinnerStateForm101
) {
gotToHomeScreen();
}
};
getRequiredAPIDataInStore();
export default LoadingScreen;
but i am getting warning messages for this.
Warning: Cannot update a component from inside the function body of a different component.
at src/screens/loading-screen.js:19:26 in gotToHomeScreen
at src/screens/loading-screen.js:37:6 in getRequiredAPIDataInStore
How to solve this warning messsage?
Here's the approach I would take.
const Loading = () => {
const [spinnerStateMonthly, setSpinnerStatMonthly] = useState(null);
const [spinnerStateWeekly, setspinnerStateWeekly] = useState(null);
const [spinnerStateForm106, setspinnerStateForm106] = useState(null);
const [spinnerStateForm101, setSpinnerStateForm101] = useState(null);
const gotToHomeScreen = () => {
props.navigation.replace("Home", { screen: HOME_SCREEN });
};
useEffect(() => {
// async callback to get all the data and set state
(async () => {
await GetAllFieldProp();
await GetAllSalaryAPIResponse();
await GetSalaryAPIResponse();
const { spinnerStateForm101: local101 } = GetForm101API();
const { spinnerStateForm106: local106 } = GetForm106API();
setSpinnerStateForm101(local101);
setSpinnerStateForm106(local106);
await GetMessagesCountAPI();
await GetMessagesAPI(props);
await GetAllFormAPIResponse();
await GetAllSpecificSalaryAPIResponse();
const { spinnerStateMonthly: localMonthly } = GetMonthlyAbsenceAPI(props);
const { spinnerStateWeekly: localWeekly } = GetWeeklyAbsenceAPI(props);
setSpinnerStateMonthly(localMonthly);
setSpinnerStateWeekly(localWeekly);
})();
}, []);
// effect to check for what the state is and if all the states are satisfied
// then go to the home screen
useEffect(() => {
if (spinnerStateMonthly
&& spinnerStateWeekly
&& spinnerStateForm106
&& spinnerStateForm101) {
gotToHomeScreen();
}
}, [spinnerStateMonthly, spinnerStateWeekly, spinnerStateForm101,
spinnerStateForm106]);
};
there is something strange happening with my code. My variable data (useState) is randomly empty when I call my callback when onpopstate event is fired.
I have 2 components and 1 hook used like that:
const Parent = props => {
const {downloadData} = useData();
const [data, setData] = useState([]);
const [filteredData, setFilteredData] = useState();
const loadData = async () => setData(await downloadData());
useEffect(() => {
loadData();
}, []);
return <FilterPage data={data} onDataChange={data => setFilteredData(data)} />
}
const FilterPage = ({data, onDataChange} => {
const {saveHistoryData} = useHistoryState('filter', null, () => {
updateFilters();
});
const filter = (filterData, saveHistory = true) => {
let r = data; // data is randomly empty here
...
if(saveHistory)saveHistoryData(filterData);
onDataChange(r);
}
});
// my hook
const useHistoryState = (name, _data, callback) => {
const getHistoryData = () => {
const params = new URLSearchParams(window.location.search);
try{
return JSON.parse(params.get(name));
}catch(err){
return null;
}
}
const saveHistoryData = (data) => {
const params = new URLSearchParams(window.location.search);
params.set(name, JSON.stringify(data || _data));
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const removeHistoryData = () => {
const params = new URLSearchParams(window.location.search);
params.delete(name);
window.history.pushState(null, '', window.location.pathname + '?' + params.toString());
}
const watchCallback = () => {
callback(getHistoryData());
};
useEffect(() => {
let d = getHistoryData();
if(d)watchCallback();
window.addEventListener('popstate', watchCallback);
return () => window.removeEventListener('popstate', watchCallback);
}, []);
return {getHistoryData, saveHistoryData, removeHistoryData};
}
Any suggestions please
Edit
I'm sorry is not the entire code, just a draft. I download the data using async function. The data is loading fine but is empty only if we call the callback from the hook.
You need to use setData to populate data
First of all you are not calling setData() anywhere.
You are using data but not setData and you are using setFilteredData but not filteredData.
Furthermore it doesn't look like updateFilters() exist within FilterPage.
You are passing onDataChange to <Filterpage> but you are not using the property, only ({data}) which explains why it's empty. You might want to update the FilterPage signature: const FilterPage = ({data, onDataChange}) => {} and use the onDataChange
I'm learning to React Hooks.
And I'm struggling initialize data that I fetched from a server using a custom hook.
I think I'm using hooks wrong.
My code is below.
const useFetchLocation = () => {
const [currentLocation, setCurrentLocation] = useState([]);
const getCurrentLocation = (ignore) => {
...
};
useEffect(() => {
let ignore = false;
getCurrentLocation(ignore);
return () => { ignore = true; }
}, []);
return {currentLocation};
};
const useFetch = (coords) => {
console.log(coords);
const [stores, setStores] = useState([]);
const fetchData = (coords, ignore) => {
axios.get(`${URL}`)
.then(res => {
if (!ignore) {
setStores(res.data.results);
}
})
.catch(e => {
console.log(e);
});
};
useEffect(() => {
let ignore = false;
fetchData(ignore);
return () => {
ignore = true;
};
}, [coords]);
return {stores};
}
const App = () => {
const {currentLocation} = useFetchLocation();
const {stores} = useFetch(currentLocation); // it doesn't know what currentLocation is.
...
Obviously, it doesn't work synchronously.
However, I believe there's the correct way to do so.
In this case, what should I do?
I would appreciate if you give me any ideas.
Thank you.
Not sure what all the ignore variables are about, but you can just check in your effect if coords is set. Only when coords is set you should make the axios request.
const useFetchLocation = () => {
// Start out with null instead of an empty array, this makes is easier to check later on
const [currentLocation, setCurrentLocation] = useState(null);
const getCurrentLocation = () => {
// Somehow figure out the current location and store it in the state
setTimeout(() => {
setCurrentLocation({ lat: 1, lng: 2 });
}, 500);
};
useEffect(() => {
getCurrentLocation();
}, []);
return { currentLocation };
};
const useFetch = coords => {
const [stores, setStores] = useState([]);
const fetchData = coords => {
console.log("make some HTTP request using coords:", coords);
setTimeout(() => {
console.log("pretending to receive data");
setStores([{ id: 1, name: "Store 1" }]);
}, 500);
};
useEffect(() => {
/*
* When the location is set from useFetchLocation the useFetch code is
* also triggered again. The first time coords is null so the fetchData code
* will not be executed. Then, when the coords is set to an actual object
* containing coordinates, the fetchData code will execute.
*/
if (coords) {
fetchData(coords);
}
}, [coords]);
return { stores };
};
function App() {
const { currentLocation } = useFetchLocation();
const { stores } = useFetch(currentLocation);
return (
<div className="App">
<ul>
{stores.map(store => (
<li key={store.id}>{store.name}</li>
))}
</ul>
</div>
);
}
Working sandbox (without the comments) https://codesandbox.io/embed/eager-elion-0ki0v