Not able to update state using localStorage - reactjs

I am trying log back in using the stored credentials but it doesn't work and I have tried everything . The dispatch function works fine with the form but it doesn't works with the localStorage .
App.tsx :
useEffect(() => {
const userDetails=localStorage.getItem('user')
if (userDetails) {
const user= JSON.parse(userDetails);
login(user); // dispatch function
}
});

If you are sure, value in the localStorage and useEffect should be called just once — on the component mount stage; I recommend sending the value through the props. With this approach, it will be much easier to guess what is going on.
const ParentComponent = () => (
<Component
user={
JSON.parse(localStorage.getItem('user'))
}
/>
)
const Component = ({
user
}) => {
useEffect(() => {
login(user); // dispatch function
}, [user]);
return <></>
};

Related

NextJS + Supabase - Blank Page Issue

I am attempting to render either an Application or Login page depending on whether getUser() returns a user object.
However, in both development and production, a blank page is rendered.
This is the code
export default function index() {
supabase.auth.getUser().then((response) => {
const userData = response.data.user;
console.log(userData);
return userData != undefined || userData != null ? (
<>
<Shell />
<AppView />
</>
) : (
<NoSessionWarn />
);
});
}
I use NextJS's router.push('/application') to route the user to this page, in case that might have something to do with it.
Any idea why this could be showing a blank page? I've tried taking the return block out of the .then() block and still nothing.
Few things:
In React functional components, side effects must be handled inside
a useEffect hook
React components names should be capitalized (Index instead of index in your case).
Most of the time it's a better idea to use strict equality operator since it also checks for the type of the operands.
As a suggestion, you could abstract the logic of the auth checking process into a custom hook. This not only increases the readability of the component, but also makes this logic reusable and you now would have separation of concerns. Your component doesn't know and doesn't care about how the user data is being retrieved, it just uses it.
Putting it all together:
useAuth custom hook:
export const useAuth = () => {
const [user, setUser] = useState(null)
const [isAuthorizing, setIsAuthorizing] = useState(true)
useEffect(() => {
supabase.auth
.getUser()
.then((response) => {
setUser(response.data.user)
})
.catch((err) => {
console.error(err)
})
.finally(() => {
setIsAuthorizing(false)
})
}, [])
return { user, isAuthorizing }
}
Component:
export default function Index() {
const { user, isAuthorizing } = useAuth()
if (isAuthorizing) return <p>Loading</p>
// Being very explicit here about the possible falsy values.
if (user === null || user === undefined) return <NoSessionWarn />
return (
<>
<Shell />
<AppView />
</>
)
}
You need to use the useState hook to re-render when you receive the data.
You need to use the useEffect hook with an empty dependency array to execute getUser() once on mount.
You'll also probably want a loading mechanism while the request is made.
export default function index() {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
supabase.auth.getUser().then((response) => {
setUserData(response.data.user);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>
if (!userData) return <NoSessionWarn />;
return (
<>
<Shell />
<AppView />
</>
);
}
Example: https://stackblitz.com/edit/react-ts-neq5rh?file=App.tsx

React functional component re-renders with old data

I have this functional component that is used as a child component. Like this:
//... in the ParentComponent
<ChildComponent
id={somevalue}
/>
and the Child Component looks like this:
const ChildComponent = ({
id
}) => {
const [rawData, setRawData] = useState([]);
const [processedData, setProcessedData] = useState([]);
useEffect(() => {
// ... do some time consuming calculations
SetProcessedData(data);
}, [rawData]);
useEffect(() => {
console.log('useEffect');
// fetch data with axios and ...
() => {
const reloadData = async () => {
axios.request({
url,
data,
...config,
})
.then((result) => {
SetRawData(data);
})
};
reloadData();
}, [id]);
console.log('Render');
return(
{processedData.map( ....) }
);
}
The first time the ChildComponent is render everything works like charm.
The problem occurs when somevalue and the prop (id) is changed.
The child sees the new props and rerenders the component AND calls useEffect[id]
The problem is that the render happens BEFORE the useEffect so the processedData state is still old.
I tried to avoid the first rerender by setting a isLoading variable but the first place I can do it is the useEffect[id] which is called AFTER the rerender.
So the console shows:
Render
useEffect
What am I missing?

React and redux usage with async data fetching

I have this action
export function fetchBranches() {
return async dispatch => {
const result = await axios.get('https://localhost:5002/Branches')
dispatch({ type: FETCH_BRANCHES, payload: result.data.value })
}
}
and such reducer
export const branchReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_BRANCHES: {
return { ...state, branches: action.payload }
}
default: return state
}
}
In my component, I'm try to do such thing
const dispatch = useDispatch()
dispatch(fetchBranches())
const branches = useSelector(state => state.branches.branches)
return <>
some component that uses branches
</>
So my problem is i'm getting infinite number of request trying to fetch (I can see them in redux dev tools).
My page are not getting updated, but if go to other page and then return to one that tries perform this fetch, I'm can see values in store and at the page. So questions are:
Where and how should I dispatch actions to fetch some data to render it then?
Why I'm getting that much requests and how can I fix it?
UPD:
Thanks a lot for your answers, but I still see behavior that my component rendered before I received data from api. I wanted to try useState and set state in useEffect, but I can't use useSelector. What should I do to re-render component as soon as my data loaded?
UPD2: now my component look like
function BranchList() {
const [isLoaded, setIsLoaded] = useState(false)
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchBranches())
setIsLoaded(true)
}, [])
const branches = useSelector(state => state.branches.branches)
const headerNames = ["Id", "Name"]
if (!isLoaded) {
return <>Loading...</>
}
return (
<EditableTable
data={branches}
headerNames={headerNames}
onEditConfirm={(row) => dispatch(updateBranch(row))}
onDelete={(id) => dispatch(deleteBranch(id))} />
)
}
Dispatching generally should never be done directly in render, but in a useEffect or an event callback.
In your case,
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchBranches());
},
[dispatch]
)
const branches = useSelector(state => state.branches.branches)
Also please note that you are writing a pretty old style of redux here - please read the official tutorials to learn the recommended "modern" style of redux we are officially recommending. You'll end up writing a lot less code.
Perhaps if you try this:
function BranchList() {
const [isLoaded, setIsLoaded] = useState(false)
const dispatch = useDispatch()
useEffect(() => {
if(!isLoaded) {
dispatch(fetchBranches())
.then(() => setIsLoaded(true))
}
}, [isLoaded])
const branches = useSelector(state => state.branches.branches)
const headerNames = ["Id", "Name"]
if (!isLoaded) {
return <>Loading...</>
}
return (
<EditableTable
data={branches}
headerNames={headerNames}
onEditConfirm={(row) => dispatch(updateBranch(row))}
onDelete={(id) => dispatch(deleteBranch(id))} />
)
}
Your HTTP request action causes side effects in the component. Every state change causes re-rendering the component. To avoid side effects, you should use useEffect hook in your component.
In your component,
const dispatch = useDispatch()
const onFetchBranches = useCallback(() => dispatch(fetchBranches()), [dispatch]);
const branches = useSelector(state => state.branches.branches)
useEffect(() => {
onFetchBranches();
}, [onFetchBranches]);
return <>
some component that uses branches
</>
You should check Reactjs documentation to understand useEffect hook better.
https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
dispatch an action into useEffect hook to solve your issue.
Try:
useEffect(() => dispatch(fetchBranches()), [])
You should try putting your dispatch action into an useEffect, so the code will be only executed once like so:
const dispatch = useDispatch()
React.useEffect(() => {
dispatch(fetchBranches())
}, [])
Documentation here.

Component state doesn't change after action dispatch

On startup I use useEffect to fetch some data from local storage. If the fetch was successful I want to send an action and change the state of the component. After dispatching the action, I see that the reducer received an action and returned the new state, but I don't see any change in the component when I try to log the new state. What can be the reason for this behaviour.
Here's the action.ts:
import {TOKEN_VALIDITY} from './actionTypes'
export const setTokenValidity = (isTokenValid: boolean) => ({
type: TOKEN_VALIDITY,
isTokenValid
})
Here's the reducer.ts:
const auth = (state = false, action: any) => {
// after dispatch I see this log in the console value is true
console.log('auth reducer action type is ' + action.type + ' value is ' + action.isTokenValid)
switch(action.type) {
case TOKEN_VALIDITY:
return action.isTokenValid
default:
return state
}
}
export default auth
In the component I want to update the state of isTokenValid, but I always get undefined for the value.
This is the component code
const Stack = createStackNavigator();
let userToken = null;
const App = (props:any) => {
useEffect(() => {
const bootstrapAsync = async () => {
try {
userToken = await retrieveData('Token',null);
SplashScreen.hide();
if(userToken != null) {
props.setTokenValidity(true)
}
// this logs undefined for props.isTokenValid -- why???
console.log("after token isValid: " + props.isTokenValid)
console.log('token ' + userToken);
} catch (e) {
// Restoring token failed
}
};
bootstrapAsync();
}, []);
return (
<NavigationContainer>
<Stack.Navigator >
{!props.isTokenValid ? (
<>
<Stack.Screen name="Login" component={Login} options={{ headerShown:false }}/>
<Stack.Screen name="Home" component={Home} options={{ headerShown:false }}/>
</>
) : (
<Stack.Screen name="Home" component={Home} options={{ headerShown:false }}/>
)}
</Stack.Navigator >
</NavigationContainer>
);
};
const mapDispatchToProps = (dispatch:any) => ({
setTokenValidity: (isTokenValid:boolean) => dispatch(setTokenValidity(isTokenValid))
})
const mapStateToProps = (state:any) => ({
isTokenValid: state.isTokenValid
})
export default connect(mapStateToProps, mapDispatchToProps)(App);
In the first rendering, i.e. when the useEffect is fired and you call the method using props.setTokenValidity to set the token validity, the token gets set. However, the console also gets printed on the same rendering.
When the state gets updated and you get an updated one using the props.isTokenValid, this is the 2nd re-rendering(not the same rendering when useEffect was called) and the useEffect doesn't fire, therefore we don't see the console being printed with the new value.
If you for some reason want to log when isTokenValid is set, use another useEffect
useEffect(() => {
console.log("after token isValid: " + props.isTokenValid)
},[props.isTokenValid]);
Reducers return a state object so you might wanna try doing this:
switch(action.type) {
case TOKEN_VALIDITY:
return { ...state, isTokenValid: action.isTokenValid }
default:
return state
}
The way I solved it in my project, is by creating multiple useEffects.
In order to solve your problem you need to do 2 things:
You need to run bootstrapAsync()
You need to check after bootstrapAsync finishes for the data that was set into userToken
You have already did no.1 successfully, And your current problem is that your component does not updates when it receives new data, aka when userToken updates.
The solution:
Write another useEffect function which will be rendered 2 times: one time on component load(which we will ignore because the fetch isn't done yet) and another time when userToken value updates.
In order to avoid running our new useEffect on component load, we need to create a new state, which we will call allowNavigation.
allowNavigation prop will be set to true only after the fetch is complete.
Then, only when allowNavigation is set to true, we can check userToken and handle it properly.
The following code will help you:
const App = (props:any) => {
const [allowNavigate, setAllowNavigate] = useState(false);
useEffect(() => {
const bootstrapAsync = async () => {
try {
userToken = await retrieveData('Token',null);
SplashScreen.hide();
setAllowNavigate(true);
...
};
bootstrapAsync();
}, []);
useEffect(() => {
if (allowNavigate)
// this will now log the correct values
console.log("after token isValid: " + props.isTokenValid)
console.log('token ' + userToken);
}, [allowNavigate]);
...

Error when initializing the child component

I am going crazy with useEffect. I started coding 3 months ago and I am not really experienced.
I have a parent component which initializes some data from database with useEffect then I pass that data as props to a child component which initializes some other data from database with useEffect. I can't make it work no matter what I try. I think that the reason is because component unmounts before the child component's initialization is done. I have been reading documentations but couldn't figure out how to overcome this problem.
I appreciate if you can help me solve it.
...
import ModelDesigner from './subComponents/ModelDesigner'
const Model = ({ match }) => {
const [model, setModel] = useState({})
const initialize = async (id) => {
try {
const res = await axios.get(`/model/${id}`)
setModel(res.data)
} catch (err) {
console.log(err.response.data)
}
}
const link = match.params.link
useEffect(() => {
initialize(link)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return (
<ModelDesigner user={model.user} />
)
...
import PropTypes from 'prop-types'
const ModelDesigner = ({ user }) => {
const [profile, setProfile] = useState({})
const loadProfile = async (id) => {
try {
const profile = await axios.get(`/profile/id/${id}`)
setProfile(profile.data)
} catch (err) {
console.log(err.response.data)
}
}
useEffect(() => {
loadProfile(user)
}, [])
return ( ... )
I think I see what's going on here. Model is initialized asynchronously, so when it's first rendered, the model variable is an empty object.
However, the child ModelDesigner is rendered right away and its useEffect callback runs before the model is loaded from the API. So it's probably calling /profile/id/undefined instead of using the profile ID you want.
One way to fix this is to wait on rendering the child component until you've finished loading the data it needs for its props. For this example I guess that would look something like:
return model.user
? <ModelDesigner user={model.user} />
: <div />
Alternatively, you could add user as a dependency for useEffect and skip the API call if it's an empty value. That way it'll wait to try to initialize until it has the ID it needs.
const loadProfile = async (id) => {
if (!id) {
return;
}
try {
const profile = await axios.get(`/profile/id/${id}`)
setProfile(profile.data)
} catch (err) {
console.log(err.response.data)
}
}
useEffect(() => {
loadProfile(user)
}, [user])

Resources