I am using React context to pass down data, following the docs, however I am stuck with the initial value and am not sure what I did wrong.
This is what my context file looks like:
export const ItemsContext = createContext([]);
ItemsContext.displayName = 'Items';
export const ItemsProvider = ({ children }) => {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const setData = async () => {
setLoading(true);
setItems(await getItemsApiCall());
setLoading(false);
};
useEffect(() => {
setData();
}, []);
console.warn('Items:', items); // This shows the expected values when using the provider
return (
<ItemsContext.Provider value={{ items, loading }}>
{children}
</ItemsContext.Provider>
);
};
Now, I want to feed in those items into my app in the relevant components. I am doing the following:
const App = () => {
return (
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
);
}
However, as my comment states, items will always be empty. On the other hands, if I do just use the ItemsProvider, I do get the proper value, but at this point need to access it directly on the app, so the ItemsContext.Consumer seems to make more sense.
Am I doing something wrong here?
Edit: A way around it seems to be to wrap the Consumer with the Provider, but that feels wrong and didn't see that at the docs. Is that perhaps the case?
So essentially, something like this:
const App = () => {
return (
<ItemsProvider>
<ItemsContext.Consumer>
{(items) => {
console.warn('items?', items); // Is always the initial value of "[]"
return (<div>Test</div>);
}}
</ItemsContext.Consumer>
</ItemsProvider>
);
}
You have to provide a ItemsContext provider above the App component hierarchy,otherwise the default value of the context will be used.
something in this form:
<ItemsContext.Provider value={...}>
<App/>
</ItemsContext.Provider>
Related
I have been using react-apollo and the render prop approach.
This worked perfectly and my code looked something like this.
const App = () => {
const [player, setPlayer] = React.useState(null);
if (player) {
return (
<GetBroncosPlayerQuery variables={{ player }}>
{({ data }) => {
// do stuff here with a select box
}}
</GetBroncosPlayersQuery>
)
}
}
Now, if I try doing the same with the useQuery hook I get the following error, when my code looks like this:
const App = () => {
const [player, setPlayer] = React.useState(false);
if (isBroncos) {
const { data } = useQuery(GetBroncosPlayersQueryDocument, {
variables: { player }
});
// do stuff here
}
}
This gives an error that you cannot use a hook in a conditional statement. I then implemented useLazyQuery but this doesn't work either as after you call it once it behaves like useQuery, so it works the first time, but if the user changes a select dropdown to be empty again it breaks.
What is the best approach with the query hook to only call the query conditionally?
You should use skip option:
If skip is true, the query will be skipped entirely.
const isBroncos = getCondition();
const App = () => {
const [player, setPlayer] = React.useState(false);
const { data } = useQuery(GetBroncosPlayersQueryDocument, {
variables: { player },
skip: isBroncos
});
return !isBroncos && data && <>...</>;
};
I'm having trouble using LocalStorage data.
I used storejs (localStorage library), therefore store.getItem equals to localStorage.getItem and store.setItem equals to localStorage.setItem.
please check my code.
const test = () => {
useEffect(() => {
const res = store.getItem('data')
// I'm trying toexecutes following code if store returns undefined.
if(!res) {
store.setItem('data', [{name:'aden', age:17}])
store.getItem('data')
}
},[])
return <></>
}
I know this code doesn't work. However, What I'm trying to do is when component mounts and there's nothing in LocalStorage, I want to immediately setItem to LocalStorage, and fetch the item right away.
However, with that code, I get nothing from the LocalStorage, It seems like I'm having misconception on lifecycle of react.
How can I solve the problem?
Try this:
import React, { useState } from "react";
const Test = () => {
const [user, setUser] = useState(JSON.parse(localStorage.getItem("user")));
const addToLocalStorage = () => {
const payload = [{ name: "aden", age: 17 }];
setUser(payload);
localStorage.setItem("user", JSON.stringify(payload));
};
return (
<>
<div>
{user?.length > 0 &&
user.map((e) => <span key={e.name}>name:{e.name}</span>)}
</div>
<button onClick={addToLocalStorage}>Add to localStorage</button>
</>
);
};
export default Test;
useEffect(() => {
const res = store.getItem('data')
// I'm trying toexecutes following code if store returns undefined.
if(!res) {
store.setItem('data', [{name:'aden', age:17}])
store.getItem('data')
}
},[])
Maybe you should type store.setItem
I am trying to render a component within a component file that relies on data from an outside API. Basically, my return in my component uses a component that is awaiting data, but I get an error of dataRecords is undefined and thus cannot be mapped over.
Hopefully my code will explain this better:
// Component.js
export const History = () => {
const [dateRecords, setDateRecords] = useState(0)
const { data, loading } = useGetRecords() // A custom hook to get the data
useEffect(() => {
fetchData()
}, [loading, data])
const fetchData = async () => {
try {
let records = await data
setDateRecords(records)
} catch (err) {
console.error(err)
}
}
// Below: Render component to be used in the component return
const GameItem = ({ game }) => {
return <div>{game.name}</div>
}
// When I map over dateRecords, I get an error that it is undefined
const renderRecords = async (GameItem) => {
return await dateRecords.map((game, index) => (
<GameItem key={index} game={game} />
))
}
const GameTable = () => {
return <div>{renderRecords(<GameItem />)}</div>
}
return(
// Don't display anything until dateRecords is loaded
{dateRecords? (
// Only display <GameTable/> if the dateRecords is not empty
{dateRecords.length > 0 && <GameTable/>
)
)
}
If dateRecords is meant to be an array, initialize it to an array instead of a number:
const [dateRecords, setDateRecords] = useState([]);
In this case when the API operation is being performed, anything trying to iterate over dateRecords will simply iterate over an empty array, displaying nothing.
You've set the initial state of dateRecords to 0 which is a number and is not iterable. You should set the initial state to an empty array:
const [dateRecords, setDateRecords] = useState([]);
I wrote a program that takes and displays contacts from an array, and we have an input for searching between contacts, which we type and display the result.
I used if in the search function to check if the searchKeyword changes, remember to do the filter else, it did not change, return contacts and no filter is done
I want to do this control with useEffect and I commented on the part I wrote with useEffect. Please help me to reach the solution of using useEffect. Thank you.
In fact, I want to use useEffect instead of if
I put my code in the link below
https://codesandbox.io/s/simple-child-parent-comp-forked-4qf39?file=/src/App.js:905-913
Issue
In the useEffect hook in your sandbox you aren't actually updating any state.
useEffect(()=>{
const handleFilterContact = () => {
return contacts.filter((contact) =>
contact.fullName.toLowerCase().includes(searchKeyword.toLowerCase())
);
};
return () => contacts;
},[searchKeyword]);
You are returning a value from the useEffect hook which is interpreted by React to be a hook cleanup function.
See Cleaning up an effect
Solution
Add state to MainContent to hold filtered contacts array. Pass the filtered state to the Contact component. You can use the same handleFilterContact function to compute the filtered state.
const MainContent = ({ contacts }) => {
const [searchKeyword, setSearchKeyword] = useState("");
const [filtered, setFiltered] = useState(contacts.slice());
const setValueSearch = (e) => setSearchKeyword(e.target.value);
useEffect(() => {
const handleFilterContact = () => {
if (searchKeyword.length >= 1) {
return contacts.filter((contact) =>
contact.fullName.toLowerCase().includes(searchKeyword.toLowerCase())
);
} else {
return contacts;
}
};
setFiltered(handleFilterContact());
}, [contacts, searchKeyword]);
return (
<div>
<input
placeholder="Enter a keyword to search"
onChange={setValueSearch}
/>
<Contact contacts={contacts} filter={filtered} />
</div>
);
};
Suggestion
I would recommend against storing a filtered contacts array in state since it is easily derived from the passed contacts prop and the local searchKeyword state. You can filter inline.
const MainContent = ({ contacts }) => {
const [searchKeyword, setSearchKeyword] = useState("");
const setValueSearch = (e) => setSearchKeyword(e.target.value);
const filterContact = (contact) => {
if (searchKeyword.length >= 1) {
return contact.fullName
.toLowerCase()
.includes(searchKeyword.toLowerCase());
}
return true;
};
return (
<div>
<input
placeholder="Enter a keyword to search"
onChange={setValueSearch}
/>
<Contact contacts={contacts.filter(filterContact)} />
</div>
);
};
I'm trying to query data from the Prismic headless CMS API and running into problems using React Hooks. The prismic API is returning null, though I know its being passed down correctly as I can query it successfully without using react hooks.
Heres my current compontent code. Its returning "cannot read property 'api' of null". It doesn't reach the 'data' console log.
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
}, []);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
It seems to be a case where on initial render prismicCtx is null and only on the subsequent render you receive the updated value. The solution is obviously to call the effect on change of prismicCtx, but you if you just want to call the api on initial render you would need to keep track of whether you called the api earlier or not which you can achieve by using useRef and also you don't need to set the state as empty if prismicCtx doesn't exist
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
const isFirstCall = useRef(true);
useEffect(() => {
if(prismicCtx && isFirstCall.current) {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
fetchLinks();
isFirstCall.current = false;
}
},[prismicCtx]);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;
Figured it out, I beleive. PrismicCTX was being changed up the tree so it was switching to undefinded. A simple if/else fixed it and making it so it only updated on that prop change. Still not sure if best practice though!
const Footer = ({ prismicCtx }) => {
const [links, setLinks] = useState([]);
useEffect(
() => {
const fetchLinks = async () => {
const data = await prismicCtx.api.query([
Prismic.Predicates.at('document.tags', [`${config.source}`]),
Prismic.Predicates.at('document.type', 'footer'),
]);
console.log('data:', data);
setLinks(data.results[0].data);
};
if (prismicCtx) {
fetchLinks();
} else {
setLinks([]);
}
},
[prismicCtx]
);
return (
<div>
<h1> Footer </h1>
</div>
);
};
export default Footer;