How to initialize the react functional component state from props - reactjs

I'm using React hooks for app state, I wondered about how to initialize the functional component state using props? The useState hook doc says something definitive like,
const [count, setCount] = useState(0);
I want to initialize that 0 value by the value of props being passed to the component. The Older as,
import React from 'react';
export default class Sym extends React.Component{
constructor(props){
super(props);
this.state = {
sym : [0,3,2,8,5,4,1,6],
active: this.props.activeSym
}
this.setActive = this.setActive.bind(this);
}
setActive(itemIndex){
this.setState({
active: itemIndex
});
}
render(){
return (
<div><h1>{ this.state.sym[this.state.active]}</h1></div>
);
}
}
works fine. Where the parent Component passes activeSym prop and Sym component initializes the state with it using this.props.activeSym in constructor. Is there any workaround to achieve same in function component?

First you can define it from props (if the prop exist):
const [count, setCount] = useState(activeSym);
And then you can update this value, when prop doesn't have a value immediately (when component rendered):
useEffect(() => {
if (activeSym) {
setCount(activeSym);
}
}, [activeSym])

Yes, this can be possible with functional component too! You just need to add useEffect to listen to prop change for initializing state with prop value
export const newComponent = (props) => {
const { path, value, info, update } = props;
const [val, setVal] = useState(value);
useEffect(() => {
setVal(value);
}, [value]);
return <div>{val}</div>;
};
Attching sandbox link
https://codesandbox.io/s/confident-agnesi-ohkq7?file=/src/MakeComponent.js

Yes you can first define state using props:
const [name, setName] = useState(props.obj?.name);
And then you can if the state is still undefined means props doesn't have a value, then:
useEffect(() => {
if (JSON.stringify(props.obj) !== "{}") {
setName(props.obj?.name);
}
}, [props.obj])

Just as follows :
const MyFunctionalComponent = ({myProp}) => {
const [count, setCount] = useState(myProp)
return (
/* ... */
)
}

There are two ways you can change the state:
one is using this.state and
another one is this.setState.
We use the first method to initialize the state in the constructor, and the second method is used for the rest of the time.
Initialize State in the Constructor
One way is to initialize the state is in the constructor. As we discussed earlier constructor is the first method to be called when React instantiates the class. This is the perfect place to initialize the state for the component because the constructor is called before the React renders the component in the UI.
class WithConstructor {
constructor() {
this.state = {
name: "StackOverflow"
}
}
}
Initialize State Without Constructor
Another way of initializing state in React is to use the Class property. Once the class is instantiated in the memory all the properties of the class are created so that we can read these properties in the render function.
class WithoutConstructor {
state = {
name: "StackOverflow"
}
}

Related

React, TypeScript - Using non-React objects to inject components into component tree

I'm working on a React Typescript project. A very simplified version of the project is below. I'm trying to use more traditional polymorphism here where I have components returned from vanilla Typescript objects (not React components) that are rendered in the component tree. The reason I want to do this is so that I can have polymorphic classes that I change at runtime and that manage their own state and business logic.
import React, { useEffect } from "react";
class ClickCounter {
private count: number;
constructor() {
this.count = 0;
}
IncrementCount() {
this.count += 1;
}
GetCount(): number {
return this.count;
}
}
interface Operation {
HandleMouseDown(event: React.MouseEvent<HTMLDivElement>): void;
GetComponents(): JSX.Element[];
}
class ClickCounterOperation implements Operation {
private clickCounter: ClickCounter;
constructor() {
const counter: ClickCounter = new ClickCounter();
this.clickCounter = counter;
}
HandleMouseDown(_: React.MouseEvent<HTMLDivElement>): void {
this.clickCounter.IncrementCount();
}
GetComponents(): JSX.Element[] {
const count: number = this.clickCounter.GetCount();
return [<div>you have clicked {count} times</div>];
}
}
export type AppState = {
currentOperation: Operation;
};
export class App extends React.Component<{}, AppState> {
constructor(props = {}) {
super(props);
const initialOperation: Operation = new ClickCounterOperation();
this.state = {
currentOperation: initialOperation,
};
this.HandleMouseDown = this.HandleMouseDown.bind(this);
}
HandleMouseDown(event: React.MouseEvent<HTMLDivElement>) {
console.log("Dispatching mouse down event to current operation");
this.state.currentOperation.HandleMouseDown(event);
}
render() {
return (
<div className="App" onMouseDown={this.HandleMouseDown}>
{this.state.currentOperation.GetComponents()}
<div>some other stuff to show</div>
</div>
);
}
}
export default App;
In the example above everything will render initially, but not after the count is updated. This is because react has no way of knowing that the state has changed and that a rerender is needed. What I'm currently doing is forcing React to rerender by passing down a RefreshOperationState callback to the Operation object that will call the App.setState() method but this feels very ugly and I don't want to do this.
Any way to achieve this kind of traditional polymorphism with React and have non-React objects inject components into the component tree and have the components update when appropriate? I understand what I am trying to do is not following the common React patterns of using Flux/Redux and having all/most app state in a store/s, but I'd like to make this app in a less functional and more OOP pattern where objects store their own state and are called polymorphicly.
Any suggestions?
As you've noted, mixing paradigms might be more trouble than it's worth. React relies on object reference equality to handle its rendering logic. Since you're mutating objects instead of creating new ones, it will never know to update.
Another rule of React state is that it is only data (never behavior and definitely not JSX), and you're trying to use both.
You could make components which use hooks like these, and then let your parent component choose how it composes itself based on what kind of Operation you want.
const useClickCounter = () => {
const [count, setCount] = useState(0);
const incCount = setCount(count + 1);
return [incCount, count];
};
The only other thing I've done is use the observable pattern on the class objects and have a React context in between which observes them and sends the updated state into the React component. The React context provider will cause all consumers beneath it to rerender with fresh state.
public subscribe = (fn: (state) => void) => {
this.observers.push(fn);
}
private update = async () => {
// Give new state to the Context which is subscribed
this.observers.forEach(fn => fn(state));
}
PS: if you're familiar with Redux, you could start with something like this:
const ClickCounter = () => {
const value = useSelector(selectedClickCounter);
return <div>{value}</div>;
};
const operations = {
clickCounter: {
RenderComponent: ClickCounter,
onPressDispatchData: { type: "increment-counter" },
},
};
const OperationHandler = () => {
const [currentOperation, setCurrentOperation] = useState(operations.clickCounter);
return <HandleMouse {...currentOperation} />;
};
const HandleMouse = (props) => {
return (
<div className="App" onMouseDown={props.onPressDispatchData}>
{props.RenderComponent}
<div>some other stuff to show</div>
</div>
);
};

React.useState react differently as a prop or as a property?

and my deepest appologies for the questions spam (I really have troubles understanding React learning it alone). In my actual code, I have created a context, specified to be able to change:
const ThemeContext = React.createContext(["light", () => {}]);
In my app function, I create a hook (I still doesn't clearly understand why, see my very last bonus question) on it so I'll be able to access those data in child components:
const themeHook = React.useState("light");
return (<ThemeContext.Provider value={themeHook}>...</ThemeContext.Provider>);
The component where I use it, I had to encompass it in a function to be able to to pass the hook data though props:
function ThemeTogglerFunction() {
const [contextType, toggleTheme] = React.useContext(ThemeContext);
return <ThemeToggler context={contextType} toggler={toggleTheme} />;
}
And finally, my ThemeToggler component can use that theme data and even switch between the two values I specified, I'm happy:
class ThemeToggler extends React.Component {
constructor(props) {
super(props);
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() { this.props.toggler(this.props.context === "light" ? "dark" : "light"); }
render() { return (<span style={ownStyle} onClick={this.handleToggle}>Toggle actuel {this.props.context} theme</span>); }
}
But then, if I insert the props into local properties (which sounds more proper to me):
class ThemeTogglerLocal extends React.Component {
constructor(props) {
super(props);
this.gloablThemeContext = props.context;
this.gloablThemeToggler = props.toggler;
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() { this.gloablThemeToggler(this.gloablThemeContext === "light" ? "dark" : "light"); }
render() { return (<span style={ownStyle} onClick={this.handleToggle}>Toggle actuel {this.gloablThemeContext} theme</span>); }
}
even if the hook value is correctly changed one ("light" becomes"dark") in the global ThemeContext (I can see it as everything is rerendered with the correct color change), the said local properties is not changed (is stays on "light") and so the app stays in "dark" mode from there. I suppose that is because the property is initialised in the constructor and have no more link with the hook, is it correct ? Does it mean that when you "import" a hook into a component, you shouldn't save it into local properties ?
Furthermore (small bonus question) : I had to create a hook in my app function, and I don't understand why, as React.createContext() already creates something which looks like a hook. Why can't I pass it directly the Theme Context.Provider (<ThemeContext.Provider value={ThemeContext}> in place of <ThemeContext.Provider value={themeHook}>) ?
Thank you very much in advance, and sorry for the length of this post.
Given
class ThemeTogglerLocal extends React.Component {
constructor(props) {
super(props);
this.gloablThemeContext = props.context;
this.gloablThemeToggler = props.toggler;
this.handleToggle = this.handleToggle.bind(this);
}
handleToggle() {
this.gloablThemeToggler(
this.gloablThemeContext === 'light' ? 'dark' : 'light',
);
}
render() {
return (
<span onClick={this.handleToggle} style={ownStyle}>
Toggle actuel {this.gloablThemeContext} theme
</span>
);
}
}
Even if the hook value is correctly changed one ("light"
becomes"dark") in the global ThemeContext (I can see it as everything
is rerendered with the correct color change), the said local
properties is not changed (is stays on "light") and so the app stays
in "dark" mode from there. I suppose that is because the property is
initialised in the constructor and have no more link with the hook, is
it correct ?
Correct, in the class-based component you would need to implement the componentDidUpdate lifecycle method to "react" to the prop values changing in order to update the class properties.
componentDidUpdate(prevProps) {
if (prevProps.context !== this.props.context( {
this.gloablThemeContext = props.context;
this.gloablThemeToggler = props.toggler;
}
}
Does it mean that when you "import" a hook into a
component, you shouldn't save it into local properties?
This is exactly correct. Storing passed props into local component state (or class variables in your case) is an anti-pattern in React. It often leads to stale state/enclosures/etc and buggy code. You should consume passed props directly in the rendered result or any other lifecycle methods.
Furthermore (small bonus question): I had to create a hook in my app
function, and I don't understand why, as React.createContext() already
creates something which looks like a hook. Why can't I pass it
directly the Theme Context.Provider (<ThemeContext.Provider value={ThemeContext}> in place of <ThemeContext.Provider value={themeHook}>)?
Recall that your context value is ["light", () => {}]:
const ThemeContext = React.createContext(["light", () => {}]);
When you create the actual value in the provider const themeHook = React.useState("light"); it basically resolves to const [theme, setTheme] = React.useState("light");
You are passing the current state value and the state updater function as the context value to the provider.
return (
<ThemeContext.Provider value={[theme, setTheme]}>
...
</ThemeContext.Provider>
);
This is destructured in consumers as
const [theme, setTheme] = React.useContext(ThemeContext);
or in your case
const [contextType, toggleTheme] = React.useContext(ThemeContext);

Usage of State in functional component of React

How to use State in functional component of React ?
I have a component like below
import React, { useState } from 'react';
function App() {
const [count] = React.useState(0); //Set State here
function count_checkbox() {
var applicable = [];
const cbs = document.querySelectorAll('input[class*="checkbox_value"]');
cbs.forEach((cb) => {
if (cb.checked) {
applicable.push(parseInt(cb.value));
}
});
this.setState({ count: applicable.length }) //Update State here
}
return (
<div className="App">
<input type="submit" id="button" value="{ this.state.count }"/> //Use State here
</div>
);
}
export default App;
But State is not working here.
states should be like this
const [count,setCount] = React.useState(0); //setState
function count_checkbox() {
var applicable = [];
const cbs = document.querySelectorAll('input[class*="checkbox_value"]');
cbs.forEach((cb) => {
if (cb.checked) {
applicable.push(parseInt(cb.value));
}
});
setCount(applicable.length) //Update State here
}
setCount sets the state count.
Updating the state in a functional component is different from a class component. The useState hook returns an array consisting of the value and a setter.
You then call the setter function to update the value of the state.
const [count, setCount] = React.useState(0);
function count_checkbox() {
...
setCount(applicable.length)
}
this.setState is unsed to update state in class components. In order to update state in functional component you need to update it with appropriate function.
useState returns 2 values first is current state value and second one function to update that state
const [count, setCount] = React.useState(0);
Whenever you want to update count state you need to update with setCount function
function count_checkbox() {
...
setCount(applicable.length); //Update State here
}
You can access count in your useEffect like below:-
useEffect(() => {
alert(count);
}, [count])
A functional component is just a plain javascript function which takes props as an argument and returns a react element.
A stateless component has no state, it means that you can’t reach this.state and this.setState() inside it. It also has no lifecycle so you can’t use componentDidMount and other hooks.
import React, { useState } from 'react';
function App() {
const [count, setCount] = React.useState(0); //Set initial state here
function count_checkbox() {
var applicable = [];
const cbs = document.querySelectorAll('input[class*="checkbox_value"]');
cbs.forEach((cb) => {
if (cb.checked) {
applicable.push(parseInt(cb.value));
}
});
setCount(applicable.length) //Update State here
}
return (
<div className="App">
<input type="submit" value={`Apply tax to ${ count } item(s)`} /> //You Can't use this.state.count here
</div>
);
}
export default App;

React hooks component did update with prevProps

I was converting my react code from class to hooks
I previously had something like this
export default class Editor extends React.Component {
constructor(props) {
super(props)
this.state = this.getStateObject()
}
getStateObject() {
const { page } = this.props
return {
label: page.label || '',
session: page.session || false
}
}
componentDidUpdate(prevProps) {
if (!_.isEqual(this.props.projects, prevProps.projects)) {
this.setState(this.getStateObject())
}
}
When Trying to move to functional, I did something like this
export default function tabEditor ({page}:Page) {
const [appPageInfo, setAppPageInfo] = useState({
label: page.label || '',
session: page.session || false
})
/* Equivalence in hooks
componentDidUpdate(prevProps) {
if (!_.isEqual(this.props.projects, prevProps.projects)) {
this.setState(this.getStateObject())
}
}
*/
const handleOnClickUpdate = () => {
updateMobileAppPage(Object.assign({}, page, appPageInfo))
close()
}
but I am unable to determine the equivalence of componentDidUpdate in React hooks.
You could use the useEffect hook add pass it the projects props as dependency like this :
useEffect(() => {
// Whatever code you want to run if props.projects change
}, [props.projects]);
To mimic componentDidUpdate in Hooks, you make use of useEffect and then pass in the conditional parameter with which useEffect will run whenever the parameter changes. Example below
useEffect(() => {
.......
//This useEffect will re-run whenever the parameter in the square bracket updates/changes.
}, [parameter])

React child component does not re-render when props passed in from parent changes

I have a simplified react structure as below where I expect MyGrandChildComponent to re-render based on changes to the 'list' property of MyParentComponent. I can see the list take new value in MyParentComponent and MyChildComponent. However, it doesnt even hit the return function of MyGrandChildComponent. Am i missing something here?
const MyGrandChildComponent = (props) => {
return (
<div>props.list.listName</div>
);
};
const MyChildComponent = (props) => {
return (
<div><MyGrandChildComponent list={props.list}/></div>
);
}
const MyParentComponent = (props) => {
const list = { listName: 'MyList' };
return (
<div><MyChildComponent list={list} /></div>
);
}
In your MyParentComponent, the list is not a state variable and as such changing it will not even cause a re-render. If you absolutely want that when ever you change the value of list it re renders, then you will want to bring state to your functional component and the way to do that is to use hooks.
In this case your parent component will be something like below
import React, {useState} from 'react'
const MyParentComponent = (props) => {
const [list, setList] = useState({ listName: 'MyList' });
return (
<div><MyChildComponent list={list} /></div>
);
}
then at the child component you render it as I suggested in the comment above.
The parent needs to hold the list as a state variable and not just as a local variable. This is because react rerenders based on a state or prop change and at the parent you can only hold it in the state. With this when the value of list changes there will be a re-render which will then propergate the change to the children and grandchildren.
Also the only way of maintaining state in a functional component is to use hooks.
const MyGrandChildComponent = (props) => {
return (
<div>{props.list.listName}</div>
);
};
You forgot the {} around props.list.listName

Resources