React update function from child using context - reactjs

How can i update a function from a child using context??
The context
import React from 'react';
const Fin_states = React.createContext({
title: "title_title"
});
export default Fin_states;
This is the main page , the aim is update function_() using set_function_(see the states const [function_,set_function_] ) , but doing it using the context in the List_of_list child.
import React , { useState ,useContext} from 'react';
import Input_list_panel from './Input_list_panel'
import Fin_states from './Financial_instrument_states'
export default function FinancialInstruments() {
//Attribs
const context = useContext(Fin_states);
const [value, set_value] = useState({title:"initial_title"})
const [function_, set_function_] = useState(() => (data) => console.log(data))
// set_function_ is going to be represented by update_function in the context provider.
return (
<>
<Fin_states.Provider value={{ data:value, update_value:set_value,update_function:set_function_}}>
<Input_list_panel/>
</Fin_states.Provider>
<button onClick={()=>function_("data")}>
Add item in child from parent
</button>
</>
);
}
This is the file where the function is going to be modified using useEffect in this line context.update_function(()=>updated_function)
import React , { useState , useEffect, forwardRef ,useRef,useImperativeHandle, useContext } from 'react';
import Fin_states from './Financial_instrument_states'
export default function List_of_list(props) {
const [list_of_list,set_list_of_list]= useState(["data_1","data_2"]);
const context = useContext(Fin_states);
const updated_function=(data)=>{
var new_item=[...list_of_list];
new_item.push(data)
set_list_of_list(new_item);
console.log(list_of_list)
}
useEffect(() => {
/*update_function represents at set_function_ to modify function_() state , see the context
provider in the parent FinancialInstruments*/
context.update_function(()=>updated_function)
}, [])
return (
<>
<Fin_states.Consumer>
{
(context)=>{
return(
<>
<button onClick={
()=>{
updated_function("data")
}
}>
Add item in child
</button>
</>
)
}
}
</Fin_states.Consumer>
</>
);
}
The problem is that when the state of function_() is updated in the main page (FinancialInstruments functional component) it does not use the same context than updated_function() in List_of_list.
How can i have the same result that i have in List_of_list using updated_function() but using function_() in FinancialInstruments?

Related

Unable to run the function from the context

I have a context, I import it into my functional component:
import { TaskContexts } from "../../../contexts";
The context stores data and functions.
The data comes from the context and is displayed on the site.
const {
editTodo,
setEditID,
toggleTodoCompletion,
editID,
editTodoHandler,
removeTodo,
state,
text,
isEditError,
} = useContext(TaskContexts);
But!
<button onClick={() => editTodo(todo.id)}>
<img src={editIcon} alt="edit button"></img>
</button>
When I try to call the editTodo function, It fails with the following error:
Uncaught TypeError: editTodo is not a function
How to fix this error?
UPD.
Full component code
import React, { useState } from 'react';
import ACTION_TYPES from '../ToDo/reducer/actionTypes';
import RenderedTable from './RenderedTable';
import styles from './TaskList.module.scss';
import allIcon from '../../icons/all.svg';
import completedIcon from '../../icons/completed.svg';
import notCompletedIcon from '../../icons/notCompleted.svg';
import mona from '../../icons/mona.gif';
import { TODO_TASK_CHEMA } from '../../utils/validationSchemas';
import { TaskContexts } from '../../contexts';
const TaskList = props => {
const {
reducerData: [state, dispatch],
} = props;
const [editID, setEditID] = useState(null);
const [editText, setEditText] = useState(null);
const [isEditError, setIsEditError] = useState(false);
const [mode, setMode] = useState('All');
const removeTodo = id => {
dispatch({ type: ACTION_TYPES.REMOVE, id });
};
const toggleTodoCompletion = id => {
dispatch({ type: ACTION_TYPES.TOGGLE, id });
};
const editTodo = id => {
const text = editText.trim();
try {
TODO_TASK_CHEMA.validateSync({ text });
} catch (e) {
setIsEditError(true);
throw new Error(e);
}
setIsEditError(false);
setEditID(null);
dispatch({ type: ACTION_TYPES.EDIT, id, text });
setEditText(null);
};
const editTodoHandler = ({ target: { value } }) => {
setEditText(value);
};
const contextsValues = {
editID,
setEditID,
editText,
setEditText,
isEditError,
setIsEditError,
mode,
setMode,
state
};
return (
<TaskContexts.Provider value={contextsValues}>
<div className={styles.container}>
{state.todos.length === 0 ? (
<div>
<h2 className={styles.noTask}>No tasks =)</h2>
<img src={mona} alt='mona gif' />
</div>
) : (
<>
<button
className={styles.section}
onClick={() => {
setMode('All');
}}
>
<img src={allIcon} alt='all button' />- All
</button>
<button
className={styles.section}
onClick={() => {
setMode('Completed');
}}
>
<img src={completedIcon} alt='completed button' />- Completed
</button>
<button
className={styles.section}
onClick={() => {
setMode('NotCompleted');
}}
>
<img src={notCompletedIcon} alt='not completed button' />- Not
completed
</button>
<RenderedTable
editTodo={editTodo}
setEditID={setEditID}
toggleTodoCompletion={toggleTodoCompletion}
editID={editID}
editTodoHandler={editTodoHandler}
removeTodo={removeTodo}
state={state}
mode={mode}
isEditError={isEditError}
/>
</>
)}
</div>
</TaskContexts.Provider>
);
};
export default TaskList;
All functions on this component do not work. But these are functions. I don't understand why React doesn't think so.
You need to do 3 things to pass the context values successfully:
Place the Context Provider at least one level above the Consuming Component.
Create Your Context, Declare all variables and methods within the Context, and Export the Context's Provider after passing the value Prop.
Consume the Context Values by importing the useContext() hook in TaskList.jsx/TaskList.js and calling it on the Provider object.
Place the Context Provider at least one level above the Consuming Component
The reason JavaScript thinks editTodo is not a function or is undefined is that you are trying to consume it in React within the <TaskList/> component before it (<TaskList/>) is even made aware of the context. By the time <TaskList/> has been rendered by React, it is too late to pass any context values. So we need to place the context, somewhere higher up the component tree where React will be made aware of the context and its values ahead of time before rendering (and passing the context values to) child components down the tree.
To fix this, place the context provider wrapper at least one level above the component that is consuming the values of the context provider. If more than one component needs values from the provider, the best place to place the provider wrapper would be in your App.jsx/App.js or your index.jsx/index.js file.
Inside App.jsx/App.js:
import { TaskProvider } from 'path/to/context';
function App() {
<TaskProvider>
{/* All your code/rendered elements/rendered route elements go here */}
</TaskProvider>
}
export default App;
or Inside index.jsx/index.js:
import React from "react";
import ReactDOM from "react-dom";
import { ToastProvider } from "path/to/context";
import "./index.css";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<ToastProvider>
<App />
</ToastProvider>
</React.StrictMode>,
document.getElementById("root")
);
I'll show you a better way to pass those context values.
Create Your Context, Declare all variables and methods within the Context, and Export the Context's Provider after passing the value Prop:
Inside TaskContexts.jsx/TaskContexts.js:
import {useContext, createContext } from "react";
// ...All your necessary imports
// Create context first
const TaskContexts = createContext();
export const TaskProvider = ({ children }) => {
const [editID, setEditID] = useState(null);
const [editText, setEditText] = useState(null);
const [isEditError, setIsEditError] = useState(false);
const [mode, setMode] = useState('All');
const removeTodo = id => {
dispatch({ type: ACTION_TYPES.REMOVE, id });
};
const toggleTodoCompletion = id => {
dispatch({ type: ACTION_TYPES.TOGGLE, id });
};
const editTodo = id => {
const text = editText.trim();
try {
TODO_TASK_CHEMA.validateSync({ text });
} catch (e) {
setIsEditError(true);
throw new Error(e);
}
setIsEditError(false);
setEditID(null);
dispatch({ type: ACTION_TYPES.EDIT, id, text });
setEditText(null);
};
// ...and the rest of the methods
// Prepare your contextValues object here
const contextValues = {
editID,
setEditID,
// ...and the rest
};
// Notice that we have called the provider here
// so that we don't have to do it within the `App.jsx` or `index.jsx`.
// We have also passed the default values here so we can that
// we don't have to export them and pass them in `App.jsx`.
// We used component composition to create a `hole` where the rest of
// our app, i.e, `{children}` will go in and returned the
// composed component from here, i.e, `<TaskProvider/>`.
// This is so that all the preparation of the context Provider object
// gets done in one file.
return (<TaskContexts.Provider value={contextValues}>
{children}
</TaskContexts.Provider>);
};
// Now, use the context, we will export it in a function called `useTask()`
// so that we don't have to call `useContext(TaskContexts)` every time we need values from the context.
// This function will call `useContext()` for us and return the values
// in the provider available as long as we wrap our app components
// with the provider (which we have already done).
export function useTask() {
return useContext(TaskContexts);
}
Consume the Context Values by importing the useContext() hook in TaskList.jsx/TaskList.js and calling it on the Provider object.
Since we've already called useContext on the provider object, we just need to import useTask() from earlier in TaskList.jsx, run it and it will return the contextValues object which we can destructure.
import React, { useState } from 'react';
import ACTION_TYPES from '../ToDo/reducer/actionTypes';
import RenderedTable from './RenderedTable';
import styles from './TaskList.module.scss';
import allIcon from '../../icons/all.svg';
import completedIcon from '../../icons/completed.svg';
import notCompletedIcon from '../../icons/notCompleted.svg';
import mona from '../../icons/mona.gif';
import { TODO_TASK_CHEMA } from '../../utils/validationSchemas';
// Import `useTask` only.
import { useTask } from '../../contexts';
const TaskList = props => {
// Values from context
const {editID, setEditID,...} = useTask();
const {
reducerData: [state, dispatch],
} = props;
const [editID, setEditID] = useState(null);
const [editText, setEditText] = useState(null);
const [isEditError, setIsEditError] = useState(false);
const [mode, setMode] = useState('All');
const removeTodo = id => {
dispatch({ type: ACTION_TYPES.REMOVE, id });
};
const toggleTodoCompletion = id => {
dispatch({ type: ACTION_TYPES.TOGGLE, id });
};
const editTodo = id => {
const text = editText.trim();
try {
TODO_TASK_CHEMA.validateSync({ text });
} catch (e) {
setIsEditError(true);
throw new Error(e);
}
setIsEditError(false);
setEditID(null);
dispatch({ type: ACTION_TYPES.EDIT, id, text });
setEditText(null);
};
const editTodoHandler = ({ target: { value } }) => {
setEditText(value);
};
return (
<div className={styles.container}>
{/*...everything else */}
<RenderedTable
editTodo={editTodo}
setEditID={setEditID}
toggleTodoCompletion={toggleTodoCompletion}
editID={editID}
editTodoHandler={editTodoHandler}
removeTodo={removeTodo}
state={state}
mode={mode}
isEditError={isEditError}
/>
</>
)}
</div>
);
};
export default TaskList;
In summary, scope everything about the context object to its own component, within its own file, export it and wrap all the children components in the root component (or wrap the root component itself), and call useContext() on the provider object in the component that needs the context values.

How do I get a state variable from child to parent?

I am learning React.
I have Component structure like this -
index.js
import React from "react";
import Button from "./Button/Button"
export default function Index() {
return (
<>
<Button />
<div>Value of flag in Index.js = {}</div>
</>
);
}
Button.js
import React, { useState } from "react";
import "./button.css";
export default function Button(props) {
const [flag, setFlag] = useState(true);
const clickHandler = () => {
setFlag(!flag);
};
return (
<div className="btn" onClick={clickHandler}>
Value of flag in Button.js = {flag.toString()}
</div>
);
}
My question is "How do I get flag value from Button.js to index.js" ? (child to parent).
1) You can lift state in parent component and pass state and handler as a prop to children.
Note: This is will work because you need flag in the JSX, But if you will pass event handler as a prop in the child component then you have to invoke the handler to get the value. So Either lift the state or use Redux
Live Demo
App.js
const App = () => {
const [flag, setFlag] = useState( true );
return (
<>
<Button flag={flag} setFlag={setFlag} />
<div>Value of flag in Index.js = { flag.toString() }</div>
</>
);
};
Button.js
export default function Button({ flag, setFlag }) {
const clickHandler = () => {
setFlag(oldFlag => !oldFlag);
};
return (
<div className="btn" onClick={clickHandler}>
Value of flag in Button.js = {flag.toString()}
</div>
);
}
2) You can pass handler as a prop in child component as shown in the Harsh Patel answer
3) You can use state management tool i.e. Redux.
You can send a value by method, refer to this:
index.js
import React from "react";
import Button from "./Button/Button"
export default function Index() {
let getFlagValue = (flag) => {
//here you'll get a flag value
console.log(flag)
}
return (
<>
<Button sendFlagValue=(getFlagValue)/>
<div>Value of flag in Index.js = {}</div>
</>
);
}
Button.js
import React, { useState } from "react";
import "./button.css";
export default function Button(sendFlagValue) {
const [flag, setFlag] = useState(true);
const clickHandler = () => {
setFlag(!flag);
sendFlagValue(flag)
};
return (
<div className="btn" onClick={clickHandler}>
Value of flag in Button.js = {flag.toString()}
</div>
);
}
There are two types state:
global state for all
private state for component
For starter, you must obey some policies, not try abuse state, otherwise you will have some troubles.
For global STATE, you can use Redux or dva or umijs.
For private STATE, you seems already known.

React Hooks useContext value not updating

I'm updating a username based on a form input from another component. I put a console.log inside the provider component to make sure it's getting updated... it is! But the value never updates on the component receiving this value.
Here is the provider component:
import React, { useState, useContext } from 'react';
export const GetFirstName = React.createContext();
export const GetLastName = React.createContext();
export const SetUserName = React.createContext();
export const UserNameProvider = ({ children }) => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
console.log(firstName);
return (
<SetUserName.Provider value={{ setFirstName, setLastName }}>
<GetFirstName.Provider value={firstName}>
<GetLastName.Provider value={lastName}>
{children}
</GetLastName.Provider>
</GetFirstName.Provider>
</SetUserName.Provider>
);
};
Account page (wraps the component with the provider so it can receive context):
import React, { useContext } from 'react';
import { useHistory } from 'react-router-dom';
import { GetLoggedIn, UserNameProvider } from '../Providers/providers.js';
import AccountHeader from './Account/AccountHeader.js';
import AccountItemsList from './Account/AccountItemsList.js';
import LoginModal from './Modal/LoginModal.js';
const Account = () => {
const history = useHistory();
const loggedIn = useContext(GetLoggedIn);
return !loggedIn ? (
<LoginModal closeModal={history.goBack} />
) : (
<div id='account-div'>
<UserNameProvider>
<AccountHeader />
</UserNameProvider>
<AccountItemsList /> // within AccountItemsList,
// another component is wrapped the same way
// to use setFirstName and setLastName
// this works fine, as the console.log shows
</div>
);
};
export default Account;
And finally the AccountHeader page, which receives only the initial value of '', then never reflects the current value after another component calls setFirstName.
import React, { useContext } from 'react';
import { GetFirstName } from '../../Providers/providers.js';
const AccountHeader = () => {
const firstName = useContext(GetFirstName);
return (
<div id='account-top'>
<img src='#' alt='User' />
<h1>{firstName}</h1>
</div>
);
};
Just to check my sanity I implemented a really simple version of this in a codepen and it works as it should. Elsewhere in my app I'm using context to check if the user is logged in. That is also working as it should. I've pulled almost all the hair out of my head.

How to prevent state passed as props from giving undefined

Good day ,I'm quite new to reactjs and I'm trying to pass state as props but it keeps giving me undefined in the component I want to make use of it in. This is where I pass the state, workspacelist is the component receiving the state
import React,{useState} from 'react'
import Background8 from '../Images/house3.jpg';
import { useHistory } from 'react-router-dom';
import Workspacelist from '../Workspace/Workspacelist';
function BoardList({ boards }) {
const history = useHistory()
const [currentid, setcurrentid] = useState("")
const navigate = (id) => {
setcurrentid(id)
history.push('./workspace');
return <Workspacelist id = {currentid} />
}
return (
<>
{
boards.map((board) => (
<li key={board.id} className="boardlist" style={styles} onClick={() => navigate(board.id)}>
<h3>{board.title}</h3>
</li>
))}
</>
)
}
export default BoardList
This is the short version of workspacelist component where I receive it
import React from 'react'
function Workspacelist({ id }) {
console.log(id)
}
but any time I log id it returns undefined

React native typescript: usecontext functions not firing from inside child component

I have an issue when i try to use functions from a context inside a child component in a React native android app.
Below is my code for the context, and the form component im using it in (stripped down for brevity).
The "isFormOpen" object can be read no problem from inside any children that is wrapped in the provider, but when i try to call the "toggleForm" function from the same child component, it does nothing, no console errors either.
I have another context which is identical in structure and syntax except for vairable and function names etc, and that works perfectly, so im a bit confused as to why this does not work. I removed the other context, thinking there might be some type of conflict, but didnt solve it.
AccountContext.tsx
import React, { FC, createContext, useContext, useState } from 'react';
interface AccountContextType {
isFormOpen: boolean,
toggleForm: (toggle: boolean) => void
};
export const AccountContext = createContext<AccountContextType>({
isFormOpen: false,
toggleForm: () => null
});
export const AccountContextProvider: FC = props => {
const [formOpen, setFormOpen] = useState<boolean>(false);
const toggleForm = (toggle: boolean) => {
setFormOpen(toggle);
}
const value: AccountContextType = {
isFormOpen: formOpen,
toggleForm
}
return (
<AccountContext.Provider value={value}>
{props.children}
</AccountContext.Provider>
)
}
export const useAccountContext = () => useContext(AccountContext);
TrackUploadForm.js
import React from 'react';
import { SafeAreaView } from 'react-native';
import { Button } from 'react-native-paper';
import { useAccountContext } from '../contexts/AccountContext';
import { AccountContextProvider } from '../contexts/AccountContext';
const TrackUploadForm = () => {
const accountContext = useAccountContext();
return (
<AccountContextProvider>
<SafeAreaView>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</SafeAreaView>
</AccountContextProvider>
)
};
export default TrackUploadForm;
useAccountContext is called outside the provider
export default function App() {
return (
<AccountContextProvider>
<Content />
</AccountContextProvider>
);
}
const Content = () => {
const accountContext = useAccountContext();
return (
<div className="App">
<h1>{accountContext.isFormOpen ? "true" : "false"}</h1>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</div>
);
};
accountContext.toggleForm(false) <-- always false, change it to accountContext.toggleForm(!accountContext.isFormOpen)
Together we have
https://codesandbox.io/s/cranky-panini-yo129

Resources