Api Context props Boolean don't turn - reactjs

What I try to do
I try to use apiContext with ThemeProvider, when the user want switch a light mode to dark mode, my state doesn't want change.
My operative mode
I create an interface themeContext files for manage types and initialize context.
A themeContext file for call createContext and made a custom hook.
A ThemeProvider for pass my constant 'theme' is always a boolean and an a method for switch it.
In my template I have a part of logic for call the hook and mutate turn the theme.
My console return always false, what's going wrong. I deliberately omitted react's imports.
My code
IthemeContext.ts
export interface IThemeContext {
theme: boolean;
toggleTurn?: () => void;
}
export const defaultThemeState: IThemeContext = {
theme: false,
toggleTurn: () => !defaultThemeState.theme
}
themeContext.ts
import {defaultThemeState, IThemeContext} from "../interface/IThemeContext";
const ThemeContext = React?.createContext<Partial<IThemeContext>>(defaultThemeState);
export const useThemeContext = () => useContext(ThemeContext);
export default ThemeContext;
ThemeProvider
(…)
import ThemeContext, {useThemeContext} from "../context/themeContext";
export const ThemeProvider: FC<{children: ReactNode}> = ({ children}) => {
const {theme, toggleTurn} = useThemeContext();
return (
<ThemeContext.Provider value={
{
theme,
toggleTurn
}
}>
{ children }
</ThemeContext.Provider>
)
}
export default ThemeProvider;
App.tsx
(…)
import Template from "./component/template";
import {ThemeProvider} from "./component/provider/theme.provider";
function App() {
return (
<ThemeProvider>
<Template />
</ThemeProvider>
)
}
export default App
Template.tsx
(…)
const Template: FC = (): ReactElement<any, any> | null => {
const { theme, toggleTurn } = useThemeContext();
const toggleTheme = (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
if (toggleTurn) {
toggleTurn();
console.log(theme);
}
};
return (
(…)
<button type="button" onClick={
(e: React.MouseEvent<HMTLButtonElement) =>
toggleTheme(e)
}
(…)
)

Related

React Context Not Updating when clicking open modal button

I am trying to open a modal by updating the state of the component. The component is wrapped in a Context Provider.
Although the button seems to be clicking successfully, the Modal will not open
Here is the code with the container which contains the "Open Modal Button"
import { type FC, useRef } from 'react'
import infomation from '#/assets/icons/infomation.svg'
import { useModal } from '#/providers/ModalProvider'
import styles from './Instructions.module.scss'
const Instructions: FC = () => {
const card = useRef<HTMLDivElement>(null)
const { openDemoModal } = useModal()
const onOpenModalClick = () => {
openDemoModal()
console.log(openDemoModal)
}
return (
<section ref={card} className={`card ${styles.card}`}>
<div className={styles.background} />
<div>OPEN THE MODAL DOWN BELOW</div>
<button variant="outlined" fullWidth onClick={onOpenModalClick}>
Open Modal
</button>
</section>
)
}
export default Instructions
Here is the file which contains the Context for the Modal, I have tried setting up the context in INITIAL_STATE and tried updating it using the "onOpenModalClick" function - but it doesn't seem to be able to update the ShowModal.current value below.
import { type FC, type PropsWithChildren, createContext, useContext, useRef } from 'react'
import Modal from '#/components/modal/Modal'
type ContextValue = {
showModal: boolean
openDemoModal: () => void
}
const INITIAL_STATE: ContextValue = {
showModal: false,
openDemoModal: () => {},
}
export const ModalContext = createContext(INITIAL_STATE)
export const ModalProvider: FC<PropsWithChildren> = ({ children }) => {
const showModal = useRef(INITIAL_STATE.showModal)
const openDemoModal = () => {
showModal.current = true
}
console.log(showModal.current)
return (
<ModalContext.Provider value={{ showModal: showModal.current, openDemoModal }}>
{children}
<Modal show={showModal.current} setShow={(shouldShow: boolean) => (showModal.current = shouldShow)} />
</ModalContext.Provider>
)
}
export function useModal() {
const context = useContext(ModalContext)
if (!context) {
throw new Error('useModal must be used within a ModalProvider')
}
return context
}
Is there any way to update the onOpenModalClick button to make it change the value of showModal.current in the Provider file?
Sorry if the post is unclear, this is my first Stack Overflow post. Let me know if I need to post anymore of the components.
I tried to add a button to the component which updates the Context, however the state failed to update
The useRef does not trigger a re-render when the value changes. Instead you can use a useState hook. Which would look something like this.
type ContextValue = {
showModal: boolean;
openDemoModal: () => void;
};
const INITIAL_STATE: ContextValue = {
showModal: false,
openDemoModal: () => console.warn('No ModalProvider'),
};
export const ModalContext = createContext(INITIAL_STATE);
export const ModalProvider: FC<PropsWithChildren> = ({ children }) => {
const [showModal, setShowModal] = useState(false);
const openDemoModal = () => {
setShowModal(true)
};
console.log(showModal);
return (
<ModalContext.Provider
value={{ showModal, openDemoModal }}
>
{children}
<Modal
show={showModal}
setShow={setShowModal}
/>
</ModalContext.Provider>
);
};

TypeError: Cannot read properties of null (reading 'useContext')

I want add {searchWord} from useContext, to function with API call getServerSideProps(), but I got different errors.
The last one: Cannot read properties of null (reading 'useContext').
Maybe something wrong with my provider???
File with API call function /search-results:
import Link from 'next/link';
import { Text, Stack, Box, SimpleGrid, IconButton, Button, useQuery } from '#chakra-ui/react'
import ListOfCards from '../components/ListOfCards';
import { SearchContext, SearchContextProps } from './_app';
import { useContext, useEffect, useState } from 'react';
import { ChevronLeftIcon } from '#chakra-ui/icons';
function SearchResult({ searchResultForMapping }) {
console.log(searchResultForMapping)
const [queryKey, setQueryKey] = useState('lebowski');
const { searchWord } = useContext<SearchContextProps>(SearchContext);
useEffect(() => {
setQueryKey(searchWord);
}, [searchWord]);
const mockup = {"status":"OK","copyright":"Copyright (c) 2022 The New York Times Company. All Rights Reserved.","has_more":false,"num_results":1,"results":[{"display_title":"The Big Lebowski","mpaa_rating":"R","critics_pick":1,"byline":"Janet Maslin","headline":"Big Lebowski, the (Movie)","summary_short":"In a film both sweeter and loopier than \u0026quot;Fargo,\u0026quot; Joel and Ethan Coen and their rogues' gallery of actors explore Southern California's post-hippie, surfer-dude bowling scene. The results are irresistible, at least for audiences on the film's laid-back wavelength and in tune with its deadpan humor. With shaggy, baggy looks that belie his performance's fine comic control, Jeff Bridges plays a guy who truly deserves to be called Dude, and is by turns oblivious and indignant once a ruined rug leads him into neo-noir escapades. In a film that certainly keeps its audience guessing what might happen next (Saddam Hussein renting bowling shoes?), John Turturro takes the cake as the most fiendish of bowlers. — Janet Maslin","publication_date":"1998-03-06","opening_date":"1998-03-06","date_updated":"2017-11-02 04:17:54","link":{"type":"article","url":"https://www.nytimes.com/1998/03/06/movies/film-review-a-bowling-ball-s-eye-view-of-reality.html","suggested_link_text":"Read the New York Times Review of The Big Lebowski"},"multimedia":null}]}
return (
<><Button leftIcon={<ChevronLeftIcon />} colorScheme='pink' variant='solid' position='absolute' right='20px;' top='25px'>
<Link href="/">Home</Link>
</Button>
<Box p={5}>
<SimpleGrid gap={12} p={12} columns={1}>
<Text fontSize='4xl'> Search Result for keyword "{queryKey} i {searchWord}"</Text>
<Stack spacing={3} rowGap={3}>
<ListOfCards movieDetails={mockup} />
</Stack>
<Text fontSize='xl'><Link href="/">Search last key word..</Link> </Text>
</SimpleGrid>
</Box></>
);
}
const { searchWord } = useContext<SearchContextProps>(SearchContext);
export async function getServerSideProps() {
// const queryWord = 'iron'
const queryWord = searchWord;
const apiKey = "nTCRt5WnuaiL5Q5VsPEgeGM8oZifd3Ma";
const endpoint = "https://api.nytimes.com/svc/movies/v2/reviews/search.json?";
const createUrl = `${endpoint}query=${queryWord}&api-key=${apiKey}`;
const res = fetch(createUrl);
const searchResultForMapping = (await res).json();
return { props: { searchResultForMapping } };}
export default SearchResult
Page where i setSearchWord SearchLine.tsx:
import { Button } from "#chakra-ui/button";
import { Input, InputGroup, InputRightElement } from "#chakra-ui/input";
import React, { useContext } from "react";
import { useRouter } from 'next/router'
import { SearchContext } from "../pages/_app";
const SearchLine = () : JSX.Element => {
const { searchWord, setSearchWord, searchHistory, setSearchHistory } = useContext(SearchContext);
const router = useRouter()
const handleChange = (keyword) => setSearchWord(keyword.target.value);
const handleOnclick = () => {
setSearchHistory([...searchHistory, searchWord]);
router.push('/search-result');
}
return (
<InputGroup size="md">
<Input
onChange={handleChange}
placeholder='Enter the name of the movie or keyword'
size='md'
/>
<InputRightElement width="4.5rem">
<Button onClick={handleOnclick}>
Enter
</Button>
</InputRightElement>
</InputGroup>
)
}
export default SearchLine;
my _app.tsx:
import { ChakraProvider } from '#chakra-ui/react'
import { QueryClientProvider } from '#tanstack/react-query';
import { AppProps } from 'next/app';
import { createContext, useContext, useMemo, useState } from 'react';
export interface SearchContextProps {
searchWord: string;
setSearchWord: (f: string) => void;
searchHistory: string[];
setSearchHistory: (f: string[]) => void;
}
export const SearchContext = createContext<SearchContextProps>({
searchWord: '',
setSearchWord: () => {
// do nothing
},
searchHistory: [],
setSearchHistory: () => {
// do nothing
},
});
const SearchProvider = (props: any) => {
const [searchWord, setSearchWord] = useState('');
const [searchHistory, setSearchHistory] = useState('');
const value = useMemo(() => ({ searchWord, setSearchWord, searchHistory, setSearchHistory }), [searchWord]);
return <SearchContext.Provider value={value} {...props} />;
};
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
const context = useContext(SearchContext);
return (
<ChakraProvider>
<SearchProvider>
<Component {...pageProps} />
</SearchProvider>
</ChakraProvider>
)
}
export default MyApp
It seems your SearchProvider is wrong.
const SearchProvider = (props: any) => {
const [searchWord, setSearchWord] = useState('');
const [searchHistory, setSearchHistory] = useState('');
const value = useMemo(() => ({ searchWord, setSearchWord, searchHistory, setSearchHistory }), [searchWord]);
return (
<SearchContext.Provider value={value}>
{props.children}
</SearchContext.Provider>
);
};

React, Typescript Error: How can I solve type error? (HTMLButtonElement problem)

Greeting!
I make TodoList Sample, Use by React, Redux, Typescript, SCSS.
I have one problem about Typescript error.
Here is error status.
Type '(event: { target: HTMLButtonElement; }) => void' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.
Types of parameters 'event' and 'event' are incompatible.
Type 'MouseEvent<HTMLButtonElement, MouseEvent>' is not assignable to type '{ target: HTMLButtonElement; }'.
Types of property 'target' are incompatible.
Type 'EventTarget' is missing the following properties from type 'HTMLButtonElement': disabled, form, formAction, formEnctype, and 248 more.
I made TodoItem remove function.
So This is my code.
Container (Add onClickRemoveTodo)
TodosContainer
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import TodoAdd from 'src/components/todo/TodoAdd';
import TodoList from 'src/components/todo/TodoList';
import { RootState } from 'src/redux/rootReducer';
import { changeTodo, clearTodo } from 'src/redux/todo/todo';
import { removeTodo, setAddNewTodo } from 'src/redux/todo/todos';
const TodosContainer = () => {
const dispatch = useDispatch();
const { todo, todos } = useSelector((state: RootState) => ({
todo: state.todo,
todos: state.todos,
}));
const onChangeTodoAdd = (event: { target: HTMLInputElement }) => {
const { name, value } = event.target;
dispatch(changeTodo({ name, value }));
};
const onClickTodoAdd = () => {
dispatch(setAddNewTodo({ todo }));
dispatch(clearTodo());
};
// I made onClickRemoveTodo
const onClickRemoveTodo = (event: { target: HTMLButtonElement }) => {
const { value } = event.target;
dispatch(removeTodo({ id: parseInt(value, 10) }));
};
return (
<>
<h3>TodosContainer</h3>
<TodoAdd
todo={todo}
onChange={onChangeTodoAdd}
onClick={onClickTodoAdd}
/>
{/* // Pass to TodoList (onClickRemoveTodo -> onRemove) */}
<TodoList
todos={todos}
onRemove={onClickRemoveTodo}
/>
</>
);
};
export default TodosContainer;
Components (Add onRemove)
TodoList
import React from 'react';
import { Todo } from 'src/types/todo';
import { isEmpty } from 'src/utils/tools';
import TodoItem from './TodoItem';
export type TodoListProps = {
todos: Todo[];
onRemove: (event: { target: HTMLButtonElement }) => void;
};
const TodoList = ({ todos, onRemove }: TodoListProps) => {
if (isEmpty(todos.length)) {
return (
<>
<p>Todo List Empty</p>
</>
);
}
return (
<>
<h4>TodoList</h4>
{
todos.map(({ id, content }) => (
// Pass to TodoItem (onRemove -> onRemove)
<TodoItem
key={id}
id={id}
content={content}
onRemove={onRemove}
/>
))
}
</>
);
};
export default TodoList;
TodoItem
import React from 'react';
import { isEmpty } from 'src/utils/tools';
export type TodoItemProps = {
id: number;
content: string;
// I think this is problem.
onRemove: (event: { target: HTMLButtonElement }) => void;
};
const TodoItem = ({ id, content, onRemove }: TodoItemProps) => {
if (isEmpty(id)) {
return (
<></>
);
}
return (
<>
<p>{content}</p>
<button
type="button"
// FIXME: need to solve Typesript error case, but logic work well.
onClick={onRemove}
value={id}
>
remove
</button>
</>
);
};
export default TodoItem;
So i made onClickRemoveTodo function from TodosContainer and it pass TodosContainer -> TodoList -> TodoItem.
Also Logic has no problem. It works(remove Todo) in webpage.
But suddenly TodoItem Component / onClick={onRemove} show type error.
I try to find solution, but i had no clue this.
Here is my repository and you can read detail code.
https://github.com/DavidYang2149/react_redux_typescript_scss_starter
Thank you for reading this. :)
===========================================================================
Solved
Container (Add onClickRemoveTodo)
TodosContainer
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import TodoAdd from 'src/components/todo/TodoAdd';
import TodoList from 'src/components/todo/TodoList';
import { RootState } from 'src/redux/rootReducer';
import { changeTodo, clearTodo } from 'src/redux/todo/todo';
import { removeTodo, setAddNewTodo } from 'src/redux/todo/todos';
const TodosContainer = () => {
const dispatch = useDispatch();
const { todo, todos } = useSelector((state: RootState) => ({
todo: state.todo,
todos: state.todos,
}));
const onChangeTodoAdd = (event: { target: HTMLInputElement }) => {
const { name, value } = event.target;
dispatch(changeTodo({ name, value }));
};
const onClickTodoAdd = () => {
dispatch(setAddNewTodo({ todo }));
dispatch(clearTodo());
};
// Fixed :)
const onClickRemoveTodo = ({ id }: { id: number }) => {
dispatch(removeTodo({ id }));
};
return (
<>
<h3>TodosContainer</h3>
<TodoAdd
todo={todo}
onChange={onChangeTodoAdd}
onClick={onClickTodoAdd}
/>
<TodoList
todos={todos}
onRemove={onClickRemoveTodo}
/>
</>
);
};
export default TodosContainer;
Components (Add onRemove)
TodoList
import React from 'react';
import { Todo } from 'src/types/todo';
import { isEmpty } from 'src/utils/tools';
import TodoItem from './TodoItem';
export type TodoListProps = {
todos: Todo[];
// Fixed :)
onRemove: ({ id }: { id: number }) => void;
};
const TodoList = ({ todos, onRemove }: TodoListProps) => {
if (isEmpty(todos.length)) {
return (
<>
<p>Todo List Empty</p>
</>
);
}
return (
<>
<h4>TodoList</h4>
{
todos.map(({ id, content }) => (
<TodoItem
key={id}
id={id}
content={content}
onRemove={onRemove}
/>
))
}
</>
);
};
export default TodoList;
TodoItem
import React from 'react';
import { isEmpty } from 'src/utils/tools';
export type TodoItemProps = {
id: number;
content: string;
// Fixed :)
onRemove: ({ id }: { id: number }) => void;
};
const TodoItem = ({ id, content, onRemove }: TodoItemProps) => {
if (isEmpty(id)) {
return (
<></>
);
}
return (
<>
<p>{content}</p>
<button
type="button"
// Fixed :)
onClick={() => onRemove({ id })}
>
remove
</button>
</>
);
};
export default TodoItem;
Thank you.
Try to change all your definition of onRemove to MouseEventHandler.
import { MouseEventHandler } from 'react';
onRemove: MouseEventHandler<HTMLButtonElement>;
For onclick function
import { MouseEvent } from 'react';
const onClickRemoveTodo = (event: MouseEvent) => {//you code...}

Error: Objects are not valid as a React child (found: object with keys {})

I'm a beginner learning ts for the first time. Thank you in advance for sharing your knowledge. I am making a to-do list. I used to react to complete it. But now I am using react and typescript together to complete the code.
I got an error. I don't know what the problem is. Help me, please.
Error: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead.
Click here to view the full code
What I think that the problem is this file.
// contet.tsx
import React, { createContext, useReducer, useContext, Dispatch } from 'react';
import reducer from "./reducer";
import { Action } from './actions'
export interface ITodo {
id: string;
text: string;
};
export interface State {
toDos: ITodo[];
completed: ITodo[];
}
interface ContextValue {
state: State;
dispatch: Dispatch<Action>;
}
export const initialState = {
toDos: [],
completed: [],
};
const ToDosContext = createContext<ContextValue>({
state: initialState,
dispatch: () => { console.error("called dispatch outside of a ToDosContext Provider") }
});
export const ToDosProvider = ({ children }: { children: React.ReactNode }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<ToDosContext.Provider value={{ state, dispatch }}>
{children}
</ToDosContext.Provider>
);
};
export const useTodosState = (): State => {
const { state } = useContext(ToDosContext);
return state;
};
export const useTodosDispatch = (): Dispatch<Action> => {
const { dispatch } = useContext(ToDosContext);
return dispatch;
};
This is App.tsx
import React from "react";
import Add from "./Add";
import Title from "./Title";
import Progress from "./Progress";
import List from "./List";
import ToDo from "./ToDo";
import styled from "styled-components";
import { useTodosState } from '../context';
const App = () => {
const { toDos, completed } = useTodosState();
console.log(toDos);
return (
<Title>
<Add />
<Progress />
<Lists>
<List title={toDos.length !== 0 ? "To Dos" : ""}>
{toDos.map((toDo) => (
<ToDo key={toDo.id} id={toDo.id} text={toDo.text} isCompleted={false} />
))}
</List>
<List title={completed.length !== 0 ? "Completed" : ""}>
{completed.map((toDo) => (
<ToDo key={toDo.id} id={toDo.id} text=
{toDo.text} isCompleted />
))}
</List>
</Lists>
</Title >
)
}
export default App;
I had a look at the repo you shared the problem is at List.tsx component and the way you are trying to access your props from your components. It should be
const List = ({ title, children }: any) => (
instead of
const List = (title: any, children: any) => (
as in react functional components take only one parameter the props object.
Also if you want to add types there you could do {title:string; children: ReactElement| ReactElement[]}
I think it has being a better way for this situation. You can use PropsWithChildren you can check it out for details.
for little example
export interface SearchProps {
height: number
}
function Search({ children }: PropsWithChildren<SearchProps>) {
..
..
return()
}

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?

Resources