I'm pretty new to coding so I hope my question won't sound too ridiculous. I've spent days to figure something out but didn't find anything.
So I have a react page with the main component that is my "planning container".
In this component, I have a child component that represents a whole month. it's a table with a line for each people and my team, and a td that represents each day of the month.
This child is stored in a variable array that is stored in the state.
When I click on a button, I create another child (for the next month), that I will push into the array stored in the state.
I have a variable called "refresh" that just setRefresh(!refresh). This variable is passed to the childs components and I put this variable in the useEffect [] that will trigger a re-render.
=> The problem is that it doesn't re-render at all.
So, just for you to know, I really need to be able to "append" a whole month at each click on the button. I really need this "view" to be able to work.
I will paste some code to make you understand the main idea.
here is the parent :
import React, {useEffect, useState} from "react";
import PlanningMonthBuild from "./PlanningMonthBuild";
import './Planning.css';
import "react-datepicker/dist/react-datepicker.css";
const PlanningBuild = () => {
const [startForNextMonth, setStartForNextMonth] = useState(new Date());
const [inputList, setInputList] = useState([]);
const [refresh, setRefresh] = useState(false);
useEffect(() => {
initPlanning();
}, []);
const handleRefresh = () => {
setRefresh(!refresh);
}
const initPlanning = () => {
let date = new Date(startForNextMonth.getFullYear(), startForNextMonth.getMonth(), 1);
let newPlanning = [...inputList];
newPlanning.splice(0, 0,
<PlanningMonthBuild
key={'current' + new Date().toISOString()}
startDate={date}
refresh={refresh}
/>
);
let date2 = new Date(startForNextMonth.getFullYear(), startForNextMonth.getMonth() + 1, 1);
date2.setMonth(date.getMonth() + 1);
setStartForNextMonth(date2);
setInputList(newPlanning);
};
const addOneMonthNext = () => {
setInputList(inputList.concat(
<PlanningMonthBuild
key={new Date().toISOString()}
startDate={startForNextMonth}
refresh={refresh}
/>
));
let date = startForNextMonth;
date.setDate(1);
date.setMonth(date.getMonth() + 1);
setStartForNextMonth(date);
};
return (
<main>
<div className="refresh-div" onClick={handleRefresh}><i className="fa fa-refresh" aria-hidden="true"></i></div>
<button className="myButton" onClick={addOneMonthNext}>Add Next Month</button>
<div id="insideDiv">
{inputList}
</div>
</main>
);
};
export default PlanningBuild;
And here is the child (that build each month) :
import React, {useEffect, useState} from "react";
import useAxiosPrivate from "../hooks/useAxiosPrivate";
import {getFirstDayOfMonth} from "../utils/dates";
const PlanningMonthBuild = ({ startDate, refresh }) => {
const [dateDebut] = useState(startDate);
const [jsListe, setJsListe] = useState([]);
const axiosPrivate = useAxiosPrivate();
useEffect(() => {
let isMounted = true;
const controller = new AbortController();
try {
axiosPrivate.get('api/planning/team?start=' + getFirstDayOfMonth(dateDebut.getFullYear(), dateDebut.getMonth()), {
signal: controller.signal
}).then(response => {
if (isMounted) {
const newListeJS = response.data;
setJsListe(newListeJS);
}
});
} catch (err) {}
return () => {
isMounted = false;
controller.abort();
}
}, [refresh])
return (jsListe.map((day)=> {
///.... building a table(tr,td...)
}))
}
export default PlanningMonthBuild;
So, when I put my child component directly into the parent return, it works when I click on refresh button (if my workmate updates something for example...), it will update my table but if I store my child component in the array in "inputList" state it doesn't...
I hope you can understand what I mean.
Thanks in advance for any help.
Related
I'm working on a new major release for react-xarrows, and I came up with some messy situation.
It's not going to be simple to explain, so let's start with visualization:
consider the next example - 2 draggable boxes with an arrow drawn between them, and a wrapping context around them.
focused code:
<Xwrapper>
<DraggableBox box={box} />
<DraggableBox box={box2} />
<Xarrow start={'box1'} end={'box2'} {...xarrowProps} />
</Xwrapper>
Xwrapper is the context, DraggableBox and Xarrow are, well, you can guess.
My goal
I want to trigger a render on the arrow, and solely on the arrow, whenever one of the connected boxes renders.
My approach
I want to be able to rerender the arrow from the boxes, so I have to consume 'rerender arrow'(let's call it updateXarrow) function on the boxes, we can use a context and a useContext hook on the boxes to get this function.
I will call XelemContext to the boxes context.
also, I need to consume useContext on Xarrow because I want to cause a render on the arrow whenever I decide.
this must be 2 different contexts(so I could render xarrow solely). one on the boxes to consume 'updateXarrow', and a different context consumed on Xarrow to trigger the reredner.
so how can I pass this function from one context to another? well, I can't without making an infinite loop(or maybe I can but could not figure it out), so I used a local top-level object called updateRef.
// define a global object
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
// define updateXarrow here
...
// assign to updateRef.func
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
//now updateRef.func is defined because XelemProvider defined later
const XelemProvider = ({ children }) => {
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
the thing is, that this object is not managed by react, and also, i will need to handle cases where there is multiple instances of Xwrapper, and I'm leaving the realm of React, so i have 2 main questions:
there is a better approach? maybe I can someone achieve my goal without going crazy?
if there is no better option, is this dangerous? I don't want to release a code that will break on edge cases on my lib consumer's apps.
Code
DraggableBox
const DraggableBox = ({ box }) => {
console.log('DraggableBox render', box.id);
const handleDrag = () => {
console.log('onDrag');
updateXarrow();
};
const updateXarrow = useXarrow();
return (
<Draggable onDrag={handleDrag} onStop={handleDrag}>
<div id={box.id} style={{ ...boxStyle, position: 'absolute', left: box.x, top: box.y }}>
{box.id}
</div>
</Draggable>
);
};
useXarrow
import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { XelemContext } from './Xwrapper';
const useXarrow = () => {
const [, setRender] = useState({});
const reRender = () => setRender({});
const updateXarrow = useContext(XelemContext);
useLayoutEffect(() => {
updateXarrow();
});
return reRender;
};
export default useXarrow;
Xwrapper
import React, { useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
const updateRef = { func: null };
const XarrowProvider = ({ children }) => {
console.log('XarrowProvider');
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
updateRef.func = updateXarrow;
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
const XelemProvider = ({ children }) => {
console.log('XelemProvider');
return <XelemContext.Provider value={updateRef.func}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('Xwrapper');
return (
<XarrowProvider>
<XelemProvider>{children}</XelemProvider>
</XarrowProvider>
);
};
export default Xwrapper;
const Xarrow: React.FC<xarrowPropsType> = (props: xarrowPropsType) => {
useContext(XarrowContext);
const svgRef = useRef(null);
....(more 1100 lines of code)
logs
I left some logs.
on drag event of a single box you will get:
onDrag
DraggableBox render box2
XarrowProvider
xarrow
Note
currently, this is working as expected.
Update
after many hours of testing, this seems to work perfectly fine. I manage my own object that remember the update function for each Xwrapper instance, and this breaks the dependency between the 2 contexts. I will leave this post in case someone else will also come across this issue.
Update (bad one)
this architecture breaks on react-trees with <React.StrictMode>...</React.StrictMode> :cry:
any idea why? any other ideas ?
just in case someone would need something similar: here's a version that will work even with react strictmode(basically being rellyed of effect which called once and not renders):
import React, { FC, useEffect, useRef, useState } from 'react';
export const XelemContext = React.createContext(null as () => void);
export const XarrowContext = React.createContext(null as () => void);
// will hold a object of ids:references to updateXarrow functions of different Xwrapper instances over time
const updateRef = {};
let updateRefCount = 0;
const XarrowProvider: FC<{ instanceCount: React.MutableRefObject<number> }> = ({ children, instanceCount }) => {
const [, setRender] = useState({});
const updateXarrow = () => setRender({});
useEffect(() => {
instanceCount.current = updateRefCount; // so this instance would know what is id
updateRef[instanceCount.current] = updateXarrow;
}, []);
// log('XarrowProvider', updateRefCount);
return <XarrowContext.Provider value={updateXarrow}>{children}</XarrowContext.Provider>;
};
// renders only once and should always provide the right update function
const XelemProvider = ({ children, instanceCount }) => {
return <XelemContext.Provider value={updateRef[instanceCount.current]}>{children}</XelemContext.Provider>;
};
const Xwrapper = ({ children }) => {
console.log('wrapper here!');
const instanceCount = useRef(updateRefCount);
const [, setRender] = useState({});
useEffect(() => {
updateRefCount++;
setRender({});
return () => {
delete updateRef[instanceCount.current];
};
}, []);
return (
<XelemProvider instanceCount={instanceCount}>
<XarrowProvider instanceCount={instanceCount}>{children}</XarrowProvider>
</XelemProvider>
);
};
export default Xwrapper;
Sorry if the title is a bit confusing. Basically I want to play a sound if a user clicks a key, and if they click again it will play another sound.
The setup could look like.
import useSound from 'use-sound';
const Home = () => {
const soundList = [assets.sounds.click1,assets.sounds.click2]
const [play] = useSound(soundList);// this seems to take one argument.
function onUserInputChange(e){
play(// random choice from soundList)
}
}
How might I be able to pass and argument into play to play audio?
You can pass from the parent url of the sound to the child and modify parent's state after click:
import useSound from 'use-sound';
import { useState } from 'react';
const soundList = [assets.sounds.click1, assets.sounds.click2];
const Home = () => {
const [soundToPlay, setSoundToPlay] = useState(soundList[0]);
const onPlay = () => {
setSoundToPlay(soundList[Math.round(Math.random() * soundList.length)]);
};
return <PlayComponent soundUrl={soundToPlay} onPlay={onPlay} />;
};
const PlayComponent = ({ soundUrl, onPlay }) => {
const [play] = useSound(soundUrl);
function onUserInputChange(e) {
play();
onPlay();
}
return <button onClick={onUserInputChange}>Play</button>;
};
*NOTE
I guess you wanted to put assets.sounds.click2 instead of assets.sounds.click1 as a second array item
I'm new in coding and i couldn't get how to fix the issue after i googled many times. The issue is i have a layout component which contains 4 different components. When i call a function in a function component it affects the others and the others re-render. But i don't pass the new props to them. I only pass props to one component which contains click events. I hope I made myself clear , thanks in advance. So here are my code samples :
This is my layout component.
import React, { useState } from "react";
import Header from "./Header";
import MenuTitle from "./MenuTitle";
import MenuList from "./MenuList";
import Cart from "./Cart";
import Footer from "./Footer";
function Layout({
cartData,
menuList,
menuTitles,
callMenuList,
addToCart,
title,
removeFromCart,
currency,
}) {
const [isCartOpened, setIsCartOpened] = useState("closed");
const openCart = () => {
if (isCartOpened == "closed") {
setIsCartOpened("opened");
} else {
setIsCartOpened("closed");
}
};
const closeCart = () => {
setIsCartOpened("closed");
};
return (
<div>
<Header openCart={() => openCart()} cartData={cartData} />
<MenuTitle
menuTitles={menuTitles}
callMenuList={(titleProp) => callMenuList(titleProp)}
/>
<MenuList
title={title}
menuList={menuList}
addToCart={(data) => addToCart(data)}
/>
<Cart
currency={currency}
cartData={cartData}
removeFromCart={(itemId) => removeFromCart(itemId)}
isCartOpened={isCartOpened}
closeCart={() => closeCart()}
/>
<Footer />
</div>
);
}
export default Layout;
And this is my App component
import React, { useState, useEffect } from "react";
import Layout from "./Components/Layout";
function App() {
const [data, setData] = useState([]);
const [menuTitle, setMenuTitle] = useState([]);
const [title, setTitle] = useState("");
const [currency, setCurrency] = useState("");
const [menuList, setMenuList] = useState([]);
const [cart, setCart] = useState([]);
const API = "./db.json";
const callMenuList = React.useCallback((titleProp) => {
setTitle(titleProp);
const filterMenuList = data.filter((title) => title.TYPE == titleProp);
setMenuList(filterMenuList);
});
const addToCart = React.useCallback((data) => {
setCart([...cart, data]);
});
const removeFromCart = React.useCallback((itemId) => {
const cartItems = cart;
cartItems.map((item) => {
if (item.CODE == itemId) {
const filtered = cartItems.filter(
(cartItem) => cartItem.CODE != itemId
);
setCart(filtered);
}
});
});
useEffect(() => {
const titles = [];
const fetchData = async () => {
const response = await fetch(API);
const responseData = await response.json();
setData(responseData);
console.log(responseData);
// Filtering menu types
responseData.map((item) => titles.push(item.TYPE));
const filtered = titles.filter(
(item, index, self) => self.indexOf(item) == index
);
setMenuTitle(filtered);
const initialMenuList = responseData.filter(
(item) => item.TYPE == filtered[0]
);
setTitle(initialMenuList[0].TYPE);
setCurrency(initialMenuList[0].CURRENCY);
setMenuList(initialMenuList);
};
fetchData();
}, []);
return (
<Layout
menuTitles={menuTitle}
menuList={menuList}
data={data}
callMenuList={(titleProp) => callMenuList(titleProp)}
addToCart={(data) => addToCart(data)}
removeFromCart={(itemId) => removeFromCart(itemId)}
cartData={cart}
title={title}
currency={currency}
/>
);
}
export default React.memo(App);
I have to add this as an answer even though it's more of a comment because so many people become overzealous about preventing renders when it doesn't matter.
React is very fast out of the box - it is supposed to be re-rendering components when props don't change. But, just to illustrate, you can design your components (using children) so that not everything re-renders all the time.
Compare these two stackblitz:
with children - C2 does NOT rerender
without children - C2 does rerender
But none of this actually matters, you should only look at preventing unnecessary renders if you see performance issues.
If you see logical issues that are fixed by preventing a re-render, then you've got a bug that you need to fix somewhere else.
If you aren't experiencing any performance or logic issues, then the answer to your question is to stop worrying about it.
You can use React.memo, but memoizing a component could easily end up being a performance penalty, rather than a win. Memoizing something isn't free.
I urge you to forget about this stuff unless you are seeing performance or logical errors.
Stop worrying, everything is functioning normally when your components re-render without props/state changes if their parents have re-rendered
If you set a new state in your layout component, it will re-run and re-render all the components in its JSX.
Don't worry, it is not the problem of React.
If you want your Header, Menu, Cart, Footer not to be re-render, read about React.PureComponent (for class), React.memo, or useMemo, useCallback (for funtional component).
I'm using React with hooks, I'm trying to create a custom hook for interaction Observer
For this feature here: Infinite Scroll in react
Since I want for it to be reused multiple times, I want to use it for posts, commments etc
Here is whta I got so far:
useObserver hook:
import React, { useState, useEffect } from 'react';
const useObserver = ({ element, callback }) => {
const [page, setPage] = useState(1);
console.log('props');
console.log(page);
const options = {
root: null,
rootMargin: '0px',
threshold: 1.0
};
const observerHandler = (entities) => {
console.log('handle observer');
const y = entities[0].boundingClientRect.y;
const target = entities[0];
if (target.isIntersecting) {
setPage((counter) => counter + 1);
}
};
useEffect(() => {
const observer = new IntersectionObserver(observerHandler, options);
if (element.current) {
observer.observe(element.current);
}
});
return [1];
};
export default useObserver;
Parent Component where I use hook:
import React, { useRef, useState, useEffect } from 'react';
import useObserver from './useObserver';
const Posts = ({ posts }) => {
// initiate posts loader
const loader = useRef(null);
const [page] = useObserver({ element: loader });
return (
<div id="post-list">
<h1> Post list </h1>
<div class="test" ></div>
<h1>Show posts</h1>
<div className="loading" ref={loader}>
<h1>Loader</h1>
</div>
</div>
);
};
The problem that I'm having is that state page inside of useObserver component get increment always and gets called muliple time continuously, but it should be called only once when user scrolls till that component
try keeping an array with element in useEffect
useEffect(() => {
const observer = new IntersectionObserver(observerHandler, options);
if (element.current) {
observer.observe(element.current);
}
},[element]); //when you specify an empty array it runs only once, an array with value will run when ever the value changes
import React, { useEffect, useState } from 'react';
import { Card } from 'components/Card';
import { dateFilter } from 'helpers';
import Chart from 'chart.js';
import 'chartjs-chart-matrix';
import chroma from 'chroma-js';
import moment from 'moment';
const WeeklyTrafficCard = (props) => {
const { start, end, data, store } = props;
const capacity = store && store.capacity;
var numberOfweeks = 0; //representing how many weeks back
const dateArray = [];
var today = moment();
while (numberOfweeks < 10) {
var from_date = today.startOf('week').format('MM/DD/YY');
var to_date = today.endOf('week').format('MM/DD/YY');
var range = from_date.concat(' ','-',' ',to_date);
dateArray.push(range);
today = today.subtract(7, 'days');
numberOfweeks++;
//console.log(dateArray);
}
const [each_daterange, setDateRange] = useState();
I have this Component called WeeklyTrafficCard and I want to use the variable, each_daterange, in another component, which imported WeeklyTrafficCard as below to send the get request, clearly I cannot use each_daterange directly right here, how I can work around it?
import React, { useContext, useEffect, useState } from 'react';
import { WeeklyTrafficCard } from './WeeklyTrafficCard';
import { AppContext } from 'contexts/App';
import { API_URL } from 'constants/index.js';
import { todayOpen, todayClose } from 'helpers';
import moment from 'moment';
const WeeklyTrafficCardContainer = (props) => {
const { API } = useContext(AppContext);
const { store = {} } = props;
const [data, setData] = useState([]);
const open = todayOpen(store.hours, store.timezone);
const close = todayClose(store.hours, store.timezone);
useEffect(() => {
async function fetchData() {
const result = await API.get(`${API_URL}/api/aggregates`, {
params: {
each_daterange,
every: '1h',
hourStart: 13,
hourStop: 4
},
});
You should use a useEffect(prop drilling) to pass your variable in your parent:
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [myVar, setMyVar] = React.useState('');
return (
<div>
<Child setMyVar={setMyVar} />
{myVar}
</div>
);
};
const Child = ({setMyVar}) => {
const myChildVar = "Hello world !"
React.useEffect( () => setMyVar(myChildVar),[]);
return <div> This is the child</div>
}
render(<App />, document.getElementById("root"));
Here is the repro on stackblitz
Understanding of the Problem
You want to pass data up to the parent from the child.
Manage each_daterange in the parent:
Instead of creating your useState variable each_daterange in the child you can declare it in the parent and pass down it's setter function. For instance:
const WeeklyTrafficCardContainer = (props) => {
const [eachDateRange, setEachDateRange] = useState();
return (
<div>
{/* your return */}
<WeeklyTrafficCard setEachDateRange={setEachDateRange} />
</div>
)
}
If you need to display eachDateRange in the traffic card, or the traffic card needs to completely own that variable, you can create another state variable in the parent and pass a callback to the child (essentially what is above but now you have two different state variables).
The parent becomes
const WeeklyTrafficCardContainer = (props) => {
const [requestDateRange, setRequestDateRange] = useState();
const updateRequestDateRange = (dateRange) => {
setRequestDateRange(dateRange)
}
return (
<div>
{/* your return */}
<WeeklyTrafficCard updateDateRange={updateRequestDateRange} />
</div>
)
}
Then in your WeeklyTrafficCard call props.updateDateRange and pass it the date range whenever each_daterange changes.
Ciao, of course you need a global state manager. My preferred is react-redux. In few word, react-redux allows you to have a state that is shared in all your components. Sharing each_daterange between WeeklyTrafficCardContainer and WeeklyTrafficCard will be very easy if you decide to use it.
This is the more appropriate guide to quick start with react-redux. have a nice coding :)
Keep the value outside of the component, where both can access it. There are other ways to do this, but just as a simple example you could create a simple "store" to hold it and reference that store from each component that needs it:
class Store {
setDateRange (newDateRange) {
this._dateRange = newDateRange;
}
get dateRange () {
return this._dateRange;
}
}
export default new Store(); // singleton; everyone gets the same instance
import store from './Store';
const WeeklyTrafficCard = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
import store from './Store';
const WeeklyTrafficCardContainer = (props) => {
// use current dateRange value
const dateRange = store.dateRange;
// set new dateRange
store.setDateRange( newDateRange );
// do other stuff
}
If you want store updates to trigger component re-renders you'd need to add some higher order component plumbing, like redux's connect, or some other mechanism for triggering updates:
// pseudocode; make store an event emitter and return
// a component that re-renders on store events
store.connect = Component => {
return props => {
React.useEffect(() => {
store.addEventListener( ... )
return () => store.removeEventListener( ... )
})
}
}
Or if the components share a common parent, you could lift the state to the parent and pass the information to each component as props. If either component updates the value, the parent state change will trigger a re-render of both components with the new value:
const Parent = () => {
const [dateRange, setDateRange] = React.useState();
return (
<>
<WeeklyTrafficCardContainer
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
<WeeklyTrafficCard
dateRange={dateRange}
onDateRangeChange={newRange => setDateRange(newRange)}
/>
</>
);
}
Let's rephrase the objective here.
Objective: access each_daterange from WeeklyTrafficCard component in WeeklyTrafficCardContainer component.
Note: simply put, choose the following case based on your problem.
choose using prop if the variable is to be accessed by only one component
choose using context if the variable is to be accessed by more than one components
Solution Cases:
Case A: using prop.
Case A.1. WeeklyTrafficCard is the parent of WeeklyTrafficCardContainer
each_datarange being passed from WeeklyTrafficCard component as prop to WeeklyTrafficCardContainer component
working example for reference: codesandbox - variable passed as prop
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case A.2. WeeklyTrafficCard & WeeklyTrafficCardContainer are children of a parent, say WeeklyTraffic component
each_datarange will be present in WeeklyTraffic component which is shared among WeeklyTrafficCard component & WeeklyTrafficCardContainer component
// WeeklyTraffic.jsx file
const WeeklyTraffic = () => {
const [each_daterange, setDateRange] = useState();
return (
<>
...
<WeeklyTrafficCard eachDateRange={each_daterange} />
<WeeklyTrafficCardContainer eachDateRange={each_daterange} />
</>
);
};
// WeeklyTrafficCard.jsx file
const WeeklyTrafficCard = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
// WeeklyTrafficCardContainer.jsx file
const WeeklyTrafficCardContainer = props => {
const eachDateRange = props.eachDateRange;
return (
<>
...
</>
);
};
Case B: using context.
follow blog example found: blog - react context
this is preferred way to implement if the variable/variables is/are shared or need to be accessed by more than 1 components