How to return initial state with useReducer? - javascript

I'm using useReducer with useContext to manage state, but I can't return initial value of state in a child component.
In my reducer file I have:
import context from './context';
const initialValue = {
case1token: null,
case2token: false,
};
const reducer = (state, action) => {
switch (action.type) {
case 'case1':
return {
case1token: action.payload,
};
case 'case2':
return {
case2token: action.payload,
};
default:
return initialValue;
}
};
export const {Provider, Context} = context(
reducer,
{
someFunction,
},
{initialValue},
);
In my context file I have :
import React, {useReducer} from 'react';
export default (reducer, actions, defaultValue) => {
const Context = React.createContext();
const Provider = ({children}) => {
const [state, dispatch] = useReducer(reducer, defaultValue);
const boundActions = {};
for (let key in actions) {
boundActions[key] = actions[key](dispatch);
}
return (
<Context.Provider value={{state, ...boundActions}}>
{children}
</Context.Provider>
);
};
return {Context, Provider};
};
In my App.js file I can successfully return the case1token:
import {
Provider as AuthProvider,
Context as AuthContext,
} from './src/Context/auth/authContext';
function App() {
const {state} = useContext(AuthContext);
return (
<NavigationContainer ref={navigationRef}>
<View style={styles.root}>
<StatusBar barStyle="light-content" />
{state.case1token ? <MainFlow /> : <AuthFlow />}
</View>
</NavigationContainer>
);
}
export default () => {
return (
<AuthProvider>
<App />
</AuthProvider>
);
};
In this file I'm trying to return the case2token but it's only returning case1token:
import {Context as AuthContext} from '../../Context/auth/authContext/';
const Home = () => {
const {state} = useContext(AuthContext);
console.log(state);
return (
<SafeAreaView style={styles.screen}>
<Header/>
<Text>{state.case2token}</Text>
</SafeAreaView>
);
};
When I log in App.js both tokens get returned.
Why isn't it returning in the child component?
Appreciate any help!

If value of state.case2token == false Text will not render false as text so u need to do like:
<Text>{state.case2token ? 'true' : 'false'}</Text>

Related

Content provider data no being loaded into a component

I have the main page 'feed' where i used to have three functions, but I moved it into custom context. The console logs in context file output all the objects correctly, but nothing is visible in feed when i concole.log them.
context file:
import React, { useEffect, createContext, useContext, useState } from 'react'
import { useMemo } from 'react';
import getPosts from '../api/getPosts';
import filterImportedPosts from '../utils/filterImportedPosts';
export const ItemContext = createContext({
postData: {}, setPostData: () => { }
});
export const FilteredItemsContext = createContext({ filteredItems: [], setFilteredItems: () => { } })
export const FilterContext = createContext({ filter: '', setFilter: () => { } })
export function useItemContext() {
return useContext(ItemContext)
}
export function useFilteredItemsContext() {
return useContext(FilteredItemsContext)
}
export function useFilterContext() {
return useContext(FilterContext)
}
export default function PostProvider({ children }) {
const [postData, setPostData] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [filter, setFilter] = useState('');
useEffect(() => {
getPosts(setPostData)
console.log('postData: ', postData)
}, []);
useEffect(() => {
// console.log(filter);
const tempFiltItems = filterImportedPosts(postData, filter);
setFilteredItems(tempFiltItems);
console.log('tempFiltItems: ', filteredItems)
}, [filter, postData]);
const filteredItemsState = useMemo(() => {
return { filteredItems, setFilteredItems }
}, [filteredItems, setFilteredItems])
return (
<FilterContext.Provider value={{ filter, setFilter }}>
<FilteredItemsContext.Provider value={filteredItemsState}>
<ItemContext.Provider value={{ postData, setPostData }}>
{children}
</ItemContext.Provider>
</FilteredItemsContext.Provider >
</FilterContext.Provider>
)
}
and here the feed file:
import React, { useState, useEffect } from 'react';
import SinglePost from '../components/singlePost/singlePost';
import FilterPane from '../components/filterPane/filterPane.feedPost';
import { Box, Spinner, Text } from '#chakra-ui/react';
import getPosts from '../api/getPosts';
import Loader from '../../common/Loader';
import filterImportedPosts from '../utils/filterImportedPosts';
import PostProvider, { useFilterContext, useFilteredItemsContext, useItemContext } from './../context/PostDataContext';
export default function Feed() {
//-----------------IMPORT DATA FROM SERVER----------------------
const [error, setError] = useState(null);
const { postData, setPostData } = useItemContext();
const { filter, setFilter } = useFilterContext();
const { filteredItems, setFilteredItems } = useFilteredItemsContext();
useEffect(() => {
console.log(filteredItems)
}, [filteredItems, setFilteredItems])
// this helps while the data is loaded
// if (postData.length === 0) {
// return (
// <Box pos='absolute' top='45vh' left='40%'>
// <Loader />
// </Box>
// )
// }
// console.log(filteredItems);
return (
<PostProvider>
<Box mt={'7vh'} mb={'7vh'} ml={'3vw'} mr={'3vw'} zIndex={200}>
<FilterPane
setFilter={setFilter}
filter={filter}
filteredItems={filteredItems}
/>
{error && (
<div>Error occurred while loading profile info. Details: {error}</div>
)}
{!error && (
<>
{filteredItems.map((item, index) => {
return <SinglePost key={index} item={item} />;
})}
</>
)}
</Box>
</PostProvider>
);
}
console window printscreen. As you can see the filteredItems in context exist but nothing gets shown in the actual feed - the objects are empty. Could someone assist please?

Passing hook function to a component to be used in passed component

Can we pass hook as a function to a component and use it in that component?
Like in the example below I am passing useProps to withPropConnector which returns a Connect component. This useProps is being used in the Connect component. Am I violating any hook rules?
// Landing.jsx
export const Landing = (props) => {
const { isTypeOne, hasFetchedData } = props;
if (!hasFetchedData) {
return <Loader />;
}
const renderView = () => {
if (isSomeTypeOne) {
return <TypeOneView />;
}
return <TypeTwoView />;
};
return (
<>
<Wrapper>
{renderView()}
<SomeNavigation />
</Wrapper>
<SomeModals />
</>
);
};
const useProps = () => {
const { query } = useRouter();
const { UID } = query;
const { isTypeOne, isTypeTwo } = useSelectorWithShallowEqual(getType);
const hasFetchedData = useSelectorWithShallowEqual(
getHasFetchedData(UID)
);
const props = {
isTypeOne,
isTypeTwo,
hasFetchedData
};
return props;
};
export default withPropConnector(useProps, Landing);
// withPropConnector.js
const withPropConnector = (useProps, Component) => {
const Connect = (propsFromParent = emptyObject) => {
const props = useProps(propsFromParent);
return <Component {...propsFromParent} {...props} />;
};
return Connect;
};
export default withPropConnector;

How do I refactor this into `withAuth` using HOC? Or is it possible to use hooks here in Next.js?

import { useSession } from 'next-auth/react'
import { LoginPage } from '#/client/components/index'
const Homepage = () => {
const session = useSession()
if (session && session.data) {
return (
<>
<div>Homepage</div>
</>
)
}
return <LoginPage />
}
export default Homepage
Basically, I don't want to write the same boilerplate of Login & useSession() on every page.
I want something like:
import { withAuth } from '#/client/components/index'
const Homepage = () => {
return (
<>
<div>Homepage</div>
</>
)
}
export default withAuth(Homepage)
Or if possible withAuthHook?
I currently have done the following:
import React from 'react'
import { useSession } from 'next-auth/react'
import { LoginPage } from '#/client/components/index'
export const withAuth = (Component: React.Component) => (props) => {
const AuthenticatedComponent = () => {
const session = useSession()
if (session && session.data) {
return <Component {...props} />
}
return <LoginPage />
}
return AuthenticatedComponent
}
But I get an error:
JSX element type 'Component' does not have any construct or call signatures.ts(2604)
If I use React.ComponentType as mentioned in the answer below, I get an error saying:
TypeError: (0 , client_components_index__WEBPACK_IMPORTED_MODULE_0_.withAuth) is not a function
Have you tried:
export const withAuth = (Component: React.ComponentType) => (props) => {
...
https://flow.org/en/docs/react/types/#toc-react-componenttype
Edit:
Try like this:
export const withAuth = (Component: React.ComponentType) => (props) => {
const session = useSession()
if (session && session.data) {
return <Component {...props} />
}
return <LoginPage />
}
return AuthenticatedComponent
}
The answer was hidden in the docs. I had to specify the following Auth function in _app.tsx:
import { useEffect } from 'react'
import { AppProps } from 'next/app'
import { SessionProvider, signIn, useSession } from 'next-auth/react'
import { Provider } from 'urql'
import { client } from '#/client/graphql/client'
import '#/client/styles/index.css'
function Auth({ children }: { children: any }) {
const { data: session, status } = useSession()
const isUser = !!session?.user
useEffect(() => {
if (status === 'loading') return
if (!isUser) signIn()
}, [isUser, status])
if (isUser) {
return children
}
return <div>Loading...</div>
}
interface AppPropsWithAuth extends AppProps {
Component: AppProps['Component'] & { auth: boolean }
}
const CustomApp = ({ Component, pageProps: { session, ...pageProps } }: AppPropsWithAuth) => {
return (
<SessionProvider session={session}>
<Provider value={client}>
{Component.auth ? (
<Auth>
<Component {...pageProps} />
</Auth>
) : (
<Component {...pageProps} />
)}
</Provider>
</SessionProvider>
)
}
export default CustomApp
And on my actual page, I had to specify Component.auth as true:
const Homepage = () => {
return (
<>
<div>Homepage</div>
</>
)
}
Homepage.auth = true
export default Homepage
A nice summary of what it does can be found on https://simplernerd.com/next-auth-global-session

Mocking React Context for testing

I have a React app which utilizes the context hook. The app functions properly but I am having difficulty writing passing tests.
My context looks like
import React, { createContext } from 'react';
const DataContext = createContext();
export const DataProvider = (props) => {
const [personSuccessAlert, setPersonSuccessAlert] = React.useState(false);
return (
<DataContext.Provider
value={{
personSuccessAlert,
setPersonSuccessAlert,
}}>
{props.children}
</DataContext.Provider>
);
};
export const withContext = (Component) => (props) => (
<DataContext.Consumer>
{(globalState) => <Component {...globalState} {...props} />}
</DataContext.Consumer>
);
The app uses this context in a useEffect hook
import React, { useEffect } from 'react';
import { Alert } from '../../../components/alert';
const PersonRecord = ({
match: {
params: { id },
},
setPersonSuccessAlert,
personSuccessAlert,
}) => {
const closeAlert = () => {
setTimeout(() => {
setPersonSuccessAlert(false);
}, 3000);
};
useEffect(() => {
closeAlert();
}, [location]);
return (
<>
<Alert
open={personSuccessAlert}
/>
</>
);
};
export default withContext(PersonRecord);
This all works as expected. When I run my tests I know I need to import the DataProvider and wrap the component but I keep getting an error.
test('useeffect', async () => {
const history = createMemoryHistory();
history.push('/people');
const setPersonSuccessAlert = jest.fn();
const { getByTestId, getByText } = render(
<DataProvider value={{ setPersonSuccessAlert }}>
<MockedProvider mocks={mocksPerson} addTypename={false}>
<Router history={history}>
<PersonRecord match={{ params: { id: '123' } }} />
</Router>
</MockedProvider>
</DataProvider>,
);
const alert = getByTestId('styled-alert');
await act(async () => new Promise((resolve) => setTimeout(resolve, 4000)));
});
There are a few different errors I get depending on how I change things up but the most common is
[TypeError: setPersonSuccessAlert is not a function]
I think my context is setup slightly different than others which is why I am having trouble using other methods found on here.

Update React Context without re-rendering the component making the update

I have a Context and 2 Components: one is displaying what is in the context, the other updating it.
By having the following code in the updater Component, it will re-render upon changing the context.
const [, setArray] = React.useContext(context);
setArray(prevArray => { return [...prevArray, []] }
This means infitie re-renders. I need to avoid this. As the updater doesn't use the data in the context it should not update.
Complete example: I'm storing and displaying Profiler data about a Component.
https://codesandbox.io/s/update-react-context-without-re-rendering-the-component-making-the-update-k8ogr?file=/src/App.js
const context = React.createContext();
const Provider = props => {
const [array, setArray] = React.useState([]);
const value = React.useMemo(() => [array, setArray], [array]);
return <context.Provider value={value} {...props} />;
};
const Metrics = () => {
const [array] = React.useContext(context);
return <TextareaAutosize value={JSON.stringify(array, null, 2)} />;
};
const Component = () => {
const [, setArray] = React.useContext(context);
const onRenderCallback = (id, _phase, actualDuration) => {
setArray(prevArray => {
return [...prevArray, [id, actualDuration]];
});
};
return (
<React.Profiler id="1" onRender={onRenderCallback}>
<div />
</React.Profiler>
);
};
export default function App() {
return (
<div className="App">
<Provider>
<Metrics />
<Component />
</Provider>
</div>
);
}
This is what I came up with using the following article: https://kentcdodds.com/blog/how-to-optimize-your-context-value
Use 2 contexts, one for storing the state, one for updating it:
const stateContext = React.createContext();
const updaterContext = React.createContext();
const array = React.useContext(stateContext);
const setArray = React.useContext(updaterContext);
Complete example:
https://codesandbox.io/s/solution-update-react-context-without-re-rendering-the-component-making-the-update-yv0gf?file=/src/App.js
import React from "react";
import "./styles.css";
import TextareaAutosize from "react-textarea-autosize";
// https://kentcdodds.com/blog/how-to-optimize-your-context-value
const stateContext = React.createContext();
const updaterContext = React.createContext();
const Provider = props => {
const [array, setArray] = React.useState([]);
return (
<stateContext.Provider value={array}>
<updaterContext.Provider value={setArray}>
{props.children}
</updaterContext.Provider>
</stateContext.Provider>
);
};
const useUpdaterContext = () => {
return React.useContext(updaterContext);
};
const Metrics = () => {
const array = React.useContext(stateContext);
return <TextareaAutosize value={JSON.stringify(array, null, 2)} />;
};
const Component = () => {
const setArray = useUpdaterContext();
const onRenderCallback = (id, _phase, actualDuration) => {
setArray(prevArray => [...prevArray, [id, actualDuration]]);
};
return (
<React.Profiler id="1" onRender={onRenderCallback}>
<div />
</React.Profiler>
);
};
export default function App() {
return (
<div className="App">
<Provider>
<Metrics />
<Component />
</Provider>
</div>
);
}

Categories