I have just started learning TypeScript, and have been working on a small web app.
Due to state management, I have created a file called CountdownContext.tsx
import { useState, createContext, useContext, SetStateAction } from "react";
import { CountdownContextType } from "./types";
const CountdownContext = createContext({});
export const useCountdownContext = () => {
return useContext(CountdownContext) as CountdownContextType;
};
// React.useContext(TodoContext) as TodoContextType;
interface Props {
children: React.ReactNode;
}
export const CountdownContextProvider: React.FC<Props> = ({ children }) => {
const [eventDate, setEventDate] = useState<Date>(new Date());
function handleEventDate(e: React.ChangeEvent<HTMLInputElement>) {
setEventDate(new Date(e.target.value));
}
return (
<CountdownContext.Provider value={{ eventDate, handleEventDate }}>
{children}
</CountdownContext.Provider>
);
};
And I imported eventDate and handleEventDate in the file below.
import React, { useState } from "react";
import { useCountdownContext } from "../../contexts/CountdownContext";
type Props = {
activeEdit: boolean;
};
const EventDate: React.FC<Props> = ({ activeEdit }) => {
const { eventDate, handleEventDate } = useCountdownContext();
return (
<div>
{activeEdit ? (
<input type="date" onChange={(e) => handleEventDate(e.target.value, typeof new Date(e.target.value))} />
) : (
<div>{eventDate}</div>
)}
</div>
);
};
export default EventDate;
However, I got an error when importing. After searching about the problem happened,
I figured out that I needed to declare types.
Then I created a file below
export type CountdownContextType = {
eventDate: Date;
handleEventDate: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
However, one concern is that I thought I already provided and declared types for eventDate and handleEventDate in the context file.
Thus, I thought to question why I gotta do this twice??
Please give me a clarification about this problem. Thanks so much for your kindness and help.
One way to not using types is to create initial value for your context like this:
import * as React from "react";
import ReactDOM from "react-dom";
import { useState, createContext, useContext } from "react";
const initialValue = {
eventDate: new Date(),
handleEventDate: (e: React.ChangeEvent<HTMLInputElement>) => {},
};
const CountdownContext = createContext(initialValue);
const useCountDownState = () => {
const [eventDate, setEventDate] = useState<Date>(new Date());
function handleEventDate(e: React.ChangeEvent<HTMLInputElement>) {
console.log("setting date");
setEventDate(new Date(e.target.value));
}
return { eventDate, handleEventDate };
};
const CountdownContextProvider = ({ children }: { children: JSX.Element }) => {
return (
<CountdownContext.Provider value={useCountDownState()}>
{children}
</CountdownContext.Provider>
);
};
const useCountdownContext = () => useContext(CountdownContext);
const EventDate = ({ activeEdit }: { activeEdit: boolean }) => {
const { eventDate, handleEventDate } = useCountdownContext();
return (
<div>
{activeEdit ? (
<input type="date" onChange={handleEventDate} />
) : (
<div>{eventDate.toDateString()}</div>
)}
</div>
);
};
function App() {
return (
<CountdownContextProvider>
<EventDate activeEdit />
</CountdownContextProvider>
);
}
ReactDOM.render(<App />, document.querySelector("#root"));
The code is heavily inspired by:
https://youtu.be/P95DuIBwnqw?t=822
And here is the source code from the link:
https://github.com/jherr/which-react-state-manager/blob/main/examples/hooks-context/src/store.tsx
Related
I Can't render my events. Its showing this error -
"Cannot update a component (App) while rendering a different component (EventList). To locate the bad setState() call inside EventList, follow the stack trace as described in https://reactjs.org/link/setstate-in-render"
Here is EventList Component code -
import { useEffect, useState } from "react";
import EventList from "../../event-list";
import EventForm from "../event-form";
const EventAction = ({
getEventsByClockID,
addEvent,
updateEvent,
clockID,
deleteEvent,
deleteEventsByClockID,
}) => {
const [isCreate, setIsCreate] = useState(false);
const [isToggle, setIsToggle] = useState(false);
const [eventState, setEventState] = useState(null)
const handleCreate = () => {
setIsCreate(!isCreate);
}
useEffect(() => {
setEventState(getEventsByClockID(clockID, true));
}, [isToggle])
const handleToggle = () => {
setIsToggle(!isToggle);
}
return (
<div>
<div>
<button onClick={handleCreate}>Create Event</button>
<button onClick={handleToggle}>Toggle Events</button>
</div>
{isCreate && (
<>
<h3>Create Event</h3>
<EventForm
clockID={clockID}
handleEvent={addEvent}
/>
</>
)}
{isToggle && (
<>
<h3>Events of this clock</h3>
<EventList
clockID={clockID}
eventState={eventState}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</>
)}
</div>
)
}
export default EventAction;
Here is my App Component Code -
import ClockList from "./components/clock-list";
import LocalClock from "./components/local-clock";
import useApp from "./hooks/useApp";
import { localClockInitState } from "./initialStates/clockInitState";
const App = () => {
const {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
} = useApp(localClockInitState);
return (
<div>
<LocalClock
clock={localClock}
updateClock={updateLocalClock}
createClock={createClock}
/>
<ClockList
clocks={clocks}
localClock={localClock.date}
updateClock={updateClock}
deleteClock={deleteClock}
getEventsByClockID={getEventsByClockID}
addEvent={addEvent}
deleteEvent={deleteEvent}
updateEvent={updateEvent}
deleteEventsByClockID={deleteEventsByClockID}
/>
</div>
)
}
export default App;
and Here is my useApp hook -
import { useState } from "react";
import deepClone from "../utils/deepClone";
import generateID from "../utils/generateId";
import useEvents from "./useEvents";
const getID = generateID('clock');
const useApp = (initValue) => {
const [localClock, setLocalClock] = useState(deepClone(initValue));
const [clocks, setClocks] = useState([]);
const {
// events,
// getEvents,
getEventsByClockID,
addEvent,
deleteEvent,
deleteEventsByClockID,
updateEvent,
} = useEvents();
const updateLocalClock = (data) => {
setLocalClock({
...localClock,
...data,
})
}
const createClock = (clock) => {
clock.id = getID.next().value;
setClocks((prev) => ([
...prev, clock
]))
}
const updateClock = (updatedClock) => {
setClocks(clocks.map(clock => {
if(clock.id === updatedClock.id) return updatedClock;
return clock;
}));
}
const deleteClock = (id) => {
setClocks(clocks.filter(clock => clock.id !== id));
}
return {
localClock,
clocks,
updateLocalClock,
createClock,
updateClock,
deleteClock,
getEventsByClockID,
addEvent,
deleteEvent,
updateEvent,
deleteEventsByClockID,
}
}
export default useApp;
I want to show all events incorporated with each individual clock.
Having a monaco-editor inside a React component:
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
The defaultValue, the default code inside of the editor, is sent via props to the component:
const MyComponent = ({
originalCode
}: MyComponentProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
When the user edits the code, onChange={onChangeCode} is called:
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
My question is, how to reset the code to the original one when the user clicks on Cancel?
Initially it was like:
const handleCancel = () => {
onChangeCode(defaultValue);
};
but it didn't work, probably because useState is asynchronous, any ideas how to fix this?
Here is the whole component for more context:
import Editor from '#monaco-editor/react';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button, HeaderWithButtons } from '../shared/ui-components';
import { ICalculationEngine } from '../../../lib/constants/types';
import { usePostScript } from '../../../lib/hooks/use-post-script';
import { scriptPayload } from '../../../mocks/scriptPayload';
import { editorDefaultValue } from '../../../utils/utils';
export interface ScriptDefinitionProps {
realInputDetails: Array<ICalculationEngine['RealInputDetails']>;
realOutputDetails: ICalculationEngine['RealInputDetails'];
originalCode: string;
scriptLibId: string;
data: ICalculationEngine['ScriptPayload'];
}
const ScriptDefinition = ({
realInputDetails,
realOutputDetails,
originalCode
}: ScriptDefinitionProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
const [code, setCode] = useState(defaultValue);
const { handleSubmit } = useForm({});
const { mutate: postScript } = usePostScript();
const handleSubmitClick = handleSubmit(() => {
postScript(scriptPayload);
});
const handleCancel = () => {
onChangeCode(defaultValue);
};
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
useEffect(() => {
setDefaultValue(editorDefaultValue(realInputDetails, realOutputDetails));
}, [realInputDetails, realOutputDetails, originalCode]);
return (
<div>
<HeaderWithButtons>
<div>
<Button title='cancel' onClick={handleCancel} />
<Button title='save' onClick={handleSubmitClick} />
</div>
</HeaderWithButtons>
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
</div>
);
};
export default ScriptDefinition;
If you need the ability to change the value externally, you'll need to use the Editor as a controlled component by passing the value prop (sandbox):
For example:
const defaultValue = "// let's write some broken code 😈";
function App() {
const [value, setValue] = useState(defaultValue);
const handleCancel = () => {
setValue(defaultValue);
};
return (
<>
<button title="cancel" onClick={handleCancel}>
Cancel
</button>
<Editor
value={value}
onChange={setValue}
height="90vh"
defaultLanguage="javascript"
/>
</>
);
}
Here is my parent component (Grid), (here I pass more props used by hoc but I ommited them here):
<QuickBooksTable itemList={quickBooksList} />
Here is the table component:
export const QuickBooksTable = withIntegrationTable(props: : WithIntegrationTableChildProps & WithIntegrationTableProps) => ...
Here is the hoc:
export function withIntegrationTable<T extends WithIntegrationTableProps>(Component: React.ComponentType<T>) {
return (
{
itemList,
...props
}: WithIntegrationTableProps & T
) => {
const [state, setState] = useState<WithIntegrationTableState>({
tableItems: new Array<any>(),
selectedItems: new Set<string>(),
isAllItemsSelected: false
});
useEffect(() => {
const tableItems = mapItemList(itemList, currentUser);
setState({
...state,
tableItems
});
}, [itemList]);
<Component {...props as T}
tableState={state}
/>
}
}
But when it compiles it says:Element QuickBooksTable doesn't have required attribute (here is another props name).
How should I use the types and generics to remove this error? I've tried to read the docs but I can't understand what I am doing wrong.
I think this is what you try to achieve.
import React from 'react';
import { useEffect, useState } from 'react';
interface WithIntegrationTableProps {
itemList: string[]
}
interface WithIntegrationTableState { }
export const withIntegrationTable = <P extends WithIntegrationTableState>(
Component: React.ComponentType<P & {
tableState: WithIntegrationTableState
}>
): React.FC<P & WithIntegrationTableProps> => ({
itemList,
...props
}: WithIntegrationTableProps) => {
const mapItemList = (itemList: any, currentUser: any) => {
}
const [state, setState] = useState<WithIntegrationTableState>({
tableItems: new Array<any>(),
selectedItems: new Set<string>(),
isAllItemsSelected: false
});
useEffect(() => {
const tableItems = mapItemList(itemList, null);
setState({
...state,
tableItems
});
}, [itemList]);
return <Component {...props as P} tableState={state} />
}
export const QuickBooksTable = withIntegrationTable(({ ...props }) => {
console.log(props.tableState)
return <div>
test
</div>
})
const App = () => {
return <QuickBooksTable itemList={[]} />
}
export default App
I am currently following a tutorial on youtube building a whatsapp clone with react and socket.io from webDevSimplified: https://www.youtube.com/watch?v=tBr-PybP_9c
and here the main repo : 'https://github.com/WebDevSimplified/Whatsapp-Clone/tree/master/client
I got stuck halfway through as for some reason my custom useConversation hook returns undefined while my useContacts hook works without any problems.
Here the setup:
App.js :
import Dashboard from "./Dashboard";
import { ContactsProvider } from "../contexts/ContactsProvider";
import { ConversationsProvider } from "../contexts/ConversationsProvider";
import Test from "../components/test";///test
console.log(Test)//test purpose
function App() {
const [id, setId] = useLocalStorage("id");
const dashboard = (
<ContactsProvider>
<ConversationsProvider id={id}>
<Dashboard id={id} />
</ConversationsProvider>
</ContactsProvider>
);
return id ? dashboard : <Login onIdSubmit={setId} />;
}
export default App;
ContactsProvider.js
import React, { useContext } from "react";
import useLocalStorage from "../hooks/useLocalStorage";
const ContactsContext = React.createContext();
export function useContacts() {
return useContext(ContactsContext);
}
export function ContactsProvider({ children }) {
const [contacts, setContacts] = useLocalStorage("contacts", []);//initalValue an empty array
function createContact(id, name) {
setContacts((prevContacts) => {
return [...prevContacts, { id, name }];
});
}
return (
<ContactsContext.Provider value={{ contacts, createContact }}>
{children}
</ContactsContext.Provider>
);
}
Contacts.js - here my useContacts works
import React from "react";
import { ListGroup } from "react-bootstrap";
import { useContacts } from "../contexts/ContactsProvider";
export default function Contacts() {
const { contacts } = useContacts();
console.log(`contacts: ${contacts}`); //returns object, object as expected
return (
<ListGroup variant="flush">
{contacts.map((contact) => (//visibly working in UI when commenting out the ListGroup in conversations.js
<ListGroup.Item key={contact.id}>{contact.name}</ListGroup.Item>
))}
</ListGroup>
);
}
Here the problematic part:
ConversationsProvider.js
import React, { useContext } from "react";
import useLocalStorage from "../hooks/useLocalStorage";
import { useContacts } from "./ContactsProvider";
const ConversationsContext = React.createContext();
export function useConversations() {
return useContext(ConversationsContext);
}
export function ConversationsProvider({ children }) {
const [conversations, setConversations] = useLocalStorage(
"conversations", []);//as well empty array
const { contacts } = useContacts();
function createConversation(recipients) {
setConversations((prevConversations) => {
return [...prevConversations, { recipients, messages: [] }];
});
}
const formattedConversations = conversations.map((conversation) => {
const recipients = conversation.recipients.map((recipient) => {
const contact = contacts.find((contact) => {
return contact.id === recipient;
});
const name = (contact && contact.name) || recipient;
return { id: recipient, name };
});
return { ...conversation, recipients };
});
const value = {
conversations: formattedConversations,
createConversation,
};
return (
<ConversationsContext.Provider value={{ value }}>
{children}
</ConversationsContext.Provider>
);
}
and the component that causes the error:
Conversations.js:
import React from "react";
import { ListGroup } from "react-bootstrap";
import { useConversations } from "../contexts/ConversationsProvider";
export default function Conversations() {
const { conversations } = useConversations();
console.log( `conversations: ${conversations}`)//returns undefined
return (
<ListGroup variant="flush">
{conversations.map((conversation, index) => (//can't map because conversations is undefined
<ListGroup.Item key={index}>
{conversation.recipients
.map(r => r.name)
.join(", ")}
</ListGroup.Item>
))}
</ListGroup>
);
}
Here the localStorage setup for clarity:
import { useEffect, useState } from 'react'
const PREFIX = 'whatsapp-clone-'
export default function useLocalStorage(key, initialValue) {
const prefixedKey = PREFIX + key;
const [value, setValue] = useState(() => {
const jsonValue = localStorage.getItem(prefixedKey);
if (jsonValue != null) return JSON.parse(jsonValue);
if (typeof initialValue === "function") {
return initialValue();
} else {
return initialValue;
}
});
useEffect(() => {
localStorage.setItem(prefixedKey, JSON.stringify(value));
}, [prefixedKey, value]);
return [value, setValue];
}
I have been trying to solve this problem for hours and running out of ideas. I setup a test.js and imported the hooks in a Test.js function. Both return their objects respectively.
I have created the application using npx create-react-app and running it via yarn.
The problem lies within your ConversationsProvider.js:
const value = {
conversations: formattedConversations,
createConversation,
};
return (
<ConversationsContext.Provider value={{ value }}>
{children}
</ConversationsContext.Provider>
);
The following lines are all the same:
<ConversationsContext.Provider value={{ value }}>
<ConversationsContext.Provider value={{ value: value }}>
<ConversationsContext.Provider value={{ value: { conversations: formattedConversations, createConversation: createConversation } }}>
When you execute:
const { conversations } = useConversations();
conversations will be set to undefined because the object returned will only have the property value.
To fix the issue remove the nesting by changing the line:
<ConversationsContext.Provider value={{ value }}>
// to
<ConversationsContext.Provider value={value}>
PS. Here a tip to improve your debugging skills. You already did:
const { conversations } = useConversations();
console.log(conversations);
Which logged undefined. The next step would be to not destruct the object immediately and log the whole context value instead.
const contextValue = useConversations();
console.log(contextValue);
This would have shown that the object returned by useConversations is not what you expect it to be.
I'm trying to set up a HOC in React to able to apply text selection detection to any Input component. However I seem to be missing something when I was trying to put it together.
I was following this article here on how to create a HOC:
https://levelup.gitconnected.com/understanding-react-higher-order-components-by-example-95e8c47c8006
My code (before the article looked like this):
import { func } from 'prop-types';
import React, { PureComponent } from 'react';
import { Input } from 'reactstrap';
class SelectableInput extends PureComponent {
handleMouseUp = () => {
const selection = window.getSelection();
if (selection) {
this.props.onSelectionChanged(selection.toString());
}
};
render() {
// eslint-disable-next-line
const { onSelectionChanged, ...rest } = this.props;
return <Input onMouseUp={this.handleMouseUp} {...rest} />;
}
}
SelectableInput.propTypes = {
onSelectionChanged: func
};
export default SelectableInput;
And I was using it like this:
render() {
return (
<SelectableInput
type="textarea"
name="textarea-input"
value={'This is some txt'}
onSelectionChanged={onTextSelectionChanged}
id="textarea-input"
onChange={e => this.onPageDataChanged(e)}
dir="rtl"
rows="14"
placeholder="Placeholder..."
/>
);
}
After reading the article I changed the above code to:
const SelectableInput = WrappedInput => {
class SelectableInputHOC extends PureComponent {
handleMouseUp = () => {
const selection = window.getSelection();
if (selection) {
this.props.onSelectionChanged(selection.toString());
}
};
render() {
// eslint-disable-next-line
const { onSelectionChanged, ...rest } = this.props;
return <WrappedInput onMouseUp={this.handleMouseUp} {...rest} />;
}
}
SelectableInputHOC.propTypes = {
onSelectionChanged: func
};
};
export default SelectableInput;
My question is how do I actually go about using it now in a render() function?
Thank you for your advance for your help.
SelectableInput is a function that returns a function that takes a component as a parameter and returns another component. You can use it like this:
const ResultComponent = ({...props}) =>
SelectableInput({...props})(YourParamComponent);
Then render ResultComponent wherever you want.
Here you have an example of using a HOC and passing props to it:
https://jsfiddle.net/58c7tmx2/
HTML:
<div id="root"></div>
JS
const YourParamComponent = ({ name }) => <div>Name: {name}</div>
const SelectableInput = ({...props}) =>
WrappedInput => <WrappedInput {...props} />
const ResultComponent = ({...props}) =>
SelectableInput({...props})(YourParamComponent);
const App = () => <ResultComponent name="textarea-input" />
ReactDOM.render(
<App />,
document.getElementById('root')
)