UseEffect but Only Update Certain Child in React Componet - reactjs

Let's say I have a complex App component with several children. I'd like to update some of these children (ShouldUpdate) when my variable localStorageLayouts changes, so naturally I reach for useEffect(), and add localStorageLayouts as a dependency. The problem is that useEffect causes all children to update, and I only need my ShouldUpdate component to re-render. What are some work arounds to only render the components I need updated?
import React, { useEffect, useState } from "react";
import { useStoreState, useStoreActions } from "./hooks";
export default function App() {
const localStorageLayouts = useStoreState(
(state) => state.appData.localStorageLayouts
);
const [availableLayouts, setAvailableLayouts] = useState(localStorageLayouts);
useEffect(() => {
setAvailableLayouts(localStorageLayouts);
}, [localStorageLayouts]);
return (
//...
<div>
<VeryExpensiveToRender/>
<ShouldUpdate layouts = {availableLayouts}/>
<OkNotToUpdate/>
<ShouldUpdate layouts = {availableLayouts}/>
<OkNotToUpdate/>
</div>
)
}

Related

React - child component re-renders even if it doesn't rely on the parent state

console.log('Render test') in my Test component runs twice.
The tests state is not used, I just put it there for an example. The Test component doesn't rely on the tests state but it still renders twice. Why?
index.js:
import React from "react";
import ReactDOM from "react-dom";
import AppTest from "./AppTest";
ReactDOM.render(<><AppTest /></>, document.getElementById("container"));
AppTest.js:
import Test from "./Test";
import React, {useState, useEffect} from "react";
function AppTest() {
const [tests, setTests] = useState([]);
useEffect(() => {
setTests(['test1', 'test2']);
}, []);
return (
<>
<Test />
</>
);
}
export default AppTest;
Test.js:
import React, {useEffect} from "react";
function Test() {
useEffect(() => {
console.log("Render Test");
});
return (
<h1>Test</h1>
);
}
export default Test;
You can use React.memo to prevent rerender unless props change - this has more info - How to stop re render child component when any state changed in react js?
The component Test is rendered twice because of the behaviour of the AppTest component. The useEffect function in AppTest modify the state of your component with setTests(['test1', 'test2']); so the AppTest component is re-rendered.
As previously said, you can use memoization to avoid re-render Test component:
import React, {useEffect} from "react";
const Test= React.memo(()=> {
useEffect(() => {
console.log("Render Test");
});
return (
<h1>Test</h1>
);
}
export default Test;
The useEffect runs twice because it's missing a dependency array. Add an empty array to indicate it has no dependencies, and then you will not see multiple log statements:
function Test() {
useEffect(() => {
console.log("Render Test");
}, []);
// ...
}

React: how to fire useeffect using context variable as dependency?

I was trying to figure out how to fire a useEffect when its dependency is a useContext variable. My Context has an "update" variable, which at one point changes, but the effect isn't fired.
import { useEffect, useState, useContext } from "react"
import context from "./Context"
const Layout = () => {
const ctx = useContext(context)
const [updateState, setUpdateState] = useState(ctx.update)
useEffect(() => {
console.log("effect fired")
}, [updateState])
return (
<div>
</div>
)
}
export default Layout
I tested whether the issue was my context "update" variable not changing, but it does. I will appreciate any help with this.
Your problem is that you used useState. This in effect memoized/froze the value of updateState, to the first run of this component.
You see useState takes a single arg (default value). Which is only used on the first render of the component, to populate updateState. When ctx.update changes, useState already has a default to set updateState to, so it is ignored.
Instead, you can remove useState completely...
import { useEffect, useState, useContext } from "react"
import context from "./Context"
const Layout = () => {
const { update as updateState } = useContext(context);
// Notice I have removed the useState.
useEffect(() => {
console.log("effect fired")
}, [updateState])
return (
<div>
</div>
)
}
export default Layout

React.js - passing functions between components using Context API - not working

I am trying to pass a function between two components but even though I do not have any errors, the function that I am passing wont show or to be precise it is not working. I have two files and one of them is creating a context while the other is using it (obviously). Now, they are not shown in App.js (which is rendered in index.js, usual stuff) they are in the seperate folder. I am using React Router to show one the pages (News.js).
Here are the files:
NewsContext.js
import React, { useContext, createContext, useState, useEffect } from "react";
export const newsK = React.createContext();
export const NewsContext = (props) => {
const working = () => {
console.log("it is working");
};
return <newsK.Provider value={working}>{props.children}</newsK.Provider>;
};
export default NewsContext;
News.js
import React, { useContext, useState, useEffect } from "react";
import { newsK } from "./NewsContext";
import { NewsContext } from "./NewsContext";
const News = () => {
const data = useContext(newsK);
return (
<NewsContext>
<div>
<button onClick={data}></button>
</div>
</NewsContext>
);
};
export default News;
When i pressed the button, it wont do anything. Any tips?
You're trying to use context outside the NewsContext component
The solution for this will be to create a component that will call useContext inside NewsContext.
I.e.
const WrappedButton = () => {
const data = useContext(newsK)
return <button onClick={data} />
}
And then put it inside the NewsContext:
<NewsContext>
<div>
<WrappedButton />
</div>
</NewsContext>

React Hooks, how to access mounted parentNode with ReactDom

I am currently migrating my react components to react hooks, but struggle with one specific case which is accessing the mounted DOM element.
My component using React.class structure :
import { Component } from "react";
import ReactDOM from "react-dom";
class LineGraph extends Component {
componentDidMount() {
this.element = ReactDOM.findDOMNode(this).parentNode;
}
render() {
return "";
}
}
Now using react hooks, ReactDOM.findDOMNode(this) throw the following error :
TypeError: react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.findDOMNode(...) is null
Looking at ReactDOM#findDOMNode documentation, I tried to use a references on a returned empty div but after few draw element variable become null.
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
export default function LineGraph () {
let myRef = React.createRef();
useEffect(() => {
element = ReactDOM.findDOMNode(myRef.current).parentNode;
}, []);
return (
<div ref={myRef}></div>
);
}
I am looking a clean way to access the DOM element in which my react code is injected/mounted.
Just for context, my goal is to inject svg content using d3 library, my code works well with components.
The problem is the timing of accessing the reference.
Try using useRef hook with a listener to myRef change, and read it when it has a valid value:
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
export default function LineGraph() {
const myRef = useRef();
useEffect(() => {
if (myRef.current) {
console.log(myRef.current.parentNode)
}
}, [])
return <div ref={myRef}></div>;
}
There are some minor issues with your code, you can make it work and simplify it at the same time.
Remove the findDomNode method
It's not required as you are already accessing a node through the ref so you can use the .parentNode property directly on the referenced DOM node.
Update Parent Node on each re-render
By removing the empty array from your useEffect method the component will access the parent node each time the parent Node updates. Thereby you always have access to the freshly updated parent node.
https://stackblitz.com/edit/react-qyydtt
export default function LineGraph () {
const parentRef = React.createRef();
let parent;
useEffect(() => {
parent = parentRef.current.parentNode
});
return (
<div ref={parentRef}></div>
);
}

Can I replace context with hooks?

Is there a way with new react hooks API to replace a context data fetch?
If you need to load user profile and use it almost everywhere, first you create context and export it:
export const ProfileContext = React.createContext()
Then you import in top component, load data and use provider, like this:
import { ProfileContext } from 'src/shared/ProfileContext'
<ProfileContext.Provider
value={{ profile: profile, reloadProfile: reloadProfile }}
>
<Site />
</ProfileContext.Provider>
Then in some other components you import profile data like this:
import { ProfileContext } from 'src/shared/ProfileContext'
const context = useContext(profile);
But there is a way to export some function with hooks that will have state and share profile with any component that want to get data?
React provides a useContext hook to make use of Context, which has a signature like
const context = useContext(Context);
useContext accepts a context object (the value returned from
React.createContext) and returns the current context value, as given
by the nearest context provider for the given context.
When the provider updates, this Hook will trigger a rerender with the
latest context value.
You can make use of it in your component like
import { ProfileContext } from 'src/shared/ProfileContext'
const Site = () => {
const context = useContext(ProfileContext);
// make use of context values here
}
However if you want to make use of the same context in every component and don't want to import the ProfileContext everywhere you could simply write a custom hook like
import { ProfileContext } from 'src/shared/ProfileContext'
const useProfileContext = () => {
const context = useContext(ProfileContext);
return context;
}
and use it in the components like
const Site = () => {
const context = useProfileContext();
}
However as far a creating a hook which shares data among different component is concerned, Hooks have an instance of the data for them self and don'tshare it unless you make use of Context;
updated:
My previous answer was - You can use custom-hooks with useState for that purpose, but it was wrong because of this fact:
Do two components using the same Hook share state? No. Custom Hooks are a mechanism to reuse stateful logic (such as setting up a subscription and remembering the current value), but every time you use a custom Hook, all state and effects inside of it are fully isolated.
The right answer how to do it with useContext() provided #ShubhamKhatri
Now i use it like this.
Contexts.js - all context export from one place
export { ClickEventContextProvider,ClickEventContext} from '../contexts/ClickEventContext'
export { PopupContextProvider, PopupContext } from '../contexts/PopupContext'
export { ThemeContextProvider, ThemeContext } from '../contexts/ThemeContext'
export { ProfileContextProvider, ProfileContext } from '../contexts/ProfileContext'
export { WindowSizeContextProvider, WindowSizeContext } from '../contexts/WindowSizeContext'
ClickEventContext.js - one of context examples:
import React, { useState, useEffect } from 'react'
export const ClickEventContext = React.createContext(null)
export const ClickEventContextProvider = props => {
const [clickEvent, clickEventSet] = useState(false)
const handleClick = e => clickEventSet(e)
useEffect(() => {
window.addEventListener('click', handleClick)
return () => {
window.removeEventListener('click', handleClick)
}
}, [])
return (
<ClickEventContext.Provider value={{ clickEvent }}>
{props.children}
</ClickEventContext.Provider>
)
}
import and use:
import React, { useContext, useEffect } from 'react'
import { ClickEventContext } from 'shared/Contexts'
export function Modal({ show, children }) {
const { clickEvent } = useContext(ClickEventContext)
useEffect(() => {
console.log(clickEvent.target)
}, [clickEvent])
return <DivModal show={show}>{children}</DivModal>
}

Resources