import {
Consumer,
IFormConsumer
} from "react-context-form/src";
import React, { FunctionComponent, useState } from "react";
import Table from './table';
import {Modal} from './modal'
interface ListProps{
// list props
}
interface TargetProps{
// target props
}
interface ValueProps {
// value props
}
interface ReturnProps {
// return props
}
export const List: FunctionComponent<ListProps> = ({
readOnly = false
}) => {
const [formModalOpen, setFormModalOpen] = useState<boolean>(false);
const onFormCancel = () => {
setFormModalOpen(false)
};
const [targetRow, setTargetRow] = useState<TargetProps | undefined>(
undefined
);
const onCreateClick = () => {
setTargetRow(undefined);
setFormModalOpen(true);
};
const handleEdit = (row: any) => {
setTargetRow(row.original);
setFormModalOpen(true);
}
let arr: any = []; // some data
let newArr: any = []; // some data
const fieldOptions = () => {
return [...arr, ...newArr];
};
const getInitialData = (
values: ValueProps
): ReturnProps[] => {
const data = get(values, "gqlMedia");
return data && Array.isArray(data) ? data.slice() : [];
};
return (
<Consumer>
{(consumerProps: IFormConsumer) => {
const initialData = getInitialData(consumerProps.values);
return (
<>
<Modal
isOpen={formModalOpen}
onCancel={onFormCancel}
targetRow={targetRow}
data={initialData}
fieldOptions={fieldOptions}
/>
<Table
initialData={initialData}
handleEdit={handleEdit}
onCreateClick={onCreateClick}
readOnly
fieldOptions={fieldOptions}
/>
<>
)
}}
</Consumer>
);
};
When I move to a page other than the 1st page in the table and edit a row, the edit modal opens. The table is automatically rendering the 1st page in the background. The page should stay when the modal is opened. I don't want the Table component to be rerendered when the modal is opened (the state is changed). Tried using useCallback, useMemo, React.memo. Not able to find a solution.
Related
So I am trying to store a global state using context to allow me to use the same state across different components.
The issue I am having is that when I set the global state in 1 component and try to access it in the other component to use the state. It appears to be null and I cannot figure out why?
The first component where I set the global state in will always be rendered before the component shown that seems to have an empty value for the global state.
GlobalStateProvider component:
import React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
const defaultActivitiesState = [];
const globalStateContext = React.createContext(defaultActivitiesState);
const dispatchStateContext = React.createContext([]);
export const useGlobalState = () =>
[
React.useContext(globalStateContext),
React.useContext(dispatchStateContext)
];
const GlobalStateProvider = ({ children }) => {
const [state, dispatch] = React.useReducer((state, newValue) => (state, newValue),
defaultActivitiesState
);
return (
<globalStateContext.Provider value={state}>
<dispatchStateContext.Provider value={dispatch}>
{children}
</dispatchStateContext.Provider>
</globalStateContext.Provider>
);
}
export default GlobalStateProvider;
Component I set the global state in:
import react from "react";
import { useState, useEffect, useMemo } from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import axios from "axios";
import polyline from "#mapbox/polyline";
import MapComp from "./MapComp";
import { useGlobalState } from "./GlobalStateProvider";
function Map() {
// ------- global state
const [activities, setActivities] = useGlobalState(); // global state
//const [activities, setActivities] = useState([]);
//const [polylines, setPolylines] = useState(null); // as empty array value is still truthy
const [isLoading, setIsLoading] = useState(true);
const [mapMode, setMapMode] = useState("light");
const [mapStyle, setMapStyle] = useState(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
);
const [showMarkers, setShowMarkers] = useState(true);
useEffect(() => {
setActivitieData();
console.log("activities after useEffect", activities)
}, []);
const polylines = useMemo(() => {
console.log("activities inside memo", activities)
console.log("activities.len =", activities.length);
if (activities.length) {
console.log("past len");
const polylineArray = [];
for (const item of activities) {
const polylineData = item.map.summary_polyline;
const activityName = item.name;
const activityType = item.type;
polylineArray.push({
positions: polyline.decode(polylineData),
name: activityName,
activityType: activityType,
});
}
setIsLoading(false);
return polylineArray;
}
return null;
}, [activities]);
const toggleMarkers = () => {
setShowMarkers((show) => !show);
};
const getActivityData = async () => {
console.log("calling")
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
console.log("Global activities state = ", activities);
};
return !isLoading && polylines ? (
<>
<div className="select-container">
<button className="toggle-markers" onClick={() => toggleMarkers()}>
Toggle Markers
</button>
</div>
<MapComp
className={`${mapMode}`}
activityData={{ polylines }}
showMarkers={showMarkers}
/>
</>
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
component that has an empty value for global state:
import React from 'react';
import { useGlobalState } from './GlobalStateProvider';
function ActivityList() {
const [activities, setActivities] = useGlobalState();
let displayValues;
displayValues =
activities.map((activity) => {
return (
<div>
<p>{activity.name}</p>
<p>{activity.distance}m</p>
</div>
);
})
return (
<>
<p>Values</p>
{displayValues}
</>
);
}
export default ActivityList;
App.js:
function App() {
return (
<GlobalStateProvider>
<div className="App">
<NavBar />
<AllRoutes />
</div>
</GlobalStateProvider>
);
}
export default App;
I am trying to add, new data that user enters, to the array declared outside the component named DUMMY_MEALS and then render it as a list.
The problem here is the 'data' which is an object adds twice or more to the DUMMY_MEALS and renders twice in the page. Why is this happening?
The component with issue
"use strict";
import React from "react";
let DUMMY_MEALS = [
{id: "m1", name: "Sushi"},
];
const MealList = ({data}) => {
//const data = {id: "m5", name: "pushi"}
let mealslist = [];
DUMMY_MEALS = [data, ...DUMMY_MEALS];
mealslist = DUMMY_MEALS.map((meal) => <li>{meal.name}</li>);
return <ul>{mealslist}</ul>;
};
export default MealList;
Parent component
const Header = () => {
const [data, setData] = useState({});
const sendInputData = (inputData) => {
setData(inputData);
}
return (
<>
<MealsList data={data}/>
<MealForm getInputData={sendInputData}/>
</>
);
};
export default Header;
Sibling Component
const MealForm = (props) => {
const [name, setName] =useState("");
const formSubmitHandler = (e) => {
e.preventDefault();
let inputData = {
key : Math.random(),
name : name,
}
props.getInputData(inputData);
inputData = {};
}
return (
<form onSubmit={formSubmitHandler}>
<label htmlFor="name">name</label>
<input type="text" id="name" value={name} onChange={(e)=>setName(e.target.value)}></input>
<button>Submit</button>
</form>
);
};
export default MealForm;
You should use useState hook instead of let mealslist = []; Inside your MealList component.
And don't use DUMMY_MEALS as the component state. use useEffect hook to add the new meal to the state just once.
Check out this tested code CodeSandbox
MealList component changed as follow :
const MealList = ({ data }) => {
const [mealslist, setMealList] = useState([]);
useEffect(() => {
if (data)
setMealList([data, ...DUMMY_MEALS]);
}, []);
return <ul>{
mealslist.map((meal)=>{ <ListRender meal={meal} />})
}
</ul>;
};
And here is your App component:
const data = {
id: "k123",
name: "Falafel",
description: "An Iranian food.",
price: 16.5
};
export default function App() {
return (
<MealList data={data} />
);
}
I am trying to call a function from a different component but when I console.log('hi') it appear but it didn't call the messageContext.
Here is my follwing code from Invitees.js:
const [showPreview, setShowPreview] = useState(false);
const toggleUserPreview = () => {
setShowPreview(!showPreview);
};
{showPreview && (
<ResultsWrappers togglePreview={toggleUserPreview}>
<UserPreview
userInfo={applicant}
skillStr={applicant.Skills}
togglePreview={toggleUserPreview}
/>
</ResultsWrappers>
)}
Here is the component have the function I want to call UserPreview.js:
import { useMessageContextProvider } from "../context/MessageContext";
const UserPreview = ({ userInfo, skillStr, togglePreview }) => {
const messageContextProvider = useMessageContextProvider();
const messageUser = () => {
togglePreview();
messageContextProvider.updateActiveUserToMessage(userInfo);
console.log('hi');
};
...
};
Here is my messageContext:
import { createContext, useContext, useState } from "react";
const messageContext = createContext();
export const MessageContextProvider = ({ children }) => {
const [activeUserToMessage, setActiveUserToMessage] = useState({});
const [isOpenMobileChat, toggleMobileChat] = useState(false);
const updateActiveUserToMessage = (user) => {
setActiveUserToMessage(user);
};
return (
<messageContext.Provider
value={{
updateActiveUserToMessage,
activeUserToMessage,
isOpenMobileChat,
toggleMobileChat,
}}
>
{children}
</messageContext.Provider>
);
};
export const useMessageContextProvider = () => {
return useContext(messageContext);
};
When the messageContext called it should open the chatbox like this:
The code you showing is not enough to say it for 100%, but it seems like toggleUserPreview - function called twice, so it reverted to original boolean value.
One time as <ResultsWrappers togglePreview={toggleUserPreview}/>
and second time as <UserPreview togglePreview={toggleUserPreview}/>.
Let's say I'm having a Parent Component providing a Context which is a Store Object. For simplicity lets say this Store has a value and a function to update this value
class Store {
// value
// function updateValue() {}
}
const Parent = () => {
const [rerender, setRerender] = useState(false);
const ctx = new Store();
return (
<SomeContext.Provider value={ctx}>
<Children1 />
<Children2 />
.... // and alot of component here
</SomeContext.Provider>
);
};
const Children1 = () => {
const ctx = useContext(SomeContext);
return (<div>{ctx.value}</div>)
}
const Children2 = () => {
const ctx = useContext(SomeContext);
const onClickBtn = () => {ctx.updateValue('update')}
return (<button onClick={onClickBtn}>Update Value </button>)
}
So basically Children1 will display the value, and in Children2 component, there is a button to update the value.
So my problem right now is when Children2 updates the Store value, Children1 is not rerendered. to reflect the new value.
One solution on stack overflow is here. The idea is to create a state in Parent and use it to pass the context to childrens. This will help to rerender Children1 because Parent is rerendered.
However, I dont want Parent to rerender because in Parent there is a lot of other components. I only want Children1 to rerender.
So is there any solution on how to solve this ? Should I use RxJS to do reative programming or should I change something in the code? Thanks
You can use context like redux lib, like below
This easy to use and later if you want to move to redux you change only the store file and the entire state management thing will be moved to redux or any other lib.
Running example:
https://stackblitz.com/edit/reactjs-usecontext-usereducer-state-management
Article: https://rsharma0011.medium.com/state-management-with-react-hooks-and-context-api-2968a5cf5c83
Reducers.js
import { combineReducers } from "./Store";
const countReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case "INCREMENT":
return { ...state, count: state.count + 1 };
case "DECREMENT":
return { ...state, count: state.count - 1 };
default:
return state;
}
};
export default combineReducers({ countReducer });
Store.js
import React, { useReducer, createContext, useContext } from "react";
const initialState = {};
const Context = createContext(initialState);
const Provider = ({ children, reducers, ...rest }) => {
const defaultState = reducers(undefined, initialState);
if (defaultState === undefined) {
throw new Error("reducer's should not return undefined");
}
const [state, dispatch] = useReducer(reducers, defaultState);
return (
<Context.Provider value={{ state, dispatch }}>{children}</Context.Provider>
);
};
const combineReducers = reducers => {
const entries = Object.entries(reducers);
return (state = {}, action) => {
return entries.reduce((_state, [key, reducer]) => {
_state[key] = reducer(state[key], action);
return _state;
}, {});
};
};
const Connect = (mapStateToProps, mapDispatchToProps) => {
return WrappedComponent => {
return props => {
const { state, dispatch } = useContext(Context);
let localState = { ...state };
if (mapStateToProps) {
localState = mapStateToProps(state);
}
if (mapDispatchToProps) {
localState = { ...localState, ...mapDispatchToProps(dispatch, state) };
}
return (
<WrappedComponent
{...props}
{...localState}
state={state}
dispatch={dispatch}
/>
);
};
};
};
export { Context, Provider, Connect, combineReducers };
App.js
import React from "react";
import ContextStateManagement from "./ContextStateManagement";
import CounterUseReducer from "./CounterUseReducer";
import reducers from "./Reducers";
import { Provider } from "./Store";
import "./style.css";
export default function App() {
return (
<Provider reducers={reducers}>
<ContextStateManagement />
</Provider>
);
}
Component.js
import React from "react";
import { Connect } from "./Store";
const ContextStateManagement = props => {
return (
<>
<h3>Global Context: {props.count} </h3>
<button onClick={props.increment}>Global Increment</button>
<br />
<br />
<button onClick={props.decrement}>Global Decrement</button>
</>
);
};
const mapStateToProps = ({ countReducer }) => {
return {
count: countReducer.count
};
};
const mapDispatchToProps = dispatch => {
return {
increment: () => dispatch({ type: "INCREMENT" }),
decrement: () => dispatch({ type: "DECREMENT" })
};
};
export default Connect(mapStateToProps, mapDispatchToProps)(
ContextStateManagement
);
If you don't want your Parent component to re-render when state updates, then you are using the wrong state management pattern, flat-out. Instead you should use something like Redux, which removes "state" from the React component tree entirely, and allows components to directly subscribe to state updates.
Redux will allow only the component that subscribes to specific store values to update only when those values update. So, your Parent component and the Child component that dispatches the update action won't update, while only the Child component that subscribes to the state updates. It's very efficient!
https://codesandbox.io/s/simple-redux-example-y3t32
React component is updated only when either
Its own props is changed
state is changed
parent's state is changed
As you have pointed out state needs to be saved in the parent component and passed on to the context.
Your requirement is
Parent should not re-render when state is changed.
Only Child1 should re-render on state change
const SomeContext = React.createContext(null);
Child 1 and 2
const Child1 = () => {
const ctx = useContext(SomeContext);
console.log(`child1: ${ctx}`);
return <div>{ctx.value}</div>;
};
const Child2 = () => {
const ctx = useContext(UpdateContext);
console.log("child 2");
const onClickBtn = () => {
ctx.updateValue("updates");
};
return <button onClick={onClickBtn}>Update Value </button>;
};
Now the context provider that adds the state
const Provider = (props) => {
const [state, setState] = useState({ value: "Hello" });
const updateValue = (newValue) => {
setState({
value: newValue
});
};
useEffect(() => {
document.addEventListener("stateUpdates", (e) => {
updateValue(e.detail);
});
}, []);
const getState = () => {
return {
value: state.value,
updateValue
};
};
return (
<SomeContext.Provider value={getState()}>
{props.children}.
</SomeContext.Provider>
);
};
Parent component that renders both the Child1 and Child2
const Parent = () => {
// This is only logged once
console.log("render parent");
return (
<Provider>
<Child1 />
<Child2 />
</Provider>
);
};
Now for the first requirement when you update the state by clicking button from the child2 the Parent will not re-render because Context Provider is not its parent.
When the state is changed only Child1 and Child2 will re-render.
Now for second requirement only Child1 needs to be re-rendered.
For this we need to refactor a bit.
This is where reactivity comes. As long as Child2 is a child of Provider when ever the state changes it will also gets updated.
Take the Child2 out of provider.
const Parent = () => {
console.log("render parent");
return (
<>
<Provider>
<Child1 />
</Provider>
<Child2 />
</>
);
};
Now we need some way to update the state from Child2.
Here I have used the browser custom event for simplicity. You can use RxJs.
Provider is listening the state updates and Child2 will trigger the event when button is clicked and state gets updated.
const Provider = (props) => {
const [state, setState] = useState({ value: "Hello" });
const updateValue = (e) => {
setState({
value: e.detail
});
};
useEffect(() => {
document.addEventListener("stateUpdates", updateValue);
return ()=>{
document.addEventListener("stateUpdates", updateValue);
}
}, []);
return (
<SomeContext.Provider value={state}>{props.children}</SomeContext.Provider>
);
};
const Child2 = () => {
console.log("child 2");
const onClickBtn = () => {
const event = new CustomEvent("stateUpdates", { detail: "Updates" });
document.dispatchEvent(event);
};
return <button onClick={onClickBtn}>Update Value </button>;
};
NOTE: Child2 will not have access to context
I hope this helps let me know if you didn't understand anything.
I am checking to see if isFetchingData then don't render yet but its not re-rendering once isFetchingData is set to false. I have the useEffect in the context and i would hope that would re-render once isFetchingData is set to false. Any ideas?
When I refresh the page it renders with the data. So I think its to do with re-rendering.
I am using react context to get the data and exposing functions to filter that data and get me what i need.
Context:
import React, { useEffect, useState } from 'react';
import getAllEmployees from 'my-services/employee/getAllEmployees';
import { arrayOf, node, oneOfType } from 'prop-types';
export const EmployeeContext = React.createContext({
allEmployees: [],
getActiveEmployees: () => [],
getTerminatedEmployees: () => []
});
const EmployeesProvider = ({ children }) => {
const [isFetchingData, setIsFetchingData] = useState(true);
const [allEmployees, setAllEmployees] = useState({});
useEffect(() => {
getAllEmployees().then(
//doing something
).then(employees => {
setAllEmployees(employees);
setIsFetchingData(false);
});
}, [isFetchingData])
const context = {
isFetchingData,
allEmployees,
getActiveEmployees: () =>
allEmployees.filter(x => x.status === 'Current'),
getTerminatedEmployees: () =>
allEmployees.filter(x => x.status === 'Terminated')
};
return (
<EmployeeContext.Provider value={context}>{children}</EmployeeContext.Provider>
);
};
EmployeesProvider.propTypes = {
children: oneOfType([node, arrayOf(node)])
};
EmployeesProvider.defaultProps = {
children: undefined
};
export default EmployeesProvider;
Component:
import React, { useContext } from 'react';
import styled from 'styled-components';
import { EmployeeContext } from 'my-contexts/EmployeeContext';
import EmployeeCard from '../../../components/EmployeeCard';
const EmployeesTab = () => {
const {
getActiveEmployees,
getTerminatedEmployees,
isFetchingData
} = useContext(EmployeeContext);
let activeEmployees = [];
let terminatedEmployees = [];
if (!isFetchingData) {
activeEmployees = getActiveEmployees();
terminatedEmployees = getTerminatedEmployees();
}
if(isFetchingData) {
return <p>Loading</p>;
}
return (
<Outer>
<TopHeader>
<H3>Employees ({activeEmployees.length})</H3>
</TopHeader>
<Wrapper>
{activeEmployees.map(employee => {
return (
<EmployeeCard
id={employee.id}
guid={employee.guid}
firstName={employee.name.first}
lastName={employee.name.last}
jobTitle={employee.jobTitle}
/>
);
})}
</Wrapper>
<H3>Terminated employees({terminatedEmployees.length})</H3>
<Wrapper>
{terminatedEmployees.map(employee => {
return (
<EmployeeCard
id={employee.id}
guid={employee.guid}
firstName={employee.name.first}
lastName={employee.name.last}
jobTitle={employee.jobTitle}
/>
);
})}
</Wrapper>
</Outer>
);
};
export default EmployeesTab;
I think many problems may exist.
At first, please check whether whole component is closed by context Provider.
For example
<EmployeesProvider>
<EmployeesTab/>
<EmployeesProvider/>
Please check this problem.