Now, I doing a project with React Framework and Material-UI's library.
My templates from https://material-ui.com/getting-started/templates/ --> Sing In
I change component from function component to class component to use this.state. Because I want get values from keyboard. But I can't.
It's error
enter image description here
this is my code
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
},
}));
class Signin extends Component {
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
redirectToReferrer: false,
message: null
}
}
render() {
const { redirectToReferrer } = this.state;
const { classes } = this.props;
if (redirectToReferrer) return <Redirect to="/routebasic" />;
return (
<div className={classes.root}>
}}
Signin.propTypes = {
classes: PropTypes.object.isRequired,
};
export default [connect(null, { ActSignin })(Signin),makeStyles(useStyles)(Signin)];
When I edit export default to
export default makeStyles(useStyles)(Signin);
It's still error.
Yes, it will error. There are (very hacky) ways to get hooks to work with classes, but you don't need to use a class just so that you can have state. It's much easier to just use a function component rather than trying to hack React.
I change component from function component to class component to use this.state. Because I want get values from keyboard.
As an example (I'm guessing here, because I don't know the context, and I've split out the object rather than keeping it as a single value):
const Signin = ({ classes }) => {
const [username, setUsername] = React.useState("kmitlclinic01");
const [password, setPassword] = React.useState("54788");
const [redirectToReferrer, setRedirectToReferrer] = React.useState(false);
const [message, setMessage] = React.useState("kmitlclinic01");
if (redirectToReferrer) return <Redirect to="/routebasic" />;
return (
<div className={classes.root}>
{/* I assume some stuff goes here? */}
</div>
);
}
export default [connect(null, { ActSignin })(Signin),makeStyles(useStyles)(Signin)];
Related
I am trying to create a HOC using react functional component that will take a component and some props, but I think I am missing something I did not get the props value in the component which I passed. I am also using typescript
My higher-order component:
interface EditChannelInfo {
Component: any;
setIsCollapsed: Function;
isCollapsed: boolean;
}
const EditChannelInfo = (props: EditChannelInfo): ReactElement => {
const {isCollapsed, setIsCollapsed, Component} = props;
const {data: gamesList} = useGamesList();
const games = gamesList.games.map((list: GamesList) => ({
value: list.gameId,
label: list.gameName,
}));
return <Component {...props} />;
};
export default EditChannelInfo;
From here I am passing the component to the higher-order component
import EditChannelInfoWrapper from '../EditChannelInfoWrapper';
const Dashboard: NextPage = (): ReactElement => {
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
return (
<div>
<EditChannelInfo
Component={EditChannelInfoWrapper}
setIsCollapsed={setIsCollapsed}
isCollapsed={isCollapsed}
/>
</div>
);
};
export default Dashboard;
I am getting games undefined
interface EditChannelInfoWrapper {
games: any;
}
const EditChannelInfoWrapper = (
props: EditChannelInfoWrapper,
): ReactElement => {
const {
games,
} = props;
console.log(games);
return ()
}
It looks like you're not passing your games prop to the Component here: <Component {...props} />.
Add in your games prop and it should work as expected <Component {...props} games={games} />
I have this component in my react js application:
import React, { useState } from "react";
import Select, { components, DropdownIndicatorProps } from "react-select";
import { ColourOption, colourOptions } from "./docs/data";
const Component = () => {
const [state, setState] = useState();
console.log(state);
const DropdownIndicator = (props) => {
const { menuIsOpen } = props.selectProps;
setState(menuIsOpen);
return (
<components.DropdownIndicator {...props}>12</components.DropdownIndicator>
);
};
return (
<Select
closeMenuOnSelect={false}
components={{ DropdownIndicator }}
defaultValue={[colourOptions[4], colourOptions[5]]}
isMulti
options={colourOptions}
/>
);
};
export default Component;
In the DropDownIndicator component i set the state:
const {
menuIsOpen
} = props.selectProps;
setState(menuIsOpen);
Setting the state in that place i get the next warning: Warning: Cannot update a component (Component) while rendering a different component (DropdownIndicator). To locate the bad setState() call inside DropdownIndicator . Question: How can i fix this warning and to make it disappear? demo: https://codesandbox.io/s/codesandboxer-example-forked-97sx0?file=/example.tsx:0-724
You should call setState inside useEffect
const DropdownIndicator = (props) => {
const { menuIsOpen } = props.selectProps;
useEffect(() => {
setState(menuIsOpen);
});
return (
<components.DropdownIndicator {...props}>12</components.DropdownIndicator>
);
};
What does useEffect do? By using this Hook, you tell React that your component needs to do something after render. Read more about useEffect
Incase if your setState is depend of menuIsOpen. Pass to useEffect as dependency.
const DropdownIndicator = (props) => {
const { menuIsOpen } = props.selectProps;
useEffect(() => {
setState(menuIsOpen);
},[menuIsOpen]);
return (
<components.DropdownIndicator {...props}>12</components.DropdownIndicator>
);
};
Complete solution on CodeSandbox
Just mark the useState with default value as false
const [state, setState] = useState(false);
looks like when you are doing setState(menuIsOpen); for the first time, its value is undefined and DropdownIndicator is not yet finished with first rendering,
[Hypothesis] There must be some code with React.Component as per the stack trace, based on undefined/null check. But when given initialState, the error is not occurring.
I have a requirement to render a specific component based on params. I have two solutions in mind.
Solution 1:
export const getForm = () => {
const { name } = useParams();
const ComponentMap = {
account: <Account />,
contact: <Contact />,
};
return ComponentMap[name];
};
Solution 2:
export const getForm = () => {
const { name } = useParams();
const ComponentMap = {
account: Account,
contact: Contact,
};
return <ComponentMap[name] />;
};
I am more inclined to solution 2 as it won't create JSX elements for all the map values.
What's the standard pattern in React world. Can someone help me in deciding an approach?
Basically what you would do is after you extract a property "name" from useParams, my suggestion would be that you pass it as a Props to the component.
Like this
export const getForm = () => {
const { name } = useParams();
const componentForm = {
account: <Account name={name} />,
contact: <Contact name={name} />,
};
return componentForm[name];
};
Then I would capture that in the componenet:
For example in Account.jsx
export const Account = ({name}) => {
getDetails(name)
}
At least that is what I would do using React and React Routers
Solution 1 is a common practice, Solution 2 looks weird. Not sure about pros and cons, but Solution 1 is how 99% of React devs do, IMHO.
My personal preference is for Solution #2. This is a very powerful pattern as it allows you to pass through a standard set of props to any of your render components. For example, perhaps we have other values in the params that we want to pass as props. You could get props from other sources like redux, firebase, etc.
export const DynamicForm = () => {
const { name, ...rest } = useParams();
const ComponentMap = {
account: Account,
contact: Contact,
};
const Component = ComponentMap[name];
return <Component {...rest} />;
};
With Typescript types:
import { useParams } from "react-router-dom";
import React from "react";
const Account = ({someProp}: {someProp: string}) => <div>{someProp}</div>
const Contact = ({otherProp}: {otherProp: string}) => <div>{otherProp}</div>
interface MyParamsType {
name: string;
someProp: string;
otherProp: string;
}
export const DynamicForm = () => {
const { name, ...rest } = useParams<MyParamsType>();
// define the components' props as the type of `rest`
const ComponentMap: Record<string, React.ComponentType<Omit<MyParamsType, 'name'>>> = {
account: Account,
contact: Contact,
};
const Component = ComponentMap[name];
return <Component {...rest} />;
};
Typescript Playground Link
I have a problem with an indirect communication in react native.
I have a parent component, which is one component per class. And I have a child component that is a functional component.
Parent:
constructor(props) {
super(props);
this.state = {
search: '',
};
}
getInfoSearch(userSearch) {
this.setState({
search: userSearch
})
}
render(){
return(
<SearchHeader placeholder={'Buscar procedimento'} getValueUserSearch={this.getInfoSearch}/>
)
}
Child:
import React, {useState} from 'react';
import {View, TextInput} from 'react-native';
const SearchHeader = props => {
const [search, setSearch] = useState('');
const {placeholder, getValueUserSearch} = props;
const handleSearch = (search) => {
console.log(this);
setSearch(search);
getValueUserSearch(search);
};
return (
<View>
<TextInput placeholder={placeholder || 'Buscar'} onChangeText={handleSearch}/>
</View>
);
};
export default SearchHeader;
But when I type text in the InputText, an error occurs. Stating that:
"I cannot apply the setState function of undefined"
Do you know how I could solve this problem? Because I want to change the 'search' state in the parent element.
Error might be because of this.setState line in getInfoSearch function.
Try using arrow function or do the explicit binding in constructor as below
constructor(props) {
...
this.getInfoSearch = this.getInfoSearch.bind(this);
}
(Or)
getInfoSearch = (userSearch) => {
this.setState({ search: userSearch });
}
Check here for more details.
It's because you are not passing argument to the handleSearch function, it should be:
onChangeText={(event) => handleSearch(event.target.value}
I have updated this with an update at the bottom
Is there a way to maintain a monolithic root state (like Redux) with multiple Context API Consumers working on their own part of their Provider value without triggering a re-render on every isolated change?
Having already read through this related question and tried some variations to test out some of the insights provided there, I am still confused about how to avoid re-renders.
Complete code is below and online here: https://codesandbox.io/s/504qzw02nl
The issue is that according to devtools, every component sees an "update" (a re-render), even though SectionB is the only component that sees any render changes and even though b is the only part of the state tree that changes. I've tried this with functional components and with PureComponent and see the same render thrashing.
Because nothing is being passed as props (at the component level) I can't see how to detect or prevent this. In this case, I am passing the entire app state into the provider, but I've also tried passing in fragments of the state tree and see the same problem. Clearly, I am doing something very wrong.
import React, { Component, createContext } from 'react';
const defaultState = {
a: { x: 1, y: 2, z: 3 },
b: { x: 4, y: 5, z: 6 },
incrementBX: () => { }
};
let Context = createContext(defaultState);
class App extends Component {
constructor(...args) {
super(...args);
this.state = {
...defaultState,
incrementBX: this.incrementBX.bind(this)
}
}
incrementBX() {
let { b } = this.state;
let newB = { ...b, x: b.x + 1 };
this.setState({ b: newB });
}
render() {
return (
<Context.Provider value={this.state}>
<SectionA />
<SectionB />
<SectionC />
</Context.Provider>
);
}
}
export default App;
class SectionA extends Component {
render() {
return (<Context.Consumer>{
({ a }) => <div>{a.x}</div>
}</Context.Consumer>);
}
}
class SectionB extends Component {
render() {
return (<Context.Consumer>{
({ b }) => <div>{b.x}</div>
}</Context.Consumer>);
}
}
class SectionC extends Component {
render() {
return (<Context.Consumer>{
({ incrementBX }) => <button onClick={incrementBX}>Increment a x</button>
}</Context.Consumer>);
}
}
Edit: I understand that there may be a bug in the way react-devtools detects or displays re-renders. I've expanded on my code above in a way that displays the problem. I now cannot tell if what I am doing is actually causing re-renders or not. Based on what I've read from Dan Abramov, I think I'm using Provider and Consumer correctly, but I cannot definitively tell if that's true. I welcome any insights.
There are some ways to avoid re-renders, also make your state management "redux-like". I will show you how I've been doing, it far from being a redux, because redux offer so many functionalities that aren't so trivial to implement, like the ability to dispatch actions to any reducer from any actions or the combineReducers and so many others.
Create your reducer
export const initialState = {
...
};
export const reducer = (state, action) => {
...
};
Create your ContextProvider component
export const AppContext = React.createContext({someDefaultValue})
export function ContextProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState)
const context = {
someValue: state.someValue,
someOtherValue: state.someOtherValue,
setSomeValue: input => dispatch('something'),
}
return (
<AppContext.Provider value={context}>
{props.children}
</AppContext.Provider>
);
}
Use your ContextProvider at top level of your App, or where you want it
function App(props) {
...
return(
<AppContext>
...
</AppContext>
)
}
Write components as pure functional component
This way they will only re-render when those specific dependencies update with new values
const MyComponent = React.memo(({
somePropFromContext,
setSomePropFromContext,
otherPropFromContext,
someRegularPropNotFromContext,
}) => {
... // regular component logic
return(
... // regular component return
)
});
Have a function to select props from context (like redux map...)
function select(){
const { someValue, otherValue, setSomeValue } = useContext(AppContext);
return {
somePropFromContext: someValue,
setSomePropFromContext: setSomeValue,
otherPropFromContext: otherValue,
}
}
Write a connectToContext HOC
function connectToContext(WrappedComponent, select){
return function(props){
const selectors = select();
return <WrappedComponent {...selectors} {...props}/>
}
}
Put it all together
import connectToContext from ...
import AppContext from ...
const MyComponent = React.memo(...
...
)
function select(){
...
}
export default connectToContext(MyComponent, select)
Usage
<MyComponent someRegularPropNotFromContext={something} />
//inside MyComponent:
...
<button onClick={input => setSomeValueFromContext(input)}>...
...
Demo that I did on other StackOverflow question
Demo on codesandbox
The re-render avoided
MyComponent will re-render only if the specifics props from context updates with a new value, else it will stay there.
The code inside select will run every time any value from context updates, but it does nothing and is cheap.
Other solutions
I suggest check this out Preventing rerenders with React.memo and useContext hook.
I made a proof of concept on how to benefit from React.Context, but avoid re-rendering children that consume the context object. The solution makes use of React.useRef and CustomEvent. Whenever you change count or lang, only the component consuming the specific proprety gets updated.
Check it out below, or try the CodeSandbox
index.tsx
import * as React from 'react'
import {render} from 'react-dom'
import {CountProvider, useDispatch, useState} from './count-context'
function useConsume(prop: 'lang' | 'count') {
const contextState = useState()
const [state, setState] = React.useState(contextState[prop])
const listener = (e: CustomEvent) => {
if (e.detail && prop in e.detail) {
setState(e.detail[prop])
}
}
React.useEffect(() => {
document.addEventListener('update', listener)
return () => {
document.removeEventListener('update', listener)
}
}, [state])
return state
}
function CountDisplay() {
const count = useConsume('count')
console.log('CountDisplay()', count)
return (
<div>
{`The current count is ${count}`}
<br />
</div>
)
}
function LangDisplay() {
const lang = useConsume('lang')
console.log('LangDisplay()', lang)
return <div>{`The lang count is ${lang}`}</div>
}
function Counter() {
const dispatch = useDispatch()
return (
<button onClick={() => dispatch({type: 'increment'})}>
Increment count
</button>
)
}
function ChangeLang() {
const dispatch = useDispatch()
return <button onClick={() => dispatch({type: 'switch'})}>Switch</button>
}
function App() {
return (
<CountProvider>
<CountDisplay />
<LangDisplay />
<Counter />
<ChangeLang />
</CountProvider>
)
}
const rootElement = document.getElementById('root')
render(<App />, rootElement)
count-context.tsx
import * as React from 'react'
type Action = {type: 'increment'} | {type: 'decrement'} | {type: 'switch'}
type Dispatch = (action: Action) => void
type State = {count: number; lang: string}
type CountProviderProps = {children: React.ReactNode}
const CountStateContext = React.createContext<State | undefined>(undefined)
const CountDispatchContext = React.createContext<Dispatch | undefined>(
undefined,
)
function countReducer(state: State, action: Action) {
switch (action.type) {
case 'increment': {
return {...state, count: state.count + 1}
}
case 'switch': {
return {...state, lang: state.lang === 'en' ? 'ro' : 'en'}
}
default: {
throw new Error(`Unhandled action type: ${action.type}`)
}
}
}
function CountProvider({children}: CountProviderProps) {
const [state, dispatch] = React.useReducer(countReducer, {
count: 0,
lang: 'en',
})
const stateRef = React.useRef(state)
React.useEffect(() => {
const customEvent = new CustomEvent('update', {
detail: {count: state.count},
})
document.dispatchEvent(customEvent)
}, [state.count])
React.useEffect(() => {
const customEvent = new CustomEvent('update', {
detail: {lang: state.lang},
})
document.dispatchEvent(customEvent)
}, [state.lang])
return (
<CountStateContext.Provider value={stateRef.current}>
<CountDispatchContext.Provider value={dispatch}>
{children}
</CountDispatchContext.Provider>
</CountStateContext.Provider>
)
}
function useState() {
const context = React.useContext(CountStateContext)
if (context === undefined) {
throw new Error('useCount must be used within a CountProvider')
}
return context
}
function useDispatch() {
const context = React.useContext(CountDispatchContext)
if (context === undefined) {
throw new Error('useDispatch must be used within a AccountProvider')
}
return context
}
export {CountProvider, useState, useDispatch}
To my understanding, the context API is not meant to avoid re-render but is more like Redux. If you wish to avoid re-render, perhaps looks into PureComponent or lifecycle hook shouldComponentUpdate.
Here is a great link to improve performance, you can apply the same to the context API too