I am trying to convert this code to not use state as I have heard using setState in _app.js is not recommended and leads to errors. I can't quite figure out how to do it. Any help would be awesome.
Thanks
export default class BookingApp extends App {
.....
componentDidMount() {
Router.events.on('routeChangeStart', () => {
this.setState({ isLoading: true })
})
Router.events.on('routeChangeComplete', () => {
this.setState({ isLoading: false })
})
Router.events.on('routeChangeError', () => {
this.setState({ isLoading: false })
})
}
return (
....
{this.state.isLoading && (
<div>
<Loader />
</div>
)}
...Other Content
Currently you have only made the UI aspect of the Loader into a separate component.
Move the entire logic into the seperate Loader component instead of the _app.js file.
Below is a hooks implementation of the Loader component. It can be done using class components too.
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
const Loader = () => {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
router.events.on('routeChangeStart', () => {
setIsLoading(true);
});
router.events.on('routeChangeComplete', () => {
setIsLoading(false);
});
router.events.on('routeChangeError', () => {
setIsLoading(false);
});
}, []);
return (
<>
{
isLoading && (
<LoaderUIComponentComesHere />
)
}
</>
);
};
export default Loader;
Related
I am trying to set an array dynamically and render it using useState hook. But it seems the array is not setting. below is my code:
import React, { useState, useEffect } from "react";
export default ({ item }) => {
const [attachments, setAttachments] = useState([]);
const setAttachmentValues = function(response){
setAttachments(response.data);
}
const fetchMedia = async ()=> {
setAttachments([]);
await apiCall().then((response) => {
setAttachmentValues(response);
});
}
useEffect(() => {
fetchMedia();
}, []);
return (
<>
<div className="w-full">
{(attachments.map((ele) => {
<div>{ele}</div>
)}
</>
)
}
apiCall() will return an array of objects.
setState in this way is working well in some cases. What is the actual issue here?
This way you can render data
import React, { useState, useEffect } from 'react';
export default ({ item }) => {
const [attachments, setAttachments] = useState([]);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((response) => {
setAttachments(response);
console.log(response);
});
}, []);
return (
<>
<div>
{attachments.map(item => <div key={item.username}> {item.username} </div> )}
</div>
</>
);
};
i want to run the useEffect first before the render function which is placed inside the <Route /> tag starts to render. i expect to get currently available user details through the API and assigne them to render function.
but render function runs before the UseEffect retrieve data from the API. so help me to find the solution.
import React, { useEffect, useState } from "react";
import { Route, Redirect } from "react-router-dom";
import { Auth } from "aws-amplify";
const ProtectedRoute = ({ children, ...rest }) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
setIsAuthenticated(
Auth.currentAuthenticatedUser({
// bypassCache: false,
})
.then((user) => console.log(user))
.catch((err) => console.log(err))
);
}, []);
return (
<Route
{...rest}
render={({ location }) =>
(isAuthenticated ) ? (
children
) : (
<Redirect
to={{
// pathname: "/login",
pathname: "/create-profile",
state: { from: location },
}}
/>
)
}
/>
);
};
export default ProtectedRoute;
Try this
useEffect(() => {
Auth.currentAuthenticatedUser({
// bypassCache: false,
})
.then((user) => user && setIsAuthenticated(true))
.catch((err) => err && setIsAuthenticated(false));
}, []);
You could wrap that authentication stuff into a hook of your own, and then simply not render anything until it's ready:
function useIsAuthenticated() {
const [isAuthenticated, setIsAuthenticated] = useState(null);
useEffect(() => {
Auth.currentAuthenticatedUser({})
.then(setIsAuthenticated)
.catch((err) => {
console.log(err);
setIsAuthenticated(false);
});
}, []);
return isAuthenticated;
}
const ProtectedRoute = ({ children, ...rest }) => {
const isAuthenticated = useIsAuthenticated(); // Will be the user if authenticated, null if busy, or false if error.
if (isAuthenticated === null) {
return null; // Don't render anything if authentication state is unknown
}
return <>...</>;
};
I've created a common component and exported it, i need to call that component in action based on the result from API. If the api success that alert message component will call with a message as "updated successfully". error then show with an error message.
calling service method in action. is there any way we can do like this? is it possible to call a component in action
You have many options.
1. Redux
If you are a fan of Redux, or your project already use Redux, you might want to do it like this.
First declare the slice, provider and hook
const CommonAlertSlice = createSlice({
name: 'CommonAlert',
initialState : {
error: undefined
},
reducers: {
setError(state, action: PayloadAction<string>) {
state.error = action.payload;
},
clearError(state) {
state.error = undefined;
},
}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const error = useSelector(state => state['CommonAlert'].error);
const dispatch = useDispatch();
return <>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() =>
dispatch(CommonAlertSlice.actions.clearError())} />
{children}
</>
}
export const useCommonAlert = () => {
const dispatch = useDispatch();
return {
setError: (error: string) => dispatch(CommonAlertSlice.actions.setError(error)),
}
}
And then use it like this.
const App: React.FC = () => {
return <CommonAlertProvider>
<YourComponent />
</CommonAlertProvider>
}
const YourComponent: React.FC = () => {
const { setError } = useCommonAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <> ... </>
}
2. React Context
If you like the built-in React Context, you can make it more simpler like this.
const CommonAlertContext = createContext({
setError: (error: string) => {}
});
export const CommonAlertProvider: React.FC = ({children}) => {
const [error, setError] = useState<string>();
return <CommonAlertContext.Provider value={{
setError
}}>
<MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
{children}
</CommonAlertContext.Provider>
}
export const useCommonAlert = () => useContext(CommonAlertContext);
And then use it the exact same way as in the Redux example.
3. A Hook Providing a Render Method
This option is the simplest.
export const useAlert = () => {
const [error, setError] = useState<string>();
return {
setError,
renderAlert: () => {
return <MyAlert
visible={error !== undefined}
body={error} onDismiss={() => setError(undefined)} />
}
}
}
Use it.
const YourComponent: React.FC = () => {
const { setError, renderAlert } = useAlert();
useEffect(() => {
callYourApi()
.then(...)
.catch(err => {
setError(err.message);
});
});
return <>
{renderAlert()}
...
</>
}
I saw the similar solution in Antd library, it was implemented like that
codesandbox link
App.js
import "./styles.css";
import alert from "./alert";
export default function App() {
const handleClick = () => {
alert();
};
return (
<div className="App">
<button onClick={handleClick}>Show alert</button>
</div>
);
}
alert function
import ReactDOM from "react-dom";
import { rootElement } from ".";
import Modal from "./Modal";
export default function alert() {
const modalEl = document.createElement("div");
rootElement.appendChild(modalEl);
function destroy() {
rootElement.removeChild(modalEl);
}
function render() {
ReactDOM.render(<Modal destroy={destroy} />, modalEl);
}
render();
}
Your modal component
import { useEffect } from "react";
export default function Modal({ destroy }) {
useEffect(() => {
return () => {
destroy();
};
}, [destroy]);
return (
<div>
Your alert <button onClick={destroy}>Close</button>
</div>
);
}
You can't call a Component in action, but you can use state for call a Component in render, using conditional rendering or state of Alert Component such as isShow.
I am following along with a React.js tutorial.
In it, a website was created with class based components. But now, it's being converted to functional component.
This is a HOC, which was at first returning a class based component but now a functional one.
Now, when i use useState and useEffect in it, it gives the error that :
Line 8:36: React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function
and
Line 29:9: React Hook "useEffect" cannot be called inside a callback. React Hooks must be
called in a React function component or a custom React Hook function
i'm using react version 17, while the instructor was using version 16.
This is the code when it was class based and working:
import React, {Component} from 'react'
import Modal from "../../components/UI/Modal/Modal";
const WithErrorHandler = (WrappedComponent , axios) =>
{
return class extends Component
{
state = {
error : null
}
componentWillMount()
{
this.resInterceptor = axios.interceptors.response.use(res => res , (error) =>
{
this.setState({error : error});
});
this.reqInterceptor =axios.interceptors.request.use((req) =>
{
this.setState({error : null});
return(req);
} );
}
componentWillUnmount()
{
axios.interceptors.request.eject(this.reqInterceptor);
axios.interceptors.response.eject(this.resInterceptor);
}
errorConfirmedHandler = () =>
{
this.setState({error : null});
}
render()
{
return(
<>
<Modal
show = {this.state.error}
modalClosed = {this.errorConfirmedHandler}>
{this.state.error ? this.state.error.message : null}
</Modal>
<WrappedComponent {...this.props}/>
</>
);
}
}
}
export default WithErrorHandler;
and this is the code when it's converted to functional component and not working :
import React, {useState , useEffect} from 'react'
import Modal from "../../components/UI/Modal/Modal";
const WithErrorHandler = (WrappedComponent , axios) =>
{
return props => {
const [error , seterror] = useState(null);
const resInterceptor = axios.interceptors.response.use(res => res , (error) =>
{
seterror(error);
});
const reqInterceptor =axios.interceptors.request.use((req) =>
{
seterror(null);
return(req);
} );
useEffect(() =>
{
return () =>
{
axios.interceptors.request.eject(reqInterceptor);
axios.interceptors.response.eject(resInterceptor);
}
} , [reqInterceptor , resInterceptor])
const errorConfirmedHandler = () =>
{
seterror(null);
}
return(
<>
<Modal
show = {error}
modalClosed = {errorConfirmedHandler}>
{error ? error.message : null}
</Modal>
<WrappedComponent {...props}/>
</>
);
}
}
export default WithErrorHandler;
Any help or guidance will be appreciated, thankyou.
Do you need those effects to run inside the returned component? One solution is to move all the hooks outside into the Wrapping component:
import React, { useState, useEffect } from 'react'
import Modal from '../../components/UI/Modal/Modal'
const WithErrorHandler = (WrappedComponent, axios) => {
const [error, seterror] = useState(null)
useEffect(() => {
const resInterceptor = axios.interceptors.response.use(
(res) => res,
(error) => {
seterror(error)
},
)
const reqInterceptor = axios.interceptors.request.use((req) => {
seterror(null)
return req
})
return () => {
axios.interceptors.request.eject(reqInterceptor)
axios.interceptors.response.eject(resInterceptor)
}
}, [axios.interceptors.request, axios.interceptors.response])
return (props) => {
const errorConfirmedHandler = () => {
seterror(null)
}
return (
<>
<Modal show={error} modalClosed={errorConfirmedHandler}>
{error ? error.message : null}
</Modal>
<WrappedComponent {...props} />
</>
)
}
}
export default WithErrorHandler
Alternatively, you can turn that callback into its own component:
import React, { useState, useEffect } from 'react'
import Modal from '../../components/UI/Modal/Modal'
const WithErrorHandler = (WrappedComponent, axios) => (props) => (
<Wrapped {...props} axios={axios} WrappedComponent={WrappedComponent} />
)
const Wrapped = ({ axios, WrappedComponent, ...props }) => {
const [error, seterror] = useState(null)
const resInterceptor = axios.interceptors.response.use(
(res) => res,
(error) => {
seterror(error)
},
)
const reqInterceptor = axios.interceptors.request.use((req) => {
seterror(null)
return req
})
useEffect(() => {
return () => {
axios.interceptors.request.eject(reqInterceptor)
axios.interceptors.response.eject(resInterceptor)
}
}, [axios.interceptors.request, axios.interceptors.response, reqInterceptor, resInterceptor])
const errorConfirmedHandler = () => {
seterror(null)
}
return (
<>
<Modal show={error} modalClosed={errorConfirmedHandler}>
{error ? error.message : null}
</Modal>
<WrappedComponent {...props} />
</>
)
}
export default WithErrorHandler
I use AppContext, when I fetch data from server I want it to save in context but on the first render it doesn't save. If I make something to rerender state data appears in context.
Here is my code:
useEffect(() => {
fetch('https://beautiful-places.ru/api/places')
.then((response) => response.json())
.then((json) => myContext.updatePlaces(json))
.then(() => console.log('jsonData', myContext.getPlaces()))
.catch((error) => console.error(error));
}, []);
My getPlaces and updatePlaces methods:
const [allPlaces, setAllPlaces] = useState();
const getPlaces = () => {
return allPlaces;
};
const updatePlaces = (json) => {
setAllPlaces(json);
};
const placesSettings = {
getPlaces,
updatePlaces,
};
Here is how I use AppContext:
<AppContext.Provider value={placesSettings}>
<ThemeProvider>
<LoadAssets {...{ assets }}>
<SafeAreaProvider>
<AppStack.Navigator headerMode="none">
<AppStack.Screen
name="Authentication"
component={AuthenticationNavigator}
/>
<AppStack.Screen name="Home" component={HomeNavigator} />
</AppStack.Navigator>
</SafeAreaProvider>
</LoadAssets>
</ThemeProvider>
</AppContext.Provider>;
Could you explain please why my console.log('jsonData', ...) returns undefined?
I don't understand because on previous .then I saved it.
Edit to note that the code below is not copy-paste ready. It is an example of how to attack the problem – you will need to implement it properly in your project.
The 'problem' is that hooks are asynchronous – in this specific case, your useEffect further uses an asynchronous fetch too.
This means that the data that is returned by the fetch will only be available after the component has rendered, and because you're not updating state/context using a hook, the context won't update.
The way to do this requires a few changes.
In your context implementation, you should have a setter method that sets a state variable, and your getter should be that state variable.
placesContext.js
import React, { createContext, useState } from "react";
export const placesContext = createContext({
setPlaces: () => {},
places: [],
});
const { Provider } = placesContext;
export const PlacesProvider = ({ children }) => {
const [currentPlaces, setCurrentPlaces] = useState(unit);
const setPlaces = (places) => {
setCurrentPlaces(places);
};
return (
<Provider value={{ places: currentPlaces, setPlaces }}>{children}</Provider>
);
};
Wrap your App with the created Provider
App.js
import { PlacesProvider } from "../path/to/placesContext.js";
const App = () => {
// ...
return (
<PlacesProvider>
// Other providers, and your app Navigator
</PlacesProvider>
);
}
Then, you should use those variables directly from context.
MyComponent.js
import { placesContext } from "../path/to/placesContext.js";
export const MyComponent = () => {
const { currentPlaces, setPlaces } = useContext(placesContext);
const [hasLoaded, setHasLoaded] = useState(false);
useEffect(() => {
async function fetchPlacesData() {
const placesData = await fetch('https://beautiful-places.ru/api/places');
if (placesData) {
setPlaces(placesData);
} else {
// error
}
setHasLoaded(true);
}
!hasLoaded && fetchPlacesData();
}, [hasLoaded]);
return (
<div>{JSON.stringify(currentPlaces)}</div>
)
};