I'm facing issue with follow DraftJS editor component.
import React, { useRef } from "react";
import styled from "styled-components";
import { EditorState, Editor as DraftEdior } from "draft-js";
const Wrapper = styled.div`
border: 1px solid #ccc;
padding: 1rem;
`;
interface IEditorProps {
state: EditorState;
onChange: (state: EditorState) => void;
readOnly?: boolean;
}
export const Editor: React.FunctionComponent<IEditorProps> = (props) => {
const { state, onChange, readOnly } = props;
const ref = useRef<DraftEdior | null>(null);
return (
<Wrapper onClick={() => ref?.current?.focus()}>
<DraftEdior
ref={ref}
editorState={state}
onChange={onChange}
readOnly={!!readOnly}
/>
</Wrapper>
);
};
export default Editor;
and the App.js
import React from "react";
import { EditorState } from "draft-js";
import { Editor } from "./Editor";
const App = () => {
const [state, setState] = React.useState<EditorState>(() =>
EditorState.createEmpty()
);
return (
<>
<h3>Editor</h3>
<Editor state={state} onChange={(state) => setState(state)} />
<h3>Result</h3>
<Editor state={state} readOnly onChange={() => null} />
</>
);
};
export default App;
When only a single editor on the page, everything work ok. But the editor continuously lost focus when I add a readonly component.
This is a code sanbox link
https://codesandbox.io/s/vigilant-wilson-70op8?file=/src/App.tsx:0-451
How to fix it, thank you very much.
Related
I have a react component that takes the input from a materialUi text form and uses a useState hook to update. On submit I want it to update another variable to the value. This is being used in an h1 element. However, the h1 element is not updating. I know the variable is updating because I log it in the console and it updates.
import React, { useState } from 'react';
import CssBaseline from '#mui/material/CssBaseline';
import { ThemeProvider, createTheme } from '#mui/material/styles';
import Alert from '#mui/material/Alert';
import { TextField } from '#mui/material';
import Button from '#mui/material/Button';
import './style.css';
import { useEffect } from 'react';
const darkTheme = createTheme({
palette: {
mode: 'dark',
},
});
export default function App() {
const [myValue, setValue] = useState('') // The original hook
let headingText = 'Hello World'
let onSubmit = (e) => {
e.preventDefault();
headingText = myValue;
console.log(headingText);
console.log(myValue);
};
return (
<>
<title>Text Changer</title>
<div className='joe'>
<ThemeProvider theme={darkTheme}>
<React.Fragment>
<CssBaseline />
<Alert severity="success">Page Loaded</Alert>
<h1>{headingText}</h1> // the h1 I want to update
<form onSubmit={onSubmit}>
<TextField id="outlined-basic" label="Search Text" variant="outlined" value={myValue} onChange={(e) => setValue(e.target.value)}/>
<br />
<br />
<Button variant="contained" color="primary" type="submit">Submit</Button>
</form>
</React.Fragment>
</ThemeProvider>
</div>
</>
);
}
You'll want to useState for that as well
const [headingText, setHeadingText] = useState('Hello World');
// ...
setHeadingText(myValue);
You have to create a state as
const [headingText, setHeadingText] = useState("Hello World");
CODESANDBOX DEMO.
and then onClick of a button you have to update state. React will re-render the component if the state changes
let onSubmit = (e) => {
e.preventDefault();
setHeadingText(myValue);
};
I am creating my react app with material-ui Snackbar.
In my project I have a lot of components and don't want to insert <Snackbar/> in each of them.
Is there a way to create function that will show snackbar, then just import and use this function in each component?
Something like:
import showSnackbar from 'SnackbarUtils';
showSnackbar('Success message');
You have to do it in react way. You can achieve this by creating a Higher Order Component.
Create a HOC that returns a snackbar component along with the wrappedComponent
Create a function in that HOC which accepts message, severity (if you are using Alert like me), duration and sets the appropriate states which are set to the props of the snackbar. And pass that function as a prop to the wrappedComponent.
Finally import this HOC wherever you want to display a snackbar, pass your component in it and call the HOC function from the prop (this.prop.functionName('Hello there!')) in the event handler where you want to display a snackbar and pass in a message.
Check this out.
https://stackblitz.com/edit/snackbar-hoc?file=src/SnackbarHOC.js
extend it as a Hook, and then you can call it once and use state with effects to show:
import { useSnackbar } from 'notistack';
import IconButton from "#mui/material/IconButton";
import CloseIcon from "#mui/material/SvgIcon/SvgIcon";
import React, {Fragment, useEffect, useState} from "react";
const useNotification = () => {
const [conf, setConf] = useState({});
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
const action = key => (
<Fragment>
<IconButton onClick={() => { closeSnackbar(key) }}>
<CloseIcon />
</IconButton>
</Fragment>
);
useEffect(()=>{
if(conf?.msg){
let variant = 'info';
if(conf.variant){
variant = conf.variant;
}
enqueueSnackbar(conf.msg, {
variant: variant,
autoHideDuration: 5000,
action
});
}
},[conf]);
return [conf, setConf];
};
export default useNotification;
Then you can use it:
const [msg, sendNotification] = useNotification();
sendNotification({msg: 'yourmessage', variant: 'error/info.....'})
Here is a sample code for fully working example using Redux, Material-ui and MUI Snackbar
import { random } from 'lodash'
import { Action } from 'redux'
import actionCreatorFactory, { isType } from 'typescript-fsa'
const actionCreator = actionCreatorFactory()
export type Notification = {
message: string
}
export type NotificationStore = Notification & {
messageId: number
}
export const sendNewNotification =
actionCreator<Notification>('NEW_NOTIFICATION')
const defaultState: NotificationStore = { message: '', messageId: 1 }
const reducer = (
state: NotificationStore = defaultState,
action: Action
): NotificationStore => {
if (isType(action, sendNewNotification)) {
const {
payload: { message }
} = action
return { message, messageId: random(0, 200000) }
}
return state
}
export default reducer
// useNotification to get state from Redux, you can include them into same file if you prefer
import { NotificationStore } from './notification'
export function useNotification(): NotificationStore {
return useSelector<NotificationStore>(
(state) => state.notification
)
}
// Notification React-component - Notification.tsx
import React, { useState } from 'react'
import Button from '#mui/material/Button'
import Snackbar from '#mui/material/Snackbar'
import IconButton from '#mui/material/IconButton'
import CloseIcon from '#mui/icons-material/Close'
type Props = {
message: string
}
export function Notification({ message }: Props): JSX.Element | null {
const [notiOpen, setNotiOpen] = useState(true)
if (!message) {
return null
}
return (
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left'
}}
open={notiOpen}
autoHideDuration={10000}
onClose={() => setNotiOpen(false)}
message={message}
action={
<React.Fragment>
<Button
color="secondary"
size="small"
onClick={() => setNotiOpen(false)}
>
Close
</Button>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={() => setNotiOpen(false)}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
}
/>
)
}
// Main App.tsx to run my application
import { Notification } from "./Notification.tsx"
import { useDispatch } from 'react-redux'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
const App: React.FC<AppProps> = () => {
const dispatch = useDispatch()
const { message, messageId } = useNotification()
return (
<ThemeProvider theme={appTheme}>
<Router>
<Switch>
<Route path="/public/:projectId" component={ProjectPage} />
<Route path="/login" component={LoginPage} />
<Route render={() => <PageNotFound />} />
</Switch>
</Router>
<Notification key={messageId} message={message} />
</ThemeProvider>
)
}
export default App
// Usage of hook in application - FileSomething.tsx
import { useDispatch } from 'react-redux'
import { useEffect } from 'react'
import { sendNewNotification } from 'src/redux/notification'
export function FileSomething(): JSX.Element {
function sendNotification() {
dispatch(
sendNewNotification({
message: 'Hey, im a notification'
})
)
}
useEffect(() => {
sendNotification()
}, [])
return (
<div>Component doing something</div>
)
}
I'm trying to create a darkmode library (named react-goodnight) based on https://github.com/luisgserrano/react-dark-mode.
This is where the context is created.
import React from 'react'
const ThemeContext = React.createContext({
theme: '',
toggle: () => {}
})
export default ThemeContext
This is my useDarkMode hook that get/sets the theme to localStorage.
import { useState, useEffect } from 'react'
const useDarkMode = () => {
const [theme, setTheme] = useState('light')
const setMode = (mode) => {
window.localStorage.setItem('theme', mode)
setTheme(mode)
}
const toggle = () => (theme === 'light' ? setMode('dark') : setMode('light'))
useEffect(() => {
const localTheme = window.localStorage.getItem('theme')
localTheme && setTheme(localTheme)
}, [])
return [theme, toggle]
}
export default useDarkMode
This is the index of my library (react-goodnight).
import React, { useContext } from 'react'
import { ThemeProvider } from 'styled-components'
import { GlobalStyles } from './globalStyles'
import { lightTheme, darkTheme } from './settings'
import ThemeContext from './themeContext'
import useDarkMode from './useDarkMode'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</ThemeProvider>
)
}
export const useDarkModeContext = () => useContext(ThemeContext)
export default Provider
And, in the end, this is my example app where I'm trying to use it.
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
const { theme, toggle } = useDarkModeContext();
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The "Toggle" button in the library's index works fine but the one in my example app does not.
The useDarkModeContext() returns empty.
What could be the issue?
Thanks!
You are doing wrong
1st option
you can use react-goodnight provider with your index.js and use useDarkModeContext(), don't name your index.js Provider else you can not use Provider coming from react-goodnight
import Provider, { useDarkModeContext } from 'react-goodnight'
const Provider = ({ children }) => {
const [theme, toggle] = useDarkMode()
return (
<ThemeProvider theme={theme === 'light' ? lightTheme : darkTheme}>
<GlobalStyles />
<Provider>
<ThemeContext.Provider value={{ theme, toggle }}>
<button onClick={toggle}>Toggle</button>
{children}
</ThemeContext.Provider>
</Provider>
</ThemeProvider>
)
}
2nd Option
you are passing ThemeContext in your index.js so you can also access that in app.js
import React, { useContext } from 'react'
import ThemeContext from './themeContext'
const App = () => {
const theme = useContext(ThemeContext);
console.log(theme)
return (
<Provider>
<div>hey</div>
<button onClick={toggle}>Toggle</button>
</Provider>
)
}
export default App
The reason it's not working is because you are calling useContext in the very same place where you print Provider.
Why is that wrong? Because useContext looks for parent context providers. By rendering Provider in the same place you call useContext, there is no parent to look for. The useContext in your example is actually part of App component, who is not a child of Provider.
All you have to do is move the button outside of that print, to its own component, and only there do useContext (or in your case the method called useDarkModeContext.
The only change would be:
import React from 'react'
import Provider, { useDarkModeContext } from 'react-goodnight'
const App = () => {
return (
<Provider>
<div>hey</div>
<ToggleThemeButton />
</Provider>
)
}
export default App
const ToggleThemeButton = () => {
const { theme, toggle } = useDarkModeContext();
return (
<button onClick={toggle}>Switch Theme outside</button>
);
};
I recently built a components library but when I import my components my IDE doesn't show autocomplete props like other components do (based on propTypes) :
In my library, all components are exported with a HOC and I found out that by removing the HOC the props are now available in auto complete. Here's my HOC :
import * as React from 'react'
import hoistNonReactStatics from 'hoist-non-react-statics'
import PropTypes from 'prop-types'
import { defaultTheme } from '../styles/defaultTheme'
const withTheme = (WrappedComponent) => {
const ThemeContext = React.createContext(defaultTheme)
const Enhance = (props) => {
const { _reactThemeProviderForwardedRef, ...rest } = props
return (
<ThemeContext.Consumer>
{(theme) => (
<WrappedComponent
{...rest}
theme={theme}
ref={_reactThemeProviderForwardedRef}
/>
)}
</ThemeContext.Consumer>
)
}
Enhance.propTypes = {
_reactThemeProviderForwardedRef: PropTypes.any,
...WrappedComponent.propTypes,
}
const ResultComponent = React.forwardRef((props, ref) => (
<Enhance {...props} _reactThemeProviderForwardedRef={ref} />
))
ResultComponent.displayName = `withTheme(${
WrappedComponent.displayName || WrappedComponent.name
})`
return hoistNonReactStatics(ResultComponent, WrappedComponent)
}
export default withTheme
I'm probably doing something wrong in my withTheme but I cannot find out what.
Here is one of the components :
import React from 'react'
import { Text, StyleSheet, TextPropTypes } from 'react-native'
import PropTypes from 'prop-types'
import { buildStyleSheet } from '../util/style-helpers'
import { withTheme } from '../core/theming'
const CustomText = React.forwardRef((props, ref) => {
const { children, className = '', style, theme, ...others } = props
const customStyle = buildStyleSheet(className, 'text', theme)
return (
<Text
ref={ref}
style={StyleSheet.flatten([customStyle, style])}
{...others}
>
{children}
</Text>
)
})
const propTypes = {
children: PropTypes.any,
className: PropTypes.string,
theme: PropTypes.object.isRequired,
...TextPropTypes,
}
CustomText.displayName = 'CustomText'
CustomText.propTypes = propTypes
export default withTheme(CustomText)
I'd like to supply a material UI TextField component to the PhoneInput component from react-phone-number-input as the inputComponent prop.
However, I don't seem to be able to successfully apply the ref. Although I see the Material UI TextField component rendered to the UI and state is successfully updated with the value, it keeps loosing focus after the first value has been typed.
import React, { forwardRef, createRef } from 'react';
import { TextField } from '#material-ui/core';
import 'react-phone-number-input/style.css';
import PhoneInput from 'react-phone-number-input';
const SampleComponent = ({ handleChange }) => {
const phoneInput = forwardRef((props, ref) => {
return (
<TextField
inputRef={ref}
fullWidth
label="Phone Number"
variant="outlined"
name="phone"
onChange={handleChange}
/>
);
});
const ref = createRef();
return (
<PhoneInput ref={ref} inputComponent={phoneInput} />
);
};
So I was able to get it to work using the following method. Any suggestions on how to improve this are more than welcome.
I have a separate file called PhoneNumber.jsx
import { forwardRef } from 'react'
import TextField from '#material-ui/core/TextField'
import { makeStyles } from '#material-ui/core/styles'
const useStyles = makeStyles(theme => ({
input: {
backgroundColor: '#fff'
}
}))
const phoneInput = (props, ref) => {
const classes = useStyles()
return (
<TextField
{...props}
InputProps={{
className: classes.input
}}
inputRef={ref}
fullWidth
size='small'
label='Phone Number'
variant='outlined'
name='phone'
/>
)
}
export default forwardRef(phoneInput)
And my form file:
import PhoneInput from 'react-phone-number-input'
import CustomPhoneNumber from '../components/prebuilt/PhoneNumber'
...
<PhoneInput
placeholder='Enter phone number'
value={phone}
onChange={setPhone}
inputComponent={CustomPhoneNumber}
/>
...
There is also a package for Material UI v5 (or MUI) called Mui tel input and it's working with React 17 and 18 !
Simply way to use. Phone validation available too.
Check the doc here : https://github.com/viclafouch/mui-tel-input
import React from 'react'
import { MuiTelInput } from 'mui-tel-input'
const MyComponent = () => {
const [value, setValue] = React.useState('')
const handleChange = (newValue) => {
setValue(newValue)
}
return <MuiTelInput label="Phone" fullWidth value={value} onChange={handleChange} />
}