Problem using React createContext and typescript - reactjs

I'm working on a simple calculator made on react + typescript using vite. It's my first time using react + typescript so I don't know the right way to use the ContextAPI. I got this error trying to build the aplication with yarn build.
src/components/Screen.tsx:5:11 - error TS2339: Property 'showOnScreen' does not exist on type '{}'.
const { showOnScreen, onDeleteButton } = useContext(AppContext);
AppContext.tsx
import { createContext } from 'react';
export const AppContext = createContext({});
ContextProvider.tsx
import { useState } from 'react';
import { AppContext } from './';
type Props = {
children: React.ReactNode;
};
export const ContextProvider: React.FC<Props> = ({ children }) => {
const [showOnScreen, setShowOnScreen] = useState('0');
const [isLastAResult, setIsLastAResult] = useState(false);
const onDeleteButton = () => {
if (showOnScreen.length === 1 || isLastAResult) {
setShowOnScreen('0');
setIsLastAResult(false);
return;
}
setShowOnScreen(showOnScreen.substring(0, showOnScreen.length - 1));
};
return (
<AppContext.Provider
value={{
onDeleteButton,
showOnScreen,
}}
>
{children}
</AppContext.Provider>
);
};
Screen.tsx
import { useContext } from 'react';
import { AppContext } from '../context';
export const Screen = () => {
const { showOnScreen, onDeleteButton } = useContext(AppContext);
return (
<div className='row bg-white rounded-4'>
<div className='col-12 pe-0 d-flex justify-content-between align-items-center'>
<span className='py-2 fs-4'>{showOnScreen}</span>
<button
className='text-danger rounded-start btn rounded-4 h-100 fs-5'
onClick={onDeleteButton}
>
DEL
</button>
</div>
</div>
);
};

In TypeScript, createContext uses generics ie it is a createContext<T> that automatically adapts to your type when you don't specify anything else. It propagates the type to your variable - AppContext which gets assigned {} as a type.
There are various ways to assign it a type, like:
export const AppContext = createContext({} as { showOnScreen?: bool; });
or
export const AppContext = createContext<{ showOnScreen?: bool; }>({});
or
interface IContext {
showscreen?: bool;
}
export const AppContext = createContext<IContext>({});

Related

Error when using react context - Cannot destructure property

i trying to using Context on my react app
but i got error:
Uncaught TypeError: Cannot destructure property 'selected' of 'Object(...)(...)' as it is undefined.
in my InputsList file
on this line:
const { selected, setSelected } = useContext(ParamsContext);
ParamsContext:
import { createContext } from 'react';
export const ParamsContext = createContext({
selected: [],
setSelected: () => {},
textName: null,
valueName: null
});
InputParams:
import React, { useState } from 'react';
import InputsList from './InputsList';
import { ParamsContext } from './services/ParamsContext';
function InputParams(props) {
const [selected, setSelected] = useState([]);
const textName = "test";
const valueName = "test2";
return (
<div>
<ParamsContext.Provider
selected={selected}
setSelected={setSelected}
textName={textName}
valueName={valueName}>
<InputsList />
</ParamsContext.Provider>
</div>
);
}
export default InputParams;
InputsList:
import React, { useContext } from 'react';
import { ParamsContext } from './services/ParamsContext';
function InputsList(props) {
const { selected, setSelected } = useContext(ParamsContext);
return (
<div>
{selected.length}
</div>
);
}
export default InputsList;
what can i do?
Contexte.Provider accept a value props,
And should be used like:
<ParamsContext.Provider value={{selected, setSelected, textName, valueName}}>
<\ParamsContext.Provider>
Provider accept a value prop, in your case, an object. So it should be:
<ParamsContext.Provider
value={{
selected,
setSelected,
textName,
valueName
}}
>
<InputsList />
</ParamsContext.Provider>
See docs

Create wrapper for translation with MUI - WEBPACK_IMPORTED_MODULE_16__ is undefined

I use react-i18next in my project to provide proper translations, in most components I import
const { t } = useTranslation();
to provide proper content however I wanted to write custom translationWrapper where I would use useTranslation hook and just reuse that component where I need. However it keeps throwing error:
Uncaught TypeError: TranslationWrapper__WEBPACK_IMPORTED_MODULE_16_
is undefined
Why does it happen? Interesting is that if project is running and I add it then it works correctly but when I refresh browser then get above error
My Custom wrapper:
import React, { FC } from 'react';
import { Typography, TypographyProps } from '#mui/material';
import { TFunction } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { styled } from '#mui/styles';
import { TOptions } from 'i18next';
export type TextContentPath = Parameters<TFunction<'translation'>>[0];
export interface TranslationWrapperProps extends TypographyProps {
content?: TextContentPath;
tParams?: TOptions<{}>;
onClick?: () => void;
}
const TranslationWrapperComponent: FC<TranslationWrapperProps> = ({ content, tParams, onClick, ...materialProps }) => {
const { t } = useTranslation();
return (
<Typography {...materialProps} {...onClick}>
{t(content as TemplateStringsArray, tParams)}
</Typography>
);
};
export const TranslationWrapper = React.memo(TranslationWrapperComponent) as typeof TranslationWrapperComponent;
and try to reuse it here:
export const MyComponent: React.FC = () => {
return (
<StyledSwitchBox>
<StyledTester
variant="p"
sx={{ cursor: 'pointer' }}
onClick={() => console.log('test')}
content={'myComponent.switchContent'}
/>
</StyledSwitchBox>
);
};
const StyledTester = styled(TranslationWrapper)({});

React Konva share ref between sibling components throws error

I have App.tsx which contains 2 sibling components:
Konva.tsx: It has the Canvas
Options.tsx: It has a Download Canvas button
So I created a ref named stageRef in App.tsx to pass it to Konva.tsx & Options.tsx. I use React.forwardRef to forward refs to child components.
App.tsx
import * as React from 'react'
import type { Stage as StageType } from 'konva/types/Stage'
import { Konva, Options } from '#/components/index'
import { FrameItProvider } from '#/store/index'
const App = () => {
const stageRef = React.createRef<StageType>()
return (
<>
<Konva ref={stageRef} />
<Options ref={stageRef} />
</>
)
}
export default App
In Konva.tsx, the ref points to the Canvas so it can access the element in the DOM.
Konva.tsx
import * as React from 'react'
import { observer } from 'mobx-react'
import { useFrameItStore } from '#/store/index'
import { BrowserWindow } from '#/components/index'
import type { Window } from '#/types/index'
import type { Stage as StageType } from 'konva/types/Stage'
interface IProps {
className?: string
}
export const Konva = observer(
React.forwardRef<StageType, IProps>(({ className }: IProps, forwardedRef) => {
const frameItStore = useFrameItStore()
const browser: Window = frameItStore.browser
return (
<>
<Stage
width={browser.width}
height={browser.height}
ref={forwardedRef}
className={className}
>
<Layer>
<BrowserWindow />
</Layer>
</Stage>
</>
)
})
)
In Options.tsx, I trigger the download call using downloadImage with the forwardedRef.
Options.tsx
import * as React from 'react'
import { observer } from 'mobx-react'
import type { Stage as StageType } from 'konva/types/Stage'
import { useFrameItStore } from '#/store/index'
import type { TrafficSignalStyle } from '#/types/index'
interface IProps {
className?: string
}
export const Options = observer(
React.forwardRef<StageType, IProps>((props: IProps, forwardedRef) => {
const frameItStore = useFrameItStore()
const downloadImage: (stageRef: React.ForwardedRef<StageType>) => void =
frameItStore.downloadImage
return (
<div>
<button onClick={() => downloadImage(forwardedRef)}>
Download Canvas
</button>
</div>
)
})
)
I'm using MobX to manage my store. However, the forwardRef causes problem.
store/index.ts
import type { Stage as StageType } from 'konva/types/Stage'
import type { IFrameItStore } from '#/types/index'
export class FrameItStore implements IFrameItStore {
downloadImage(stageRef: React.ForwardedRef<StageType>) {
console.log(stageRef)
stageRef
.current!.getStage()
.toDataURL({ mimeType: 'image/jpeg', quality: 1 })
}
}
types/index.ts
export interface IFrameItStore {
downloadImage(stageRef: React.ForwardedRef<StageType>): void
}
I get 2 TypeScript errors in store/index.ts:
TS2531: Object is possibly 'null'.
on stageRef when I try to access stageRef.current and
TS2339: Property 'current' does not exist on type '((instance: Stage | null) => void) | MutableRefObject<Stage | null>'.
Property 'current' does not exist on type '(instance: Stage | null) => void'.
on current
I tried not using ForwardedRef but it gave error that the types do not match so I have to use ForwardedRef but I'm not sure how to solve this?
I made a ref in App.tsx & then passed it to Konva.tsx. I catched the ref using React.forwardRef function.
Then I used the lesser known useImperativeHandle hook in Konva.tsx to access the function downloadImage in App.tsx & then pass the function downloadImage directly to Options.tsx.
I didn't keep anything in MobX store. Just kept it locally in Konva component where its easily accessible. The old rule is that if it isn't going to be used in any other component, then it should be kept as close to the component as possible.
App.tsx
import * as React from 'react'
import { Konva, Options } from '#/components/index'
const App = () => {
const stageRef = React.useRef<{ downloadImage: Function }>(null)
return (
<>
<Konva ref={stageRef} />
<Options downloadImage={() => stageRef?.current?.downloadImage()} />
</>
)
}
export default App
Konva.tsx
import * as React from 'react'
import { observer } from 'mobx-react'
import { useFrameItStore } from '#/store/index'
import { BrowserWindow } from '#/components/index'
import type { Window } from '#/types/index'
import type { Stage as StageType } from 'konva/types/Stage'
interface IProps {
className?: string
}
interface ForwardedRef {
downloadImage: Function
}
export const Konva = observer(
React.forwardRef<ForwardedRef, IProps>(
({ className }: IProps, forwardedRef) => {
const frameItStore = useFrameItStore()
const browser: Window = frameItStore.browser
const stageRef = React.useRef<StageType>(null)
React.useImperativeHandle(
forwardedRef,
() => ({
downloadImage: () =>
stageRef.current
?.getStage()
.toDataURL({ mimeType: 'image/jpeg', quality: 1 }),
}),
[]
)
return (
<Stage
width={browser.width}
height={browser.height}
ref={stageRef}
className={className}
>
<Layer>
<BrowserWindow />
</Layer>
</Stage>
)
}
)
)
Options.tsx
import * as React from 'react'
import { observer } from 'mobx-react'
interface IProps {
className?: string
downloadImage: Function
}
export const Options = observer((props: IProps) => {
const { downloadImage } = props
return (
<div>
<button onClick={() => console.log(downloadImage())}>
Download Image
</button>
</div>
)
})
Here's a complete working demo on CodeSandBox → https://codesandbox.io/s/react-konva-share-refs-between-siblings-dsd97?file=/src/App.tsx

How to update custom hook value

I've started learning typescript and react hooks on my new web project. I have some problem with managing initial value of my custom hook i've made. Cant step over this functionality. How should I update nameInput hook, after data is fetched from my API. For example if I pass custom string as initial value in useTextInput everything is fine, but problem is when im passing undefined first there(when data is fetched from API) and value after is downloaded, and seems like it is not updating.
My question is, how to update in AccountInput inputValue value properly by value coming from API as userAccount.firstName from getUser() method, witch depends to nameInput, witch is not updated after initial value in useTextInput.
Account.tsx
import React, { useEffect, useState} from 'react';
import { connect } from 'react-redux';
import { IApplicationState } from '../../store';
import { actionCreators, reducer, AccountStatusEnum } from '../../store/Account';
import { MDBBtn, MDBInput } from 'mdbreact';
import { AccountInput } from './child-components';
import { useTextInput } from '../../hooks';
type AccountProps = ReturnType<typeof reducer> & typeof actionCreators;
const Account: React.FC<AccountProps> = ({ getUser, resetState, userAccount, status }) => {
const nameInput = useTextInput(userAccount.firstName);
const [isInputInvalid, setIsInputInvalid] = useState<boolean>(false);
useEffect(() => {
getUser();
}, []);
return (
<React.Fragment>
<div className="mt-3 container border border-light">
<h5 className="secondary-heading font-weight-bold pt-3 pl-5">User info</h5>
<div className="row">
<AccountInput
textInput={nameInput}
typeInput="text"
labelInput="Your name & surename"
isInputInvalid={isInputInvalid}
/>
</div>
</div>
</React.Fragment>
);
};
const mapStateToProps = (state: IApplicationState) => state.acc;
export default connect(mapStateToProps, actionCreators)(Account as any);
AccointInput.tsx
import React, { Fragment, useEffect, useState } from 'react';
import { TextInput } from '../../../hooks';
import { MDBInput } from 'mdbreact';
type AccountInputProps = {
readonly textInput: TextInput;
readonly isInputInvalid: boolean;
readonly typeInput: string;
readonly labelInput: string;
};
const AccountInput = React.memo<AccountInputProps>(({ textInput, isInputInvalid, typeInput, labelInput }) => {
const { hasValue, bindToInput } = textInput;
return (
<Fragment>
<div className="col-sm w-100 px-5">
<MDBInput hint={textInput.value} {...bindToInput} type={typeInput} label={labelInput} />
</div>
</Fragment>
);
});
AccountInput.displayName = 'AccountInput';
export default AccountInput;
useTextInput.ts
import { useState, useCallback, useMemo, FormEvent } from 'react';
export type TextInputType = 'text' | 'password';
export type TextInput = {
value: string;
hasValue: boolean;
clear: () => void;
bindToInput: {
value: string;
type: TextInputType;
onChange: (e: FormEvent<HTMLInputElement>) => void;
};
};
export const useTextInput = (initial: string = '', type: TextInputType = 'text'): TextInput => {
const [value, setValue] = useState<string>(initial);
const clear = useCallback((): void => setValue(''), []);
const onChange = useCallback((e: FormEvent<HTMLInputElement>): void => setValue(e.currentTarget.value), []);
return useMemo<TextInput>(
() => ({
value,
clear,
hasValue: !!(value && value.trim()),
bindToInput: {
type,
value,
onChange,
},
}),
[value, type, onChange, clear],
);
};
I don't know for sure how custom hooks handle promises, but normally when you want to pass an async value to useState you do something like this:
const useTextInput = (initial) => {
const [value, setValue] = useState(initial);
useEffect(() => {
setValue(initial);
}, [initial]);
};
You can use useEffect to update your stateful value when initial changes. Would this work in your case?

TypeError: Invalid attempt to destructure non-iterable instance with CONTEXT API value state on reactjs

Hi i try to make a simply state managment with context api and hooks on react js.
I have a context named UIcontext and one component container called home.
when i do the new instance of useUIcontextStateValue in home, the app crash and throw this error
TypeError: Invalid attempt to destructure non-iterable instance
I have no idea this is happen, in other app i have the same code and work, please help friend.
this is the context
import React , {createContext, useContext, useReducer} from 'react';
export const UIcontext = createContext();
export const UIcontextProvider = ({reducer, initialState, children}) => {
return (
<UIcontext.Provider>
{children}
</UIcontext.Provider>
);
};
export const useUIcontextStateValue = () => useContext(UIcontext);
and this is the component where i use it
import React, {useEffect, useState} from 'react';
import { UIcontextProvider, useUIcontextStateValue } from '../../Store/UiContext';
import { Header, Logo, LandingModule, Menu } from '../../Components';
const Home = props => {
const [showHeader, setShowHeader] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const [{ submenu }, dispatch] = useUIcontextStateValue();
const initialState = {
submenu: false
};
const reducer = (state, action) => {
switch (action.type) {
case 'toogleSubmenu':
return {
...state,
submenu: action.submenuState
};
default:
return state;
}
};
useEffect(()=>{
window.onscroll = function() {
if(window.pageYOffset === 0) {
setShowHeader(false);
} else {
setShowHeader(true);
}
}
});
const toogleMenu = () => {
console.log("ANTES", menuOpen);
setMenuOpen(!menuOpen);
console.log("DESPUES", menuOpen);
};
return (
<UIcontextProvider>
<section className="home" >
<Header show={showHeader}/>
<Menu open={menuOpen}/>
<section name="landing" className="home__landing" >
<Logo/>
<div className="home__landing__container-text">
<h1>Welcome</h1>
<div>
<h5><span>TO ADAM'S GRAY</span><br></br> WEBSITE</h5>
<div></div>
</div>
</div>
</section>
<LandingModule
title="HELLA SLINGSHOTS"
subtitle="In this project I make over 35,000 slingshot each year in a variety of colors and designs."
titleBg={hellaBG}
productBg={hellaProductBG}
color='#00c691'
/>
<LandingModule
title="BICYCLE BOLTS"
subtitle="Here i make and sell metric security bolts to help
keep components on your bicycle safe from
opportunistic thievery."
titleBg={bicycleBG}
productBg={bicycleProductBG}
color='#6140FF'
/>
<LandingModule
title="SURF BRAIN"
subtitle="In this project I make over 35,000 slingshot each year in a variety of colors and designs."
titleBg={hellaBG}
productBg={hellaProductBG}
color='#2AD9B1'
/>
</section>
</UIcontextProvider>
);
};
export default Home;

Resources