What is the difference between defining this counterHandler like this
counterHandler = () => {
this.setState(() => {
return { times: this.state.times + 1 }
});
}
And this?
counterHandler = () => {
this.setState((prevState) => {
return { times: prevState.times + 1 }
});
}
Does the state from a component always gets passed to setState automatically?
If you only use One data inside state, you dont need to make callback of prevState,
but if your state more than one, you need to callback of prevstate because this will make your other and previous data will not be lost.
for example
const [state, setState] = useState({
loading: false,
data: []
})
const handleLoading = () => {
setState({
loading: true
})
}
const handleData = () => {
setState({
data: [a,b,c] // you will lost your loading = true
})}
const handleData = () => {
setState((prevState) => {
...prevState,
data: [a,b,c] // you still have loading = true
})
}
Related
const [state, setState] = React.useState({ default: () => callApiHere(), defaultTab: " });
useEffect(() => {
console.log(state, 'current state values')
}, [state])
The problem here is whenever I do a setState the Api call isn't happening properly. It returns me the function name callApiHere itself in the console.log
Make api calls in useEffects or event handlers
const [state, setState] = React.useState({
default: {}, // set default value here
defaultTab: ""
});
useEffect(() => {
let ignore = false;
const fetchData = async () => {
const response = await callApiHere();
// skip execution in invalid scope
if (ignore) { return; }
setState(s => ({ ...s, default: response }))
}
fetchData()
return () => {
ignore = true;
}
}, [])
when using state use chaining ?. or ?? to handle missing data accordingly
{ state?.default?.property ?
<div>...</div>
: null
}
You can check the beta docs about useEffect life cycles
Hope it helps
I am trying to implement pagination using react-query. On page change I am updating the page inside useEffect using setArrivals. For some reason I am always sending the previous value of the arrivals as the argument for the getProductList function. To fix the issue I am sending the refetch() request inside the setTimeout. It does work but it doesn't feel right to me. Let me know what I am doing wrong.
const HomePage = ({ newArrivals }) => {
const [page, setPage] = useState(1);
const [arrivals, setArrivals] = useState({ ...newArrivals, page: page });
useEffect(() => {
setArrivals((values) => {
console.log({ page });
return { ...values, page: page };
});
setTimeout(function () {
newArrivalsQuery.refetch();
}, 0);
}, [page]);
const newArrivalsQuery = useQuery(
['productListByNewArrivals'],
() => getProductList(arrivals),
{
select: useCallback((data) => {
return JSON.parse(data);
}, []),
}
);
return (
<>
<NewArrivals
newArrivalsQuery={newArrivalsQuery}
page={page}
setPage={setPage}
/>
</>
);
};
export async function getServerSideProps(context) {
const newArrivals = {
sort: 'createdAt',
order: 'desc',
};
try {
const queryClient = new QueryClient();
await queryClient.prefetchQuery('productListByNewArrivals', async () => {
const newArrivalsResult = await listProduct(newArrivals);
return JSON.stringify(newArrivalsResult);
});
return {
props: {
newArrivals: newArrivals,
dehydratedState: dehydrate(queryClient),
},
};
} catch (error) {
console.log('error: ', error);
if (error) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
}
}
The best way is to add the dependencies of your query function to the queryKey. react-query is declarative and will re-fetch automatically if the queryKey changes. If you have to reach to useEffect and refetch, it's likely not the easiest solution:
const HomePage = ({ newArrivals }) => {
const [page, setPage] = useState(1);
const [arrivals, setArrivals] = useState({ ...newArrivals, page: page });
const newArrivalsQuery = useQuery(
['productListByNewArrivals', arrivals],
() => getProductList(arrivals),
{
select: useCallback((data) => {
return JSON.parse(data);
}, []),
}
);
now, arrivals are part of the queryKey, which is what you are using in the queryFn in getProductList. Now all you need to do is call setArrivals and react-query will refetch.
Side note: it looks like arrivals is not really state, but derived state. At least in this snippet, you only call the setter in an effect, which seems wrong. It looks like you want to keep in in-sync with page and compute it every time you call setPage, so you can also do:
const [page, setPage] = useState(1);
const arrivals = { ...newArrivals, page: page };
First of all, I researched the question a lot, but I could not find a solution. I would appreciate if you help.
functional component
I add the code briefly below. this is not full code
state and props
// blog id
const { id } = props.match.params;
// state
const initialState = {
title: "",
category: "",
admin_id: "",
status: false
};
const [form, setState] = useState(initialState);
const [adminList, setAdminList] = useState([]);
const [articleText, setArticleText] = useState([]);
const [me, setMe] = useState([]);
const [locked, setLocked] = useState(true);
const timerRef = useRef(null);
// queries and mutations
const { loading, error, data } = useQuery(GET_BLOG, {
variables: { id }
});
const { data: data_admin, loading: loading_admin } = useQuery(GET_ADMINS);
const [editBlog, { loading: loadingUpdate }] = useMutation(
UPDATE_BLOG
);
const [lockedBlog] = useMutation(LOCKED_BLOG);
multiple useEffect and functions
useEffect(() => {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
}, [loading, data]);
useEffect(() => {
if (!loading_admin && data_admin) {
const me = data_admin.admins.filter(
x => x.id === props.session.activeAdmin.id
);
setAdminList(data_admin);
setMe(me[0]);
}
}, [data_admin, loading_admin]);
useEffect(() => {
const { id } = props.match.params;
lockedBlog({
variables: {
id,
locked: locked
}
}).then(async ({ data }) => {
console.log(data);
});
return () => {
lockedBlog({
variables: {
id,
locked: false
}
}).then(async ({ data }) => {
console.log(data);
});
};
}, [locked]);
// if loading data
if (loading || loading_admin)
return (
<div>
<CircularProgress className="loadingbutton" />
</div>
);
if (error) return <div>Error.</div>;
// update onChange form
const updateField = e => {
setState({
...form,
[e.target.name]: e.target.value
});
};
// editor update
const onChangeEditor = text => {
const currentText = articleText.text;
const newText = JSON.stringify(text);
if (currentText !== newText) {
// Content has changed
if (timerRef.current) {
clearTimeout(timerRef.current);
}
setArticleText({ text: newText });
if (!formValidate()) {
timerRef.current = setTimeout(() => {
onSubmitAuto();
}, 10000);
}
}
};
// auto save
const onSubmitAuto = () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
editBlog({
variables: {
id,
admin_id,
title,
text: articleText.text,
category,
status
}
}).then(async ({ data }) => {
console.log(data);
});
};
// validate
const formValidate = () => {
const { title, category } = form;
return !title || !articleText.text || !category;
};
// clear state
const resetState = () => {
setState({ ...initialState });
};
return (
// jsx
)
first issue, when call onSubmitAuto, first useEffect is running again. i dont want this.
because I just want it to work on the first mount.
second issue, if the articleText state has changed before, when mutation it does not mutate the data in the form state. but if the form state changes first, it mutated all the data. I think this issue is the same as the first issue.
I hope I could explain the problem. :/
Ciao, I have an answer to the first issue: when onSubmitAuto is triggered, it calls editBlog that changes loading. And loading is on first useEffect deps list.
If you don't want that, a fix could be something like that:
const isInitialMount = useRef(true);
//first useEffect
useEffect(() => {
if(isInitialMount.current) {
if (!loading && data) {
setState({
title: data.blog.title,
category: data.blog.category,
admin_id: data.blog.admin.id,
status: data.blog.status
});
setArticleText({
text: data.blog.text
});
}
console.log(data);
if (data !== undefined) isInitialMount.current = false;
}
}, [loading, data]);
I'm new to React JS and am slowly blundering my way along but hit an issues I figured I'd ask about here.
I have a component that is a container of the state and all the data I care about
class PirateContainer extends Component {
constructor(props) {
super(props);
this.state = {
PirateCaptains: [],
Year: [],
Ship: [],
PirateChoices: {
SelectedYear : "",
SelectedPirateCaptain : "",
SelectedShip : ""
}
};
this.handleYearChange = this.handleYearChange.bind(this);
this.handleShipChange = this.handleShipChange.bind(this);
this.handlePirateCaptainChange = this.handlePirateCaptainChange.bind(this);
}
popYear(accessLevel){
fetch('https://Pirateman/api/Years').then(response => {
return response.json();
})
.then((json) => {
this.setState(
prevState => ({
Year: json,
Choices: {
...prevState.PirateChoices,
SelectedYear: json[0]
}
}), () => {
console.log(this.state);}
);
});
}
popCaptain({
fetch('https://Pirateman/api/PirateCaptains').then(response => {
return response.json();
})
.then((json) => {
this.setState(
prevState => ({
PirateCaptain: json,
PirateChoices: {
...prevState.PirateChoices,
SelectedPirateCaptain: json[0]
}
}), () => {console.log(this.state);}
);
});
}
popShip(year, captain){
fetch('https://Pirateman/api/Ships/' + year + '/' + captain ).then(response => {
return response.json();
})
.then((json) => {
this.setState(
prevState => ({
Ship: json,
PirateChoices: {
...prevState.PirateChoices,
SelectedShip: json[0]
}
}), () => {console.log(this.state);}
);
});
}
handleYearChange(e) {
let value = e.target.value;
if(this.state.PirateChoices.SelectedYear === value)
{
console.log('Do nothing')
}
else
{
this.setState(
prevState => ({
PirateChoices: {
...prevState.PirateChoices,
SelectedYear: value
}
}), () => {console.log(this.state)}
);
}
}
handlePirateCaptainChange(e) {
let value = e.target.value;
if(this.state.PirateChoices.SelectedPirateCaptain === value)
{
console.log('Do nothing')
}
else
{
this.setState(
prevState => ({
PirateChoices: {
...prevState.PirateChoices,
SelectedPirateCaptain: value
}
}), () => {console.log(this.state)}
);
}
}
handleShipChange(e) {
let value = e.target.value;
if(this.state.PirateChoices.SelectedShip === value)
{
console.log('Do nothing')
}
else
{
this.setState(
prevState => ({
PirateChoices: {
...prevState.PirateChoices,
SelectedShip: value
}
}), () => {console.log(this.state)}
);
}
}
I have a generic component that loads a select form that takes the change function and triggers onchange.
So the first thing the app does is get Years and PirateCaptains from a service and sets the state of those (as well as the Selected Ship and PirateCaptain which is the first entry of the array shown). On change the selected is updated.
The problem I have is that the Ship data depends on the combination of SelectedPirateCaptain and SelectedYear.
I feel like the easy way of populating that is having it trigger on change of either of those two properties, but I can't figure out a way to set up a function to do that.
I've also tried (for initial loads) using promises or callbacks but can't seem to figure out how to get it to wait until the state of both selected properties has a value. (I tried a while loop but the function never seems to get the state with the value.)
Any help is greatly appreciated in advance, I suspect I'm being an idiot and missing something obvious so thanks in advance.
how to add loading state to true in 'addItem' (reducer case), and set the loading state to false?
But I'm stuck using react-hook
https://codesandbox.io/s/new-water-vczdp
I did it here: https://codesandbox.io/s/react-todolist-3dko3 with older react version.
What to do here?
case "addItem": {
//fakeHttp();
const { toAddItem, items } = state;
const nextId = +items[items.length - 1].id + 1;
return {
items: [...items, { id: nextId, name: toAddItem }],
loading: false
};
}
You don't have to call async functions in your reducer. You have to use other hooks for this. In your case, you should use useCallback hook:
function App() {
const [{ items, toAddItem, loading }, dispatch] = useReducer(
reducer,
initialState
);
const addItem = useCallback(() => {
async function fakeHttp() {
dispatch({ type: "loading" });
await delay(500);
dispatch({ type: "addItem"})
}
fakeHttp();
}, []);
return (
...
<AddList
handleInput={e =>
dispatch({ type: "handleInput", value: e.target.value })
}
toAddItem={toAddItem}
addItem={addItem}
loading={loading}
/>
)
}
I make some changes in your sandbox example. You could see a working example here:
https://codesandbox.io/embed/stupefied-currying-l39il