How to get the React context from another context? - reactjs

I have two contexts - gameContext, roundContext. I am using useReducer to manipulate the state. Can I dispatch action in gameContext from roundContext reducer (RoundReducer)?
The reducer function is defined outside RoundProvider component.
const RoundReducer = (state: RoundStateType, action: any) => {
///sth
}
const RoundProvider: React.FC<{}> = ({ children }) => {
const [state, dispatch] = useReducer(RoundReducer, initState);
return (
<RoundContext.Provider
value={{ roundState: state, roundDispatch: dispatch }}>
{children}
</RoundContext.Provider>
);
};
The GameProvider component looks the same.

If you have nested contexts GameContext and RoundContext you can access the outer game context from the inner round, then call a setter/dispatch method to initiate a change in each. The inner RoundContext provider is inside the GameContext provider, so there (dispatch in this example) you have access to the methods exposed by the GameContext.
const GameContext = React.createContext(null);
const GameProvider = ({ children }) => {
const [gameState, setGameState] = React.useState();
return (
<GameContext.Provider value={{ gameState, setGameState }}>
{children}
</GameContext.Provider>
);
};
const useGame = () => React.useContext(GameContext)
const RoundContext = React.createContext(null);
const RoundProvider = () => {
const { gameState, setGameState } = useGame();
const [roundState, setRoundState] = React.useState();
const dispatch = (value) => {
// Do something to both the round and the game state
setGameState(value.toUpperCase());
setRoundState(value);
};
return (
<RoundContext.Provider value={{ roundState, dispatch }}>
{children}
</RoundContext.Provider>
);
}
const useRound = () => React.useContext(RoundContext)
const Main = () => {
const game = useGame()
const round = useRound()
const handleAction = () => {
round.dispatch('some value that also goes to the game')
}
return <>
<input type='text' onChange={handleAction} />
<div>{game.gameState}</div>
<div>{round.roundState}</div>
</>
}
const App = () => (<GameProvider>
<RoundProvider>
<Main />
</RoundProvider>
</GameProvider>)
Here's a codesandbox example:
https://codesandbox.io/s/xenodochial-wind-gkhje

pass the prop from the game context to roundContext or other way around, let say if you're using react hooks - useState,in props pass the setValue.
Please review the following code.
also, this is just for referance
export default function GameComponent(){
//suppose this is parent component and you want to change the value from child componenet
const [value, setValue] = useState("Patel");
return(
<div>
<h1>{value}</h1>
<RoundComponent setValue={setValue} value={value} />
//pass props like this
</div>
)
}
now coming back to round component
export default function RoundComponent(props){
return(
<div>
<input type="text" name="name" value={props.value} onChange={e=>props.setValue(e.target.value)}/>
</div>
)
}
I hope this answers your question.

Related

React Refs undefined inside functions and has values outside

I am having a lot of troubles working with react refs, what i want is to use a function declared in another components
the below code is what i am doing:
const Component1 = (props, ref) => {
const getText = () => {};
React.useImperativeHandle(ref, () => ({ getText }));
return <div />;
};
export default React.forwardRef(Component1);
const Component2 = (props) => {
const component1Ref = React.createRef();
const getTextFromComponent1 = () => {
console.log({ component1Ref }); //will be equal to {current:null}
};
console.log({ component1Ref }); //will be equal to {current:{getText}}
return <Component1 ref={component1Ref} />;
};
export default Component2;
It is very weird, the value inside getTextFromComponent1 was the same as outside, it suddenly broke! this happened with me many times
Anyone has a clue of the solution?
Features are breaking without any change
Thanks in advance
Hanan
It will take some time for ref to be initialized. I have shown an example to call it from a click event handler and within a useEffect hook.
Following works without any issues:
const Component1 = React.forwardRef((props, ref) => {
const textRef = React.createRef();
const getText = () => {
return textRef?.current?.value;
};
React.useImperativeHandle(ref, () => ({ getText }));
return <input ref={textRef} defaultValue="sample text" />;
});
const Component2 = (props) => {
const component1Ref = React.createRef();
React.useEffect(() => {
getTextFromComponent1();
}, []);
const getTextFromComponent1 = () => {
console.log(component1Ref.current?.getText());
};
return (
<div>
<button onClick={getTextFromComponent1}>Check text</button>
<br />
<Component1 ref={component1Ref} />
</div>
);
};
export default Component2;
Working Demo

React TS: wait for props before calling function with props as arguments

I'm using React through functional components. One of my components has props that are passed by its parent components, like:
const About = () => {
const { data } = useFetch('About');
return (
<div>
{data && (
<div>
<Title title={data.custom_title} />
<Card
text={data.body}
>
<Skills skills={data.skills} columns={data.n_skills_columns} />
</Card>
</div>
)}
</div>
);
};
These props are used in a function to manipulate the prop data before rendering it, such as:
const Skills: FC<SkillsProps> = ({ skills, columns }): JSX.Element => {
const skillsTable = sliceArray(skills, columns);
return (
<div>
skillsTable.map(...)
</div>
);
};
Within the Skills component, the props contain data when I console.log the data with an useEffect hook, nevertheless sliceArray() complains that its arguments are undefined.
What I already tried:
const Skills: FC<SkillsProps> = ({ skills_, columns_ }): JSX.Element => {
const [skills, setSkills] = useState([]);
const [columns, setColumns] = useState(1);
useEffect(() => {
setSkills(skills_);
setColumns(columns_);
}, []);
const skillsTable = sliceArray(skills, columns);
What am I missing here?
You can add default values to the destructured props. There's no need for a useEffect or useState (unless you're modifying these values somehow in your Skills component).
const Skills: FC<SkillsProps> = ({ skills = [], columns = 1 }) => {
const skillsTable = sliceArray(skills, columns);
return ...;
}
If you don't want to render the Skills component at all before skills_ and columns_ are loaded, then add a loading indicator in the parent component:
const ParentComponent = () => {
const skills = ...;
if (!skills) return <>Loading skills...</>;
return <Skills skills={skills} />;
}

How do I rerender a component from another competent

I have an ActivityFeed of posts. When I click on an icon in the ActivityPost component it saves the postId in a global state (EditPostIndex) that's meant to act like a toggle for the CreatePost and EditPost component in the Activity feed. When I click on the editpost icon it brings up the body of the post that I'm suppose to edit
ActivityFeed
const ActivityFeed = () => {
const {posts} = useContext(GlobalContext);
const {editPostIndex} = useContext(GlobalContext);
return (
<div id="mobile-activity">
<DeviceNav />
{ editPostIndex === null ?
<CreatePost />
:
<EditPost />
}
{posts.slice(0).reverse().map(post => (
<ActivityPost key={post.id} post={post} />
))}
</div>
)
}
ActivityPost
function ActivityPost({post, index}) => {
const {toggleEditPost} = useContext(GlobalContext);
function updatePost(index){
toggleEditPost(index)
}
}
EditPost.js
const EditPost = () => {
const {posts} = useContext(GlobalContext);
const {updatePost} = useContext(GlobalContext);
const {editPostIndex} = useContext(GlobalContext);
let val = posts[editPostIndex].body;
let [body, setBody] = useState(val);
function editPost() {
//update
}
return (
<div id="make-post">
<div id="create-post">
<textarea value={body} onChange={(e) => setBody(e.target.value)} id="post-activity" placeholder="Tell them what you think."></textarea>
</div>
<div id="create-post-actions">
<button onClick={editPost} id="post">Edit</button>
</div>
</div>
)
}
GlobalState/GlobalContext
const initialState = {
posts: posts,
editPostIndex: null
}
export const GlobalProvider = ({children}) => {
const [state, dispatch] = useReducer(AppReducer, initialState)
function toggleEditPost(index = null){
dispatch({
type: 'TOGGLE_EDIT_POST',
payload: index
})
//alert(index);
}
function updatePost(post){
dispatch({
type: 'UPDATE_POST',
payload: post
})
toggleEditPost(null);
}
}
The problem is that in EditPost component let val = posts[editPostIndex].body; let [body, setBody] = useState(val); the useState only renders once because the EditPostIndex is already changed. How do I make it so when I click on the edit post icon the let [body, setBody] = useState(val); changes to the posts body that I want to edit? Or rerender the EditPost component so setBody is set again?
In this case, I'd say you'd need more hooks like useState & useEffect to detect a change in your context EditPost.js.
const [postIndex, setPostIndex] = useState(editPostIndex);
useEffect(() => {
if(editPostIndex !== postIndex){
setPostIndex(editPostIndex);
setBody(posts[editPostIndex].body)
}
}, [setPostIndex, postIndex])
You can use redux, which is actually built for this purpose. With redux you can subscribe components to the redux store and then push updates which will automatically update the subscribed components.

React hooks useEffect calls mutiple times when redux store other data changed

my code like this:
Info component:
import {
getAttachData,
} from '#src/actions/creators/account'
const Info: React.FC = () => {
const info = useSelector<any, Account>(state => state.getIn(['account', 'info']).toJS())
const list = useSelector<any, Data[]>(state => state.getIn(['account', 'list']).toJS())
const attach = useSelector<any, AttachData[]>(state => state.getIn(['account', 'attach']).toJS())
...
const handleChange = ({ select }) => {
dispatch(getAttachData({v: select}))
}
const Template = (params) => {
return (
<div>
<BaseSelect onChange={(val) => handleChange(val)} list={list} />}
</div>
)
}
return (
...
<Template data={info} />
{attach.map((child, cidx) => (<Template data={child} />))}
)
}
export default Info
BaseSelect component:
const BaseSelect: React.FC<Props> = props => {
const [selectId, setSelectId] = useState('')
const { list } = props
useEffect(() => {
if (!isEmpty(list)) {
...
}
console.log('init')
}, [])
const handleChange = (value) => {
setSelectId(value)
props.onChange({
select: value,
})
}
return (
<Select
data={list}
value={selectId}
onChange={handleChange}
/>
)
}
export default BaseSelect
when excute handleChange event in BaseSelect component, the props.onChange function will call handleChange event in info component, and dispatch http request getAttachData which will change attach data in redux store, but useEffect in BaseSelect component will also excute and in console will print 'init' two times.
console:
It's because your Template component re-creates every time when redux store is changing.
Just move Template component outside the Info component.

React useContext value is not getting passed from the provider?

I'm trying to pass a value down using useContext as below
This is Context.js
export const selectedContext = React.createContext();
export const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
<Pies />
</selectedContext.Provider>
);
};
I'm calling the context in one of the components like so
This is in Card.js (a child in the provider)
const value = React.useContext(selectedContext);
console.log(value);
When I initialize the value from React.createContext, the value is passed down to my component but when I try using the provider it doesn't work.
What am I doing wrong?
When you are using React.useContext like this it's not wire into the <Context.Provider>
Please see the docs on who to use React.useContext here.
It's seems that the React.useContext will not work with in the Provider direct component children, so you need to make one more component in between. (like in the docs example)
const selectedContext = React.createContext();
const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
</selectedContext.Provider>
);
};
const Cards = () => {
const value = React.useContext(selectedContext);
console.log(value); // will not work
return (
<Card />
);
};
const Card = () => {
const value = React.useContext(selectedContext);
console.log(value); // will work
return (
<div>My Card</div>
);
};
If you need it to work on the first layer of component you can use <Context.Consumer> and it will work within.
const selectedContext = React.createContext();
const SelectProvider = () => {
return (
<selectedContext.Provider value={"Team One"}>
<Cards />
</selectedContext.Provider>
);
};
const Cards = () => {
const value = React.useContext(selectedContext);
console.log(value); // will not work
return (
<div>
<selectedContext.Consumer>
{({value}) => (
<h1>{value}</h1> // will work
)}
</selectedContext.Consumer>
</div>
);
};
Your code is fine, but you should "call the context" in the child component of the provider, as the value is available in Provider's children:
export const SelectedContext = React.createContext();
export const SelectProvider = ({ children }) => {
return (
<SelectedContext.Provider value={'Team One'}>
{children}
</SelectedContext.Provider>
);
};
const ProviderChecker = () => {
const value = React.useContext(SelectedContext);
return <div>{value}</div>;
};
const App = () => {
return (
<SelectProvider>
<ProviderChecker />
</SelectProvider>
);
};

Resources