Make child component re-render without sending useless props - reactjs

I'm changing state inside of the parent component:
this.setState({ langIndex: 0 });
The parent component re-renders correctly. But it's child do not re-render unless I pass the state from the parent as props to them:
<LoginForm langIndex={this.state.langIndex} />
Now langIndex is useless inside of LoginForm but I need stuff to re-render inside of it nonetheless. Is there any 'clean' way of doing so ?

As you mentioned in one of your comments that you are using i18next:
There are react bindings for i18next that enable re-rendering on language change if that is what you want. They work by subscribing to the i18next store and triggering a state change on language change.
You can use the useTranslation hook:
function MyComponent() {
const { t, i18n } = useTranslation();
// or const [t, i18n] = useTranslation();
return <p>{t('my translated text')}</p>
}
or the WithTranslation HOC:
function MyComponent({ t, i18n }) {
return <p>{t('my translated text')}</p>
}
export default withTranslation()(MyComponent);
Calling i18n.changeLanguage() will trigger a re-render.

If your problem is that you need to pass data from a Parent to a grand child (or several levels down the tree) and you don't want to pass it through all levels (usually called as prop drilling), then you can use react context to skip levels
Here is a running example:
const MyContext = React.createContext(null);
const Child = () => {
const value = React.useContext(MyContext);
return <div>{`in child... ${value}`}</div>;
};
const Parent = () => <Child />;
function App() {
const [value, setValue] = React.useState("");
return (
<MyContext.Provider value={value}>
<div>App</div>
<input value={value} onChange={e => setValue(e.target.value)} />
<Parent />
</MyContext.Provider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Related

why components state data are gone in conditional rendering

I am new to react and come from a background of functional component only.
In my react project,
When I conditionally rendering , ie from false to true, the data inside child component will be gone.
Then I wonder why is that.
Then I heard a concept called unmounting. It means, when my condition change from true to false, the component will get unmounting. And in unmounting, the state inside will gone.
But then, it doesn't add up.
Q: Whenever we re-render any other components, just like the normal situation, we will also unmount component in order to do re-rendering. And our state value would not be gone.
Why this problem was happened especially on having conditional statement in react?
Edit:
My emphsis is not on how to avoid state loss. My question is that why data will be gone in conditional rendering. And why unmounts will cause such problem, but re rendering would not cause such ( both also involves unmounting)
Here is my code
In parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 />
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
In child:
import React, { useEffect, useState } from "react";
function Child1() {
useEffect(() => {
console.log("useeffect in child");
console.log("newMessage: " + newMessage);
});
const [newMessage, setNewMessage] = useState("");
return (
<div>
<input onChange={(event) => setNewMessage(event.target.value)} />
</div>
);
}
export default Child1;
Add some picture to illurste what I mean by data lose in conidtional rendering
enter
https://i.stack.imgur.com/UrIhT.png
click to not show it
https://i.stack.imgur.com/0OC87.png
click to show again
https://i.stack.imgur.com/4zlWk.png
Try moving all state management to the parent component and leave the child component 'dumb'. You can pass the setMessage and any other state variables to the child as props.
Parent:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import Child1 from "./child";
import "./styles.css";
function Parent() {
const [message, setMessage] = useState("initial text");
const [showChild,setShowChild] = useState(true);
useEffect(() => {
console.log("useeffect in parent");
});
return (
<div className="App">
<button onClick={() => setShowChild(!showChild)}>show child</button>
{showChild?
<Child1 setMessage={setMessage}/>
:
null
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
Child:
import React from "react";
function Child1({setMessage}) {
return (
<div>
<input onChange={(event) => setMessage(event.target.value)} />
</div>
);
}
export default Child1;
The answer for your question is very simple, While unmounting you are removing the component itself from react-dom. The state, props and all the data's handled inside your component will be active only If the component is inside the react-dom. If the particular component is unmounted, all the states and props that was created and processed will also be removed from react-dom along with the component. And the fresh component, state and props will be mounted in the react-dom if you conditionally render the component again.

How to pass a reference created by react hook of parent component to child components?

My Code:
const MyComponent: React.FC = () => {
const ParentReference = useRef(null);
return(
<Parent className="d-flex flex-row" ref={ParentReference}>
<ChildComponent
className="mr-3"
target={ParentReference.current}
/>
<AnotherChild className="mr-3" />
</Nav>
)};
As seen in the code above, I have created a reference using useRef hook and attached it to my ParentComponent.
Now am passing to ChildComponent by means of target prop and using it to do some dom manipulation inside the child.
Issue :
I am getting ParentReference as null for the first render of the component. (If I force re-render on change of the ParentReference it will update and re-render whole component then it will have value.)
How to get the ParentReference inside my child Component for initial render itself?
segFault's reference to this answer is correct. Your ref it not initialized until after your component's first render. So when you render your <ChildComponent target={ParentReference.current} />, the ref is not yet defined.
In this scenario you might consider using a useEffect to set a state variable on first render, and conditionally rendering the <ChildComponent /> once that state variable is set.
const MyComponent: React.FC = () => {
const ParentReference = useRef(null);
// define state variable defining the ref as not yet ready
const [refReady, setRefReady] = useState(false)
// On first mount, set the variable to true, as the ref is now available
useEffect( () => {
setRefReady(true)
}, [])
return(
<Parent className="d-flex flex-row" ref={ParentReference}>
{refReady && <ChildComponent
className="mr-3"
target={ParentReference.current}
/>}
<AnotherChild className="mr-3" />
</Nav>
)};

Is there a way to force multiple Context Consumers to share state?

I'm using the React Context API with the main intent of avoiding prop drilling. Right now my Context includes a useState and various functions that update the state - these are put into a const object that is passed as the value prop of ActionsContext.Provider. This is an abstraction of my current component hierarchy:
Header
---NavPanel
ContentContainer
---Content (Context.Consumer being returned in this component)
where Header and ContentContainer are sibling elements and NavPanel and ContentContainer are their respective children.
I initially put the Context.Consumer in Content because the other elements did not need it. However I'm building a feature now where NavPanel needs to know about the state that's managed by the Context. So I put another Consumer in NavPanel, only to find that a separate Consumer means a separate instance of the state.
Is there any smart workaround that gives NavPanel and Content access to the same state, that doesn't involve putting the Consumer in the parent component of Header and Content? That would result in a lot of prop drilling with the way my app is currently structured.
Codesandbox example of multiple instances: https://codesandbox.io/s/context-multiple-consumers-v2wte
Several things:
You should have only one provider for every state you want to share.
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
It is better to split your context in several contexts so you pass values instead of objects. This way when you update your state React will detect it is different instead of comparing the same object.
Your input should be a controlled component https://reactjs.org/docs/forms.html
Consider using the useContext API for better ergonomics if you are using React 16.8 instead of ContextConsumer.
With these changes, your code would be:
MyContext.js
import React, { useState } from "react";
export const MyItemContext = React.createContext();
export const MySetItemContext = React.createContext();
export const MyHandleKeyContext = React.createContext();
const ContextProvider = props => {
const [itemBeingEdited, setItemBeingEdited] = useState("");
const handleKey = event => {
if (event.key === "Enter") {
setItemBeingEdited("skittles");
} else if (event.key === "K") {
setItemBeingEdited("kilimanjaro");
} else {
setItemBeingEdited("");
}
};
const editFunctions = {
itemBeingEdited,
setItemBeingEdited,
handleKey
};
return (
<MyItemContext.Provider value={itemBeingEdited}>
<MyHandleKeyContext.Provider value={handleKey}>
<MySetItemContext.Provider value={setItemBeingEdited}>
{props.children}
</MySetItemContext.Provider>
</MyHandleKeyContext.Provider>
</MyItemContext.Provider>
);
};
export default ContextProvider;
PartOne.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartOne = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
placeholder="Type in me"
onKeyDown={handleKey}
value={itemBeingEdited}
onChange={e => setItem(e.target.value)}
/>
</React.Fragment>
);
};
export default PartOne;
PartTwo.js
import React, { useContext } from "react";
import ContextProvider, {
MyContext,
MyItemContext,
MySetItemContext,
MyHandleKeyContext
} from "./MyContext";
const PartTwo = () => {
// blah
const itemBeingEdited = useContext(MyItemContext);
const handleKey = useContext(MyHandleKeyContext);
const setItem = useContext(MySetItemContext);
return (
<React.Fragment>
<span>{itemBeingEdited}</span>
<input
value={itemBeingEdited}
type="text"
placeholder="Type in me"
onChange={e => setItem(e.target.value)}
onKeyDown={handleKey}
/>
</React.Fragment>
);
};
export default PartTwo;
index.js
import React from "react";
import ReactDOM from "react-dom";
import PartOne from "./PartOne";
import PartTwo from "./PartTwo";
import ContextProvider from "./MyContext";
import "./styles.css";
function App() {
return (
<div className="App">
<ContextProvider>
<PartOne />
<hr />
<PartTwo />
</ContextProvider>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CodeSandbox: https://codesandbox.io/s/context-multiple-consumers-vb9oj?fontsize=14

Ist there a way to pass data from child to parent using Hooks?

As an absolute newbie to React, I'd like to pass data from a child to parent component. But if I look for this question, I always find the "old" way using "state" and "callback" functions. See for example this article: https://medium.com/#ruthmpardee/passing-data-between-react-components-103ad82ebd17
Since all the guides use the traditional way, I'm very confused because they look so different from what I know. For example, instead of using "this.state" in the constructor, I use the useState() Hook.
Is there any way or maybe Hook I don't see that makes it possible to pass data from a child to a parent component?
Imagine that you have a parent component App that has the logic for handling the submit (could be any other kind of logic) of a form that is a child component of App.
The ChildForm has a local state to store its inputValue.
When you click the submit function, it will call a function onSubmit from the parent App and it will pass along its inputValue (you can pass any other value that it's present inside the component) to be processed and submitted, in this example.
So the gist of it, is:
Send a function from the parent to the child as props
The child will call that function sending some data as parameters
That function will handle the data and can trigger some action from the parent, for example
See snippet below:
function App() {
function onSubmit(formState) {
console.log('I will submit my ChildForm Input State: ' + formState);
}
return(
<ChildForm
onSubmit={onSubmit}
/>
);
}
function ChildForm(props) {
const [inputValue,setInputValue] = React.useState('');
function onChange() {
setInputValue(event.target.value);
}
return(
<React.Fragment>
<div>I am ChildForm</div>
<input type='text' value={inputValue} onChange={onChange}/>
<button onClick={()=>props.onSubmit(inputValue)}>Click to Submit through parent App</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
There is no difference in flow between Functional and Class Component. You can update the value of the parent by passing the function with props and using it at the child.
parent.js
import React, {useState} from 'react';
const Parent = () => {
const [counter, setCounter] = useState(0);
return (
<Child updateCounter={setCounter}>
</Child>
)
}
child.js
const Child = (props) => {
const {updateCounter} = props;
return (
<button onClick={() => updateCounter(some value)}>
</button>
)
}
You will need to pass functions as props from the parent component to the child in order to pass data up the tree
function Child({ num, onNumClick }) {
return <span onClick={() => onNumClick(num)}>{num}</span>;
}
function Parent() {
const [numList] = useState([1, 2, 3, 4, 5]);
const [clickedItem, setClickedItem] = useState(null);
const onNumClick = num => setClickedItem(num);
return (
<div className="App">
{numList.map(n => (
<Child num={n} onNumClick={onNumClick} />
))}
{clickedItem && <p>You clicked on {clickedItem}</p>}
</div>
);
}
sandbox

Is it wrong export React Hooks to manage global state?

I am exporting the return of a Hook which I use in the root component of a project. Then it becomes very easy for the other components to import globalState and setGlobalState ().
I did several tests here and it worked very well. The problem is that I have not seen anyone in the community using it in the same way.
import React, { useState } from "react";
import Level2 from "./components/Level2";
export let setGlobalState = () => {};
export let globalState = {};
const initalState = { counter: 0 };
const App = () => {
[globalState, setGlobalState] = useState(initalState);
return (
<>
<Level2 />
</>
);
};
export default App;
Is it wrong to manage global state in that way? If it is, why?
Here I have a repository with the whole project:
https://github.com/andregardi/global-state-with-hooks
This approach has some issues with updates. Only children of the App component can react to global state changes. Those children still might not get rerendered if something in the tree blocks an update (PureComponent, React.memo, etc.)
Also setGlobalState might get redefined by some module.
Check this example to observe the issue. All components will update the global state but the "broken" one will not react to updates because its props don't change.
It is not very correct to define states globally then mutating them because multiple instances of same component might need to have their own state and not share it. If you define the state globally all of them will share the same state and it will lead to inconsistencies
DEMO
const { useState } = React;
let initialState = 0;
let globalState;
let setGlobalState;
function Counter () {
[globalState, setGlobalState] = useState(initialState);
return (
<div>
<div>Count: {globalState}</div>
<button onClick={() => setGlobalState(count => count + 1)}>Increment</button>
</div>
)
}
function App() {
return (
<div>
<div>
<div>Counter 1: </div>
<Counter />
</div>
<div>
<div>Counter 2: </div>
<Counter />
</div>
</div>
)
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

Resources