REACT - show loader spinner by setState inside .map() - reactjs

Got an array on objects from the server.
Want to display for the user a loader spinner when the request is lodaing.
const onDownland = (reportId: string) => {
setDownloadLoadingState(() => true);
backendAPIAxios.get(`/download/${reportId}`)
.then((response: AxiosResponse<IDownloadResponse>) => {
})
.catch((e: AxiosError) => {
}).finally(() => {
setDownloadLoadingState(() => false);
});
};
The problem is I get multiple objects from the server, and I got one state that changes all of the objects UI.
<Button>
{!props.downloadLoadingState ?
<MSvg
name='download'
className={classes['svgContainerBlack']}
onClick={() => props.onDownload(history.id!)}
/> :
<Tooltip title={<h1 style={{ fontSize: '17px' }}>Loading</h1>} placement="left" arrow>
<CircularProgress color="inherit" />
</Tooltip>
}
</Button>
when loading
after loading
How can I display the loader spinner for each object when I fire the request.
Added -

If you move your loadingState into the Button component, you can have independent spinners for each object.
You can set the onDownload prop to an async function (i.e. a function returning a Promise), and manage the loading state inside the button component, instead of in its parent.
Something like this might work:
// your button component
const [downloadLoadingState, setDownloadLoadingState] = useState(false);
...
<Button>
{!downloadLoadingState ? // Now looking into local state, instead of prop
<MSvg
...
onClick={() => {
setDownloadLoadingState(true)
props.onDownload(history.id!).finally(() => setDownloadLoadingState(true))
}
}
/> :
<Tooltip ...>
...
</Tooltip>
}
</Button>
// in the parent component, keep only the fetching in onDownload and remove the loading state management
// then, return the axios promise so the .finally() can be used inside the button component
const onDownload = (reportId: string) => {
return backendAPIAxios.get(`/download/${reportId}`)
.then((response: AxiosResponse<IDownloadResponse>) => {
})
.catch((e: AxiosError) => {
});
};

Related

React 18: Executing Code When Suspense Completes Promise

I want to be able to make my own state change when when the return from useTransition completes. That is, currently, in the example below, isPending toggles from true to false, but that's not enough. I want to be able to execute code on that change.
That is, in the example below, I would like to possible do something similar to what useEffect does, and have the code that is returned from the startTransition execute when the pending event changes, but that does not work.
Any other options?
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId);
setResource(fetchProfileData(nextUserId));
return () => { DO THIS CODE WHEN PENDING BECOMES FALSE }
});
}}
function App() {
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition({
timeoutMs: 3000
});
return (
<>
<button
disabled={isPending}
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId);
setResource(fetchProfileData(nextUserId));
});
}}
>
Next
</button>
{isPending ? " Loading..." : null}
<ProfilePage resource={resource} />
</>
);
}
It's is not possible within the startTransition call / useTransition config. You can use useEffect/useLayoutEffect to run some code after resource update.
useEffect/useLayoutEffect(() => {
// some code after resource state update
}, [resource])

Update state when returning to previous page

I'm getting a description from a query call which is showing on the page "profile". When the user wants to edit this description they go to another page called "editProfile". When they are done editing they can press submit which will change the description on the server. My problem is that when I return to the "profile" page the description is not updated in the UI. How do I best fix this? With a hook or some sort of state management like redux? Thanks in advance
function Profile({ navigation }: ProfileStackNavProps<"Profile">) {
const { loading, error, data } = useQuery(GET_DESCRIPTIONS_FROM_ID);
const [description, setDescription] = useState(data.profiles[0].description);
return (
<Text>{description}</Text>
//bunch of other code
<Button
onPress={() => {
navigation.navigate("EditProfile", { description });
}}
>
)
}
function EditProfile({ route }: ProfileStackNavProps<"EditProfile">) {
const [text, setText] = useState(route.params.description);
const [editDescription, { loading, error }] = useMutation(EDIT_DESCRIPTION);
return (
<Center>
<TextInput onChangeText={setText} value={text} editable={true} />
<MyButton
title={"Save"}
onPress={() => {
editDescription({
variables: { description: text },
});
}}
/>
</Center>
);
}
when navigate to EditProfile, you pass a callback function like this:
<Button
onPress={() => {
navigation.navigate("EditProfile", { description,cb:(newDes)=>{setDescription(newDes)} });
}}
>
then, when you finish edit, call the function you pass:
route.params.cb(newDes)

ReactJS - passing part of onClick function as a prop from parent component

Hey guys I am trying to pass a command in a prop to child component from a parent. The thing is that after my button is clicked a state in parent component has to be changed for intended sidebar to open. I seem to not get it right with the .then() part at the end on the onClick function, as I get error: ×
Unhandled Rejection (TypeError): Cannot read property 'then' of undefined
CHILD COMPONENT:
return (
<div
className="results-item"
onClick={async () => {
let place_detail;
try {
const URL = `https://maps.googleapis.com/maps/api/place/details/json?place_id=${places.place_id}&fields=name,rating,formatted_phone_number&key=
MYAPIKEY`;
const response = await axios.get(URL);
console.log(response.data);
place_detail = response.data.results;
} catch (error) {
console.log(error.message);
}
this.setState({ place_detail }).then(this.props.sideBarOpen);
}}
>
<ResultsItem
name={places.name}
image={Thumbnail}
rating={places.rating}
rating_total={places.user_ratings_total}
/>
</div>
);
PARENT COMPONENT:
<Search sideBarOpen={() => setOpen(!open)} />
setState doesn't return a promise. Use a callback instead:
this.setState({ place_detail }, () => {
this.props.sideBarOpen();
});
You can't use .then on setState call. You can use callback function of setState to what you are trying to do -
this.setState({ place_detail }, () => this.props.sideBarOpen());

How to notify parent component of property change when using react hooks?

Lets say I have a parent component and child component. The parent component is composed of several child components. The parent component holds and manages a very complex and deep data object. Each child component provides the UI to manage various child objects and properties of the main data object. Whenever the child component changes a property value in the data object hierarchy, that change needs to bubble up to the main data object.
Here is how I might do it in a child component class by passing in a callback object...
<div>
<button onClick={e => this.setState({propA: e.target.value}, () => props.onChangePropA(this.state.propA)}>Prop A</button>
<button onClick={e => this.setState({propB: e.target.value}, () => props.onChangePropB(this.state.propB)}>Prop B</button>
</div>
Versus how I think I need to do it using hooks. The main problem I'm seeing is that there is no callback option for after the state change has completed. So I have to detect it in the useEffect and figure out which property just changed...
let prevPropA = props.propA;
let prevPropB = props.propB;
const [propA, setPropA] = useState(props.propA);
const [propB, setPropB] = useState(props.propB);
useEffect(() => {
if (prevPropA != propA) props.onChangePropA(propA);
if (prevPropB != propB) props.onChangePropB(propB);
});
<div>
<button onClick={e => {prevPropA = propA; setPropA(e.target.value)}}>Prop A</button>
<button onClick={e => {prevPropB = propB; setPropB(e.target.value)}}>Prop B</button>
</div>
I see this method getting extremely cumbersome and messy. Is there a more robust/proper way to accomplish this?
Thanks
=============================================================
Below is updated sample code based on Shubham's answer and
Ryan's feedback. Shubham answered the question as asked, but
Ryan is suggesting I give a more thorough example to ensure
I'm giving the right info for the right answer.
Here is sample code that more closely follows my real world
situation... although still a simplified example.
The parent component manages comments from users. Imagine
they can create new comments and select a date or a date-range.
They can also update existing comments. I have put the date
and date-range selector in its own component.
Therefore the parent comment manager component needs the ability
to create/load comments and pass the associated date(s) down to the
date-selector component. The user can then change the date(s)
and those values need to be propagated back up to the parent comment
manager to later be sent to the server and saved.
So you see, there is a bidirectional flow of property values (dates, etc)
that can be changed at any time from either end.
NOTE: This new example is updated using a method similar to what
Shubham suggested based on my original question.
=============================================================
const DateTimeRangeSelector = (props) =>
{
const [contextDateStart, setContextDateStart] = useState(props.contextDateStart);
const [contextDateEnd, setContextDateEnd] = useState(props.contextDateEnd);
const [contextDateOnly, setContextDateOnly] = useState(props.contextDateOnly);
const [contextDateHasRange, setContextDateHasRange] = useState(props.contextDateHasRange);
useEffect(() => { setContextDateStart(props.contextDateStart); }, [ props.contextDateStart ]);
useEffect(() => { if (contextDateStart !== undefined) props.onChangeContextDateStart(contextDateStart); }, [ contextDateStart ]);
useEffect(() => { setContextDateEnd(props.contextDateEnd); }, [ props.contextDateEnd ]);
useEffect(() => { if (contextDateEnd !== undefined) props.onChangeContextDateEnd(contextDateEnd); }, [ contextDateEnd ]);
useEffect(() => { setContextDateOnly(props.contextDateOnly); }, [ props.contextDateOnly ]);
useEffect(() => { if (contextDateOnly !== undefined) props.onChangeContextDateOnly(contextDateOnly); }, [ contextDateOnly ]);
useEffect(() => { setContextDateHasRange(props.contextDateHasRange); }, [ props.contextDateHasRange ]);
useEffect(() => { if (contextDateHasRange !== undefined) props.onChangeContextDateHasRange(contextDateHasRange); }, [ contextDateHasRange ]);
return <div>
<ToggleButtonGroup
exclusive={false}
value={(contextDateHasRange === true) ? ['range'] : []}
selected={true}
onChange={(event, value) => setContextDateHasRange(value.some(item => item === 'range'))}
>
<ToggleButton value='range' title='Specify a date range' >
<FontAwesomeIcon icon='arrows-alt-h' size='lg' />
</ToggleButton>
</ToggleButtonGroup>
{
(contextDateHasRange === true)
?
<DateTimeRangePicker
range={[contextDateStart, contextDateEnd]}
onChangeRange={val => { setContextDateStart(val[0]); setContextDateEnd(val[1]); }}
onChangeShowTime={ val => setContextDateOnly(! val) }
/>
:
<DateTimePicker
selectedDate={contextDateStart}
onChange={val => setContextDateStart(val)}
showTime={! contextDateOnly}
/>
}
</div>
}
const CommentEntry = (props) =>
{
const [activeComment, setActiveComment] = useState(null);
const createComment = () =>
{
return {uid: uuidv4(), content: '', contextDateHasRange: false, contextDateOnly: false, contextDateStart: null, contextDateEnd: null};
}
const editComment = () =>
{
return loadCommentFromSomewhere();
}
const newComment = () =>
{
setActiveComment(createComment());
}
const clearComment = () =>
{
setActiveComment(null);
}
return (
<div>
<Button onClick={() => newComment()} variant="contained">
New Comment
</Button>
<Button onClick={() => editComment()} variant="contained">
Edit Comment
</Button>
{
activeComment !== null &&
<div>
<TextField
value={(activeComment) ? activeComment.content: ''}
label="Enter comment..."
onChange={(event) => { setActiveComment({...activeComment, content: event.currentTarget.value, }) }}
/>
<DateTimeRangeSelector
onChange={(val) => setActiveComment(val)}
contextDateStart={activeComment.contextDateStart}
onChangeContextDateStart={val => activeComment.contextDateStart = val}
contextDateEnd={activeComment.contextDateEnd}
onChangeContextDateEnd={val => activeComment.contextDateEnd = val}
contextDateOnly={activeComment.contextDateOnly}
onChangeContextDateOnly={val => activeComment.contextDateOnly = val}
contextDateHasRange={activeComment.contextDateHasRange}
onChangeContextDateHasRange={val => activeComment.contextDateHasRange = val}
/>
<Button onClick={() => clearComment()} variant="contained">
Cancel
</Button>
<Button color='primary' onClick={() => httpPostJson('my-url', activeComment, () => console.log('saved'))} variant="contained" >
<SaveIcon/> Save
</Button>
</div>
}
</div>
);
}
useEffect takes a second argument which denotes when to execute the effect. You can pass in the state value to it so that it executes when state updates. Also you can have multiple useEffect hooks in your code
const [propA, setPropA] = useState(props.propA);
const [propB, setPropB] = useState(props.propB);
useEffect(() => {
props.onChangePropA(propA);
}, [propA]);
useEffect(() => {
props.onChangePropB(propB);
}, [propB]);
<div>
<button onClick={e => {setPropA(e.target.value)}}>Prop A</button>
<button onClick={e => {setPropB(e.target.value)}}>Prop B</button>
</div>

disable button in reactjs

I have a series of buttons that execute internal logic(no forms not dependant on input), but call the functions asynchronously. I would like to disable the button after one click, and have tried several things on onclick() method but keep getting errors.
Code looks something like this:
{ this.state.isEthTransferVisible && <button id="button"
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}>Check Balances</button>
}
this is the function called from within the onclick
async handleTransferFromEthereum(){
await parseAddress(this.state.sc)
this.setState(prevState => ({
isEthTransferVisible: !prevState.isEthTransferVisible,
isGoDeployedVisible: !prevState.isGoDeployedVisible
}));
}
Add another state variable, such as this.isEthTransferEnabled (Default true). Change your button to:
{ this.state.isEthTransferVisible && <button id="button"
disabled={this.state.isEthTransferEnabled}
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}>Check Balances</button>
}
And change your handleTransferFromEthereum method:
async handleTransferFromEthereum(){
this.setState({ isEthTransferEnabled: false });
await parseAddress(this.state.sc)
this.setState(prevState => ({
isEthTransferVisible: !prevState.isEthTransferVisible,
isEthTransferEnabled: true,
isGoDeployedVisible: !prevState.isGoDeployedVisible
}));
}
onClick={() => { parseAddress(this.state.sc);}, this.handleTransferFromEthereum}
Wrong syntax? It should be:
onClick={() => {
parseAddress(this.state.sc);
this.handleTransferFromEthereum();
}}

Resources